2023-12-03 00:27:07 -05:00
|
|
|
//
|
|
|
|
// Created by william on 12/2/23.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdio.h>
|
2024-04-30 12:28:43 -04:00
|
|
|
#include "log.h"
|
2023-12-03 00:27:07 -05:00
|
|
|
#include "../include/rom.h"
|
2024-01-06 14:27:09 -05:00
|
|
|
#include "../include/system.h"
|
2023-12-03 00:27:07 -05:00
|
|
|
|
|
|
|
// 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 char header[16]) {
|
|
|
|
return header[0] == 'N' && header[1] == 'E' && header[2] == 'S';
|
|
|
|
}
|
|
|
|
|
|
|
|
INesHeader read_header(const char header_buf[16]) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-12-23 16:35:23 -05:00
|
|
|
bool rom_ines_read_trainer(FILE *file, INesHeader *header) {
|
|
|
|
if (!header->flags.has_trainer) {
|
|
|
|
log_debug("ROM does not contains trainer");
|
|
|
|
return true;
|
|
|
|
}
|
2023-12-03 00:27:07 -05:00
|
|
|
|
2023-12-23 16:35:23 -05:00
|
|
|
// 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;
|
2023-12-03 00:27:07 -05:00
|
|
|
}
|
|
|
|
|
2023-12-23 16:35:23 -05:00
|
|
|
log_error("Failed to skip trainer");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-01-06 14:27:09 -05:00
|
|
|
bool rom_ines_read_prg_rom(FILE *file, INesHeader *header, System *system) {
|
2023-12-23 16:35:23 -05:00
|
|
|
unsigned int prg_rom_size = header->prg_rom_size * 16384;
|
|
|
|
log_debug("Reading %d bytes PRG ROM", prg_rom_size);
|
|
|
|
|
2024-01-06 14:27:09 -05:00
|
|
|
if (fread(&system->ram[system->mapper.prg_rom_start_addr], sizeof(byte), prg_rom_size, file) < prg_rom_size) {
|
2023-12-23 16:35:23 -05:00
|
|
|
log_error("Failed to read PRG ROM");
|
2023-12-03 00:27:07 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-01-06 14:27:09 -05:00
|
|
|
system->mapper.post_prg_load(&system->ram[0], header->prg_rom_size);
|
|
|
|
|
2023-12-23 16:35:23 -05:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-01-06 14:27:09 -05:00
|
|
|
bool rom_ines_read_chr_rom(FILE *file, INesHeader *header, System *system) {
|
2023-12-23 16:35:23 -05:00
|
|
|
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);
|
|
|
|
|
2024-01-06 14:27:09 -05:00
|
|
|
if (fread(system->ppu.vram, sizeof(byte), chr_rom_size, file) < chr_rom_size) {
|
2023-12-23 16:35:23 -05:00
|
|
|
log_error("Failed to read CHR ROM");
|
|
|
|
return false;
|
2023-12-03 00:27:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2023-12-23 16:35:23 -05:00
|
|
|
}
|
|
|
|
|
2024-01-06 14:27:09 -05:00
|
|
|
bool rom_ines_read(const char header_buf[ROM_HEADER_SIZE], FILE *file, System *system) {
|
2023-12-23 16:35:23 -05:00
|
|
|
INesHeader header = read_header(header_buf);
|
2024-01-06 14:27:09 -05:00
|
|
|
system->rom_header = &header;
|
2023-12-23 16:35:23 -05:00
|
|
|
|
|
|
|
return rom_ines_read_trainer(file, &header) &&
|
2024-01-06 14:27:09 -05:00
|
|
|
rom_ines_read_prg_rom(file, &header, system) &&
|
|
|
|
rom_ines_read_chr_rom(file, &header, system);
|
2023-12-03 00:27:07 -05:00
|
|
|
}
|