From 77d37dd455f804c79969979eb39d525b3c5ddfe7 Mon Sep 17 00:00:00 2001 From: vin Date: Mon, 17 Jun 2024 14:46:00 +0530 Subject: [PATCH] implement basic iNES and Mapper 0 ROM loading It seems like the test ROM loads fine but the instructions are not, but that's exactly what the test ROM is for I suppose. --- cpu.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++------------- rom.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ rom.h | 17 ++++++++++++++++ 3 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 rom.c create mode 100644 rom.h diff --git a/cpu.c b/cpu.c index 61b835c..968fcd8 100644 --- a/cpu.c +++ b/cpu.c @@ -1,8 +1,11 @@ +#include #include #include #include #include +#include "rom.h" + #define MAX(a, b) ((a > b) ? a : b) #define STATUS_UPDATE_ZERO(r) \ @@ -37,7 +40,9 @@ struct registers { struct cpu_flags status; uint16_t pc; }; -struct registers regs; +struct registers regs = {0}; + +struct Rom rom = {0}; enum addressing_mode { AM_IMM, @@ -56,9 +61,6 @@ enum addressing_mode { /* 64K address space, 16bit words */ uint8_t memory[0x16000]; -/* example program taken from https://bugzmanov.github.io/nes_ebook/chapter_3_1.html */ -uint8_t program[] = { 0xa9, 0xc0, 0xaa, 0xe8, 0x00 }; - uint32_t cycles = 0; static uint8_t @@ -66,7 +68,14 @@ peek(uint16_t addr) { MEMORY_MIRROR(addr); - return memory[addr]; + if (addr > 0x7FFF) { + if (rom.prg_rom_size == 0x4000) + return rom.prg_rom[(addr - 0x8000) % 0x4000]; + else if (rom.prg_rom_size == 0x8000) + return rom.prg_rom[addr - 0x8000]; + } else { + return memory[addr]; + } } static uint16_t @@ -738,7 +747,7 @@ interpret(void) for (;;) { opcode = peek(regs.pc++); - printf("opcode: $%02X\n", opcode); + printf("opcode: $%02X @ $%04X\n", opcode, regs.pc - 1); switch (opcode) { case 0x69: @@ -1403,7 +1412,7 @@ interpret(void) regs.status.decimal_mode, regs.status.brk, regs.status.unused, regs.status.overflow, regs.status.negative); - printf("A: $%02X, X: $%02X, Y: $%02X, SP: $%02X, PC: $%02X\n", + printf("A: $%02X, X: $%02X, Y: $%02X, SP: $%02X, PC: $%04X\n", regs.a, regs.x, regs.y, regs.sp, regs.pc); } } @@ -1416,26 +1425,51 @@ cpu_init(void) regs.pc = 0xFFFC; regs.sp = 0xFD; - memset(®s.status, 0, sizeof(regs.status)); + //memset(®s.status, 0, sizeof(regs.status)); regs.status.unused = 1; cycles += 7; } int -main(void) +main(int argc, char *argv[]) { - cpu_init(); + FILE *fp; + uint8_t *buf; + size_t buflen; - if (sizeof(program) > (0x10000 - 0x8000)) { - fprintf(stderr, "program is too big for memory\n"); + if (argc != 2) { + fprintf(stderr, "Usage: %s rom.nes\n", basename(argv[0])); return 1; } - memcpy(memory + 0x8000, program, sizeof(program)); + fp = fopen(argv[1], "r"); + fseek(fp, 0, SEEK_END); + buflen = ftell(fp); + buf = calloc(1, buflen); + fseek(fp, 0, SEEK_SET); + if (fread(buf, 1, buflen, fp) != buflen && ferror(fp)) { + fprintf(stderr, "file %s was not read properly\n", argv[1]); + clearerr(fp); + fclose(fp); + return 1; + } + fclose(fp); + + parse_rom(buf, buflen, &rom); + free(buf); + + cpu_init(); + + /* TODO: move to separate file? */ + if (rom.mapper != 0) { + fprintf(stderr, "Only iNES ROMs using Mapper 0 are supported for now.\n"); + return 1; + } + + memwrite16(0xFFFC, 0x8000); regs.pc = 0x8000; - printf("Initial State:\n"); printf("status: %i%i%i%i%i%i%i%i\n", regs.status.carry, regs.status.zero, regs.status.interrupt_disable, regs.status.decimal_mode, regs.status.brk, @@ -1448,5 +1482,7 @@ main(void) interpret(); + free_rom(&rom); + return 0; } diff --git a/rom.c b/rom.c new file mode 100644 index 0000000..d432cc2 --- /dev/null +++ b/rom.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include "rom.h" + +void +parse_rom(const uint8_t *data, size_t data_len, struct Rom *rom) +{ + size_t prg_rom_offset = 16; + size_t chr_rom_offset = 0; + + if (data_len < 16) { + fprintf(stderr, "Invalid ROM: size less than 16 bytes\n"); + exit(1); + } + + if (data[0] != 'N' || data[1] != 'E' || data[2] != 'S' || data[3] != 0x1A) { + fprintf(stderr, "Invalid ROM: not in iNES format\n"); + exit(1); + } + + /* trainer data present */ + if (data[6] & (1 << 2)) + prg_rom_offset += 512; + + rom->prg_rom_size = data[4] * 0x4000; + rom->prg_rom = calloc(rom->prg_rom_size, 1); + memcpy(rom->prg_rom, data + prg_rom_offset, rom->prg_rom_size); + + rom->chr_rom_size = data[5] * 0x2000; + if (rom->chr_rom_size > 0) { + rom->chr_rom = calloc(rom->chr_rom_size, 1); + chr_rom_offset = prg_rom_offset + rom->prg_rom_size; + memcpy(rom->chr_rom, data + chr_rom_offset, rom->chr_rom_size); + } + + if ((data[6] & (1 << 3)) != 0) + rom->mirror = M_FOUR; + else if ((data[6] & 1) == 1) + rom->mirror = M_VERTICAL; + else if ((data[6] & 1) == 0) + rom->mirror = M_HORIZONTAL; + + rom->mapper = (data[6] & (0xF << 4)) >> 4; + rom->mapper |= data[7] & (0xF << 4); +} + +void +free_rom(struct Rom *rom) +{ + free(rom->prg_rom); + if (rom->chr_rom_size > 0) + free(rom->chr_rom); +} diff --git a/rom.h b/rom.h new file mode 100644 index 0000000..a52bda1 --- /dev/null +++ b/rom.h @@ -0,0 +1,17 @@ +enum screen_mirroring { + M_HORIZONTAL, + M_VERTICAL, + M_FOUR, +}; + +struct Rom { + char *prg_rom; + char *chr_rom; + size_t prg_rom_size; + size_t chr_rom_size; + uint8_t mapper; + enum screen_mirroring mirror; +}; + +void parse_rom(const uint8_t *data, size_t data_len, struct Rom *rom); +void free_rom(struct Rom *rom);