// // Created by william on 12/2/23. // #include #include #include #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); }