nesemu/rom/ines.c

170 lines
5.6 KiB
C

//
// Created by william on 12/2/23.
//
#include <stdbool.h>
#include <stdio.h>
#include <malloc.h>
#include "log.h"
#include "../include/rom.h"
#include "../include/system.h"
#include "../cpu/memory.h"
// Flag 6
#define NES_HEADER_FLAG_MIRRORING 0x01
#define NES_HEADER_FLAG_BATTERY 0x02
#define NES_HEADER_FLAG_TRAINER 0x04
#define NES_HEADER_FLAG_IGNORE_MIRRORING 0x08
// Lower nybble of mapper number
#define NES_HEADER_FLAG_LOWER_MAPPER_NUMBER 0xf0
// Flag 7
#define NES_HEADER_FLAG_VS_UNISYSTEM 0x01
#define NES_HEADER_FLAG_PLAYCHOICE_10 0x02
// Upper nybble of mapper number, not used in iNes 1.0 format
#define NES_HEADER_FLAG_UPPER_MAPPER_NUMBER 0xf0
// Flag 9
#define NES_HEADER_FLAG_TV_SYSTEM 0x01
// Flag 10
#define NES_HEADER_FLAG_PRG_RAM 0x10
#define NES_HEADER_FLAG_BUS_CONFLICT 0x20
struct INesHeaderFlags {
/* Mirroring: 0: horizontal (vertical arrangement) (CIRAM A10 = PPU A11)
1: vertical (horizontal arrangement) (CIRAM A10 = PPU A10) */
bool nametable_mirrored;
// 1: Ignore mirroring control or above mirroring bit; instead provide four-screen VRAM
bool ignore_mirroring;
// 1: Cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory
bool has_battery;
// 1: 512-byte trainer at $7000-$71FF (stored before PRG data)
bool has_trainer;
bool vs_unisystem;
// PlayChoice-10 (8 KB of Hint Screen data stored after CHR data)
bool playchoice_10;
bool tv_system;
bool has_prg_ram;
bool has_bus_conflict;
};
typedef struct {
// PRG ROM size in 16KB units
unsigned char prg_rom_size;
unsigned char prg_ram_size;
// CHR ROM size in 8KB units
unsigned char chr_rom_size;
unsigned char mapper_number;
struct INesHeaderFlags flags;
} INesHeader;
bool rom_is_ines(const byte header[16]) {
return header[0] == 'N' && header[1] == 'E' && header[2] == 'S';
}
INesHeader read_header(const byte *header_buf) {
INesHeader header;
unsigned char flag6 = header_buf[6];
unsigned char flag7 = header_buf[7];
unsigned char flag9 = header_buf[9];
unsigned char flag10 = header_buf[10];
header.flags.nametable_mirrored = flag6 & NES_HEADER_FLAG_MIRRORING;
header.flags.ignore_mirroring = flag6 & NES_HEADER_FLAG_IGNORE_MIRRORING;
header.flags.has_battery = flag6 & NES_HEADER_FLAG_BATTERY;
header.flags.has_trainer = flag6 & NES_HEADER_FLAG_TRAINER;
header.flags.vs_unisystem = flag7 & NES_HEADER_FLAG_VS_UNISYSTEM;
header.flags.playchoice_10 = flag7 & NES_HEADER_FLAG_PLAYCHOICE_10;
header.flags.tv_system = flag9 & NES_HEADER_FLAG_TV_SYSTEM;
header.flags.has_prg_ram = flag10 & NES_HEADER_FLAG_PRG_RAM | 0x01;
header.flags.has_bus_conflict = flag10 & NES_HEADER_FLAG_BUS_CONFLICT;
header.prg_rom_size = header_buf[4];
header.prg_ram_size = header_buf[8];
header.chr_rom_size = header_buf[5];
header.mapper_number = (flag6 & NES_HEADER_FLAG_LOWER_MAPPER_NUMBER) >> 4;
if (!header.prg_ram_size) {
// For compatibility, a value of 0 unit of PRG RAM infers 8KB, or 1 unit.
header.prg_ram_size = 1;
}
log_debug("=== iNes ROM Header ===");
log_debug("PRG: %dx16KB (ROM), %dx8KB (RAM)",
header.prg_rom_size,
header.prg_ram_size);
log_debug("CHR: %dx8KB (ROM)",
header.chr_rom_size);
log_debug("Mapper number: %d",
header.mapper_number);
log_debug("Nametable mirrored: %d", header.flags.nametable_mirrored);
log_debug("Ignore mirroring: %d", header.flags.ignore_mirroring);
log_debug("Has PRG RAM: %d", header.flags.has_prg_ram);
log_debug("Has bus conflict: %d", header.flags.has_bus_conflict);
log_debug("Has battery: %d", header.flags.has_battery);
log_debug("Has trainer: %d", header.flags.has_trainer);
log_debug("VS Unisystem: %d", header.flags.vs_unisystem);
log_debug("Playchoice 10: %d", header.flags.playchoice_10);
log_debug("TV System: %d", header.flags.tv_system);
return header;
}
bool rom_ines_read_trainer(FILE *file, INesHeader *header) {
if (!header->flags.has_trainer) {
log_debug("ROM does not contains trainer");
return true;
}
// We don't support the trainer, so we skip ahead instead.
if (fseek(file, ROM_TRAINER_SIZE, SEEK_CUR)) {
log_debug("ROM has trainer, skipping %d bytes", ROM_TRAINER_SIZE);
return true;
}
log_error("Failed to skip trainer");
return false;
}
bool rom_ines_read_prg_rom(FILE *file, INesHeader *header, Rom *rom) {
unsigned int prg_rom_size = header->prg_rom_size * 16384;
log_debug("Reading %d bytes PRG ROM", prg_rom_size);
rom->prg_rom_size = prg_rom_size;
rom->prg_rom = malloc(prg_rom_size);
if (fread(rom->prg_rom, sizeof(byte), prg_rom_size, file) < prg_rom_size) {
log_error("Failed to read PRG ROM");
return false;
}
return true;
}
bool rom_ines_read_chr_rom(FILE *file, INesHeader *header, Rom *rom) {
if (header->chr_rom_size <= 0) {
log_debug("No CHR ROM to read");
return true;
}
unsigned int chr_rom_size = header->chr_rom_size * 8192;
log_debug("Reading %d bytes CHR ROM", chr_rom_size);
rom->chr_rom = malloc(sizeof(byte) * chr_rom_size);
if (fread(rom->chr_rom, sizeof(byte), chr_rom_size, file) < chr_rom_size) {
log_error("Failed to read CHR ROM");
return false;
}
return true;
}
bool rom_ines_read(Rom *rom, FILE *file) {
INesHeader header = read_header(rom->header);
rom->nametable_mirrored = header.flags.nametable_mirrored;
return rom_ines_read_trainer(file, &header) &&
rom_ines_read_prg_rom(file, &header, rom) &&
rom_ines_read_chr_rom(file, &header, rom);
}