diff options
| author | Peter Fors <peter.fors@mindkiller.com> | 2025-11-02 13:54:50 +0100 |
|---|---|---|
| committer | Peter Fors <peter.fors@mindkiller.com> | 2025-11-02 13:54:50 +0100 |
| commit | 46d0f6aeb1588b85852487e581a8b4c9c2401646 (patch) | |
| tree | ca93ed439b297fe8f59841b6885ba65ded81f9a3 | |
| parent | fc41466fe825eae4e5c2e2f4764482c53c687679 (diff) | |
Add MMC5, not in a working state, but can start castlevania iii, this is a horrible mapper to implement.
| -rw-r--r-- | mappers/mapper_004_0.c | 4 | ||||
| -rw-r--r-- | mappers/mapper_005_0.c | 313 | ||||
| -rw-r--r-- | mappers/mapper_005_0.h | 27 | ||||
| -rw-r--r-- | mknes_mapper.c | 2 | ||||
| -rw-r--r-- | mknes_mapper.h | 4 | ||||
| -rw-r--r-- | mknes_memory.c | 4 | ||||
| -rw-r--r-- | mknes_ppu.c | 2 | ||||
| -rw-r--r-- | mknes_sdl.c | 58 |
8 files changed, 379 insertions, 35 deletions
diff --git a/mappers/mapper_004_0.c b/mappers/mapper_004_0.c index 34970d6..2104c3d 100644 --- a/mappers/mapper_004_0.c +++ b/mappers/mapper_004_0.c @@ -159,12 +159,12 @@ static void mapper_004_0_ciram_write(struct nes_state *state, uint32_t addr, uin } } -static void mapper_004_0_tick(struct nes_state *state) { +static void mapper_004_0_tick(struct nes_state *state, uint16_t scanline, uint16_t dot) { struct mapper_004_0 *mapper = &state->mapper_data.m004_0; // clock IRQ counter once per scanline at dot 260 // this approximates the filtered A12 behavior - if(state->ppu.dot == 260 && state->ppu.scanline < 240) { + if(dot == 260 && scanline < 240) { if(mapper->irq_counter == 0 || mapper->irq_reload) { mapper->irq_counter = mapper->irq_latch; if(mapper->irq_reload) { diff --git a/mappers/mapper_005_0.c b/mappers/mapper_005_0.c new file mode 100644 index 0000000..b76311c --- /dev/null +++ b/mappers/mapper_005_0.c @@ -0,0 +1,313 @@ + + +__attribute__((always_inline)) +static inline void mapper_005_0_update_prg_banks(struct nes_state *state) { + struct mapper_005_0 *mapper = &state->mapper_data.m005_0; + + uint32_t prg_size = state->ines.prg_size; + + // $6000-$7fff: 8kb ram bank from $5113 + mapper->prg_banks[0] = state->sram + ((mapper->prg_bank_regs[0] & 0x07) * 0x2000); + + switch(mapper->prg_mode) { + case 0: { + // 32kb mode: $5117 controls all of $8000-$ffff + uint32_t bank = (mapper->prg_bank_regs[4] & 0x7c) >> 2; + uint32_t offset = (bank * 0x8000) % prg_size; + mapper->prg_banks[1] = state->prg_rom + offset; + mapper->prg_banks[2] = state->prg_rom + offset + 0x2000; + mapper->prg_banks[3] = state->prg_rom + offset + 0x4000; + mapper->prg_banks[4] = state->prg_rom + offset + 0x6000; + } break; + + case 1: { + // 16kb mode: $5115 controls $8000-$bfff, $5117 controls $c000-$ffff + uint32_t bank_low = (mapper->prg_bank_regs[2] & 0x7e) >> 1; + uint32_t bank_high = (mapper->prg_bank_regs[4] & 0x7e) >> 1; + uint32_t offset_low = (bank_low * 0x4000) % prg_size; + uint32_t offset_high = (bank_high * 0x4000) % prg_size; + mapper->prg_banks[1] = state->prg_rom + offset_low; + mapper->prg_banks[2] = state->prg_rom + offset_low + 0x2000; + mapper->prg_banks[3] = state->prg_rom + offset_high; + mapper->prg_banks[4] = state->prg_rom + offset_high + 0x2000; + } break; + + case 2: { + // 16kb+8kb+8kb: $5115 controls $8000-$bfff, $5116 controls $c000-$dfff, $5117 controls $e000-$ffff + uint32_t bank_0 = (mapper->prg_bank_regs[2] & 0x7e) >> 1; + uint32_t bank_1 = mapper->prg_bank_regs[3] & 0x7f; + uint32_t bank_2 = mapper->prg_bank_regs[4] & 0x7f; + mapper->prg_banks[1] = state->prg_rom + ((bank_0 * 0x4000) % prg_size); + mapper->prg_banks[2] = state->prg_rom + ((bank_0 * 0x4000 + 0x2000) % prg_size); + mapper->prg_banks[3] = state->prg_rom + ((bank_1 * 0x2000) % prg_size); + mapper->prg_banks[4] = state->prg_rom + ((bank_2 * 0x2000) % prg_size); + } break; + + case 3: { + // 8kb mode: each register controls its own 8kb bank + for(int i = 0; i < 4; i++) { + uint32_t bank = mapper->prg_bank_regs[i + 1] & 0x7f; + mapper->prg_banks[i + 1] = state->prg_rom + ((bank * 0x2000) % prg_size); + } + } break; + } +} + +__attribute__((always_inline)) +static inline void mapper_005_0_update_chr_banks(struct nes_state *state) { + struct mapper_005_0 *mapper = &state->mapper_data.m005_0; + + if(!state->ines.chr_size) { + return; + } + + uint32_t chr_size = state->ines.chr_size; + + // background CHR banks ($5120-$5127) + switch(mapper->chr_mode) { + case 0: { + // 8kb mode: use last register + uint32_t bank = mapper->chr_bank_regs[7] | (mapper->chr_upper << 8); + uint32_t offset = (bank * 0x2000) % chr_size; + for(int i = 0; i < 8; i++) { + mapper->chr_banks_bg[i] = state->chr_rom + offset + (i * 0x400); + } + } break; + + case 1: { + // 4kb mode: registers $5123 and $5127 + uint32_t bank_low = mapper->chr_bank_regs[3] | (mapper->chr_upper << 8); + uint32_t bank_high = mapper->chr_bank_regs[7] | (mapper->chr_upper << 8); + for(int i = 0; i < 4; i++) { + mapper->chr_banks_bg[i] = state->chr_rom + ((bank_low * 0x1000) % chr_size) + (i * 0x400); + mapper->chr_banks_bg[i + 4] = state->chr_rom + ((bank_high * 0x1000) % chr_size) + (i * 0x400); + } + } break; + + case 2: { + // 2kb mode: registers $5121, $5123, $5125, $5127 + for(int i = 0; i < 4; i++) { + uint32_t bank = mapper->chr_bank_regs[i * 2 + 1] | (mapper->chr_upper << 8); + uint32_t offset = (bank * 0x800) % chr_size; + mapper->chr_banks_bg[i * 2] = state->chr_rom + offset; + mapper->chr_banks_bg[i * 2 + 1] = state->chr_rom + offset + 0x400; + } + } break; + + case 3: { + // 1kb mode: all registers + for(int i = 0; i < 8; i++) { + uint32_t bank = mapper->chr_bank_regs[i] | (mapper->chr_upper << 8); + mapper->chr_banks_bg[i] = state->chr_rom + ((bank * 0x400) % chr_size); + } + } break; + } + + // sprite CHR banks ($5128-$512B) - 4 registers control 8 1kb banks (each is 2kb) + for(int i = 0; i < 4; i++) { + uint32_t bank = mapper->chr_bank_regs[8 + i] | (mapper->chr_upper << 8); + uint32_t offset = (bank * 0x800) % chr_size; + mapper->chr_banks_sp[i * 2] = state->chr_rom + offset; + mapper->chr_banks_sp[i * 2 + 1] = state->chr_rom + offset + 0x400; + } +} + +static uint8_t mapper_005_0_prg_rom_read(struct nes_state *state, uint32_t addr) { + struct mapper_005_0 *mapper = &state->mapper_data.m005_0; + + uint32_t bank = (addr >> 13) & 3; + return mapper->prg_banks[bank + 1][addr & 0x1fff]; +} + +static uint8_t mapper_005_0_prg_ram_read(struct nes_state *state, uint32_t addr) { + struct mapper_005_0 *mapper = &state->mapper_data.m005_0; + + // handle MMC5 registers at $5000-$5fff + if(addr >= 0x5c00 && addr <= 0x5fff) { + return mapper->exram[addr & 0x3ff]; + } else if(addr == 0x5204) { + uint8_t status = mapper->irq_status; + mapper->irq_status &= ~0x80; + state->cpu.irq_pending = 0; + return status; + } else if(addr >= 0x5000 && addr < 0x6000) { + return 0; // other MMC5 registers return open bus + } + + return mapper->prg_banks[0][addr & 0x1fff]; +} + +static void mapper_005_0_prg_ram_write(struct nes_state *state, uint32_t addr, uint8_t value) { + struct mapper_005_0 *mapper = &state->mapper_data.m005_0; + + // handle MMC5 registers at $5000-$5fff + if(addr >= 0x5c00 && addr <= 0x5fff) { + mapper->exram[addr & 0x3ff] = value; + return; + } else if(addr >= 0x5000 && addr < 0x6000) { + // MMC5 control registers + switch(addr) { + case 0x5100: + mapper->prg_mode = value & 0x03; + mapper_005_0_update_prg_banks(state); + break; + + case 0x5101: + mapper->chr_mode = value & 0x03; + mapper_005_0_update_chr_banks(state); + break; + + case 0x5104: + mapper->exram_mode = value & 0x03; + break; + + case 0x5105: + mapper->nametable_mapping = value; + break; + + case 0x5106: + mapper->fill_tile = value; + break; + + case 0x5107: + mapper->fill_attr = value & 0x03; + break; + + case 0x5113: + case 0x5114: + case 0x5115: + case 0x5116: + case 0x5117: { + uint32_t reg = addr - 0x5113; + mapper->prg_bank_regs[reg] = value; + mapper_005_0_update_prg_banks(state); + } break; + + default: + if(addr >= 0x5120 && addr <= 0x512b) { + uint32_t reg = addr - 0x5120; + mapper->chr_bank_regs[reg] = value; + mapper_005_0_update_chr_banks(state); + } else if(addr == 0x5130) { + mapper->chr_upper = value & 0x03; + mapper_005_0_update_chr_banks(state); + } else if(addr == 0x5203) { + mapper->irq_scanline = value; + } else if(addr == 0x5204) { + mapper->irq_pending = value & 0x80; + } + break; + } + return; + } + + mapper->prg_banks[0][addr & 0x1fff] = value; +} + +static uint8_t mapper_005_0_chr_read(struct nes_state *state, uint32_t addr) { + struct mapper_005_0 *mapper = &state->mapper_data.m005_0; + + if(state->ines.chr_size == 0) { + return state->chr_ram[addr]; + } + + uint32_t bank = (addr >> 10) & 7; + return mapper->chr_banks_bg[bank][addr & 0x3ff]; +} + +static void mapper_005_0_chr_write(struct nes_state *state, uint32_t addr, uint8_t value) { + state->chr_ram[addr] = value; +} + +static uint8_t mapper_005_0_ciram_read(struct nes_state *state, uint32_t addr) { + struct mapper_005_0 *mapper = &state->mapper_data.m005_0; + + // determine which nametable (0-3) based on address + uint32_t nt = (addr >> 10) & 0x03; + uint32_t mapping = (mapper->nametable_mapping >> (nt * 2)) & 0x03; + + switch(mapping) { + case 0: return state->ciram[addr & 0x3ff]; + case 1: return state->ciram[0x400 | (addr & 0x3ff)]; + case 2: return mapper->exram[addr & 0x3ff]; + case 3: return mapper->fill_tile; + } + return 0; +} + +static void mapper_005_0_ciram_write(struct nes_state *state, uint32_t addr, uint8_t value) { + struct mapper_005_0 *mapper = &state->mapper_data.m005_0; + + uint32_t nt = (addr >> 10) & 0x03; + uint32_t mapping = (mapper->nametable_mapping >> (nt * 2)) & 0x03; + + switch(mapping) { + case 0: state->ciram[addr & 0x3ff] = value; break; + case 1: state->ciram[0x400 | (addr & 0x3ff)] = value; break; + case 2: + if(mapper->exram_mode < 2) { + mapper->exram[addr & 0x3ff] = value; + } + break; + } +} + +static void mapper_005_0_tick(struct nes_state *state, uint16_t scanline, uint16_t dot) { + struct mapper_005_0 *mapper = &state->mapper_data.m005_0; + + // simplified scanline IRQ: trigger at start of target scanline + if(dot == 0 && scanline < 240) { + mapper->in_frame = 1; + + if(scanline == mapper->irq_scanline) { + if(mapper->irq_pending) { + state->cpu.irq_pending = 1; + mapper->irq_status |= 0x80; + } + } + } + + if(scanline == 240 && dot == 0) { + mapper->in_frame = 0; + } +} + +static void mapper_005_0_init(struct nes_state *state) { + struct mapper_005_0 *mapper = &state->mapper_data.m005_0; + + memset(mapper, 0, sizeof(struct mapper_005_0)); + // default to mode 3 (8kb prg banks) and mode 3 (1kb chr banks) + mapper->prg_mode = 3; + mapper->chr_mode = 3; + + // default nametable mapping: vertical mirroring + mapper->nametable_mapping = 0x44; + + // initialize prg banks to last 4 banks (mode 3 default) + uint32_t prg_bank_count = state->ines.prg_size / 0x2000; + mapper->prg_bank_regs[1] = (prg_bank_count - 4) & 0x7f; + mapper->prg_bank_regs[2] = (prg_bank_count - 3) & 0x7f; + mapper->prg_bank_regs[3] = (prg_bank_count - 2) & 0x7f; + mapper->prg_bank_regs[4] = (prg_bank_count - 1) & 0x7f; + + mapper_005_0_update_prg_banks(state); + + if(state->ines.chr_size) { + mapper_005_0_update_chr_banks(state); + } else { + for(int i = 0; i < 8; i++) { + mapper->chr_banks_bg[i] = state->chr_ram + (i * 0x400); + mapper->chr_banks_sp[i] = state->chr_ram + (i * 0x400); + } + } + + state->mapper_function.prg_rom_read = mapper_005_0_prg_rom_read; + state->mapper_function.prg_rom_write = mapper_default_prg_rom_write; + state->mapper_function.prg_ram_read = mapper_005_0_prg_ram_read; + state->mapper_function.prg_ram_write = mapper_005_0_prg_ram_write; + state->mapper_function.chr_read = mapper_005_0_chr_read; + state->mapper_function.chr_write = mapper_005_0_chr_write; + state->mapper_function.ciram_read = mapper_005_0_ciram_read; + state->mapper_function.ciram_write = mapper_005_0_ciram_write; + state->mapper_function.tick = mapper_005_0_tick; +} diff --git a/mappers/mapper_005_0.h b/mappers/mapper_005_0.h new file mode 100644 index 0000000..f9b9851 --- /dev/null +++ b/mappers/mapper_005_0.h @@ -0,0 +1,27 @@ + + +struct mapper_005_0 { + uint8_t *prg_banks[5]; // banks at $6000, $8000, $a000, $c000, $e000 + uint8_t *chr_banks_bg[8]; // 1kb banks at $0000-$1fff for background + uint8_t *chr_banks_sp[8]; // 1kb banks at $0000-$1fff for sprites + + uint8_t prg_mode; + uint8_t chr_mode; + uint8_t prg_bank_regs[5]; // $5113-$5117 + uint8_t chr_bank_regs[12]; // $5120-$512b (0-7=bg, 8-11=sprites) + uint8_t chr_upper; // $5130 + + uint8_t nametable_mapping; // $5105 + uint8_t fill_tile; // $5106 + uint8_t fill_attr; // $5107 + + uint8_t exram_mode; + uint8_t exram[1024]; + + uint8_t irq_scanline; // $5203 + uint8_t irq_status; // $5204 + uint8_t irq_pending; + + uint8_t in_frame; + uint16_t scanline_counter; +} __attribute__((packed, aligned(64))); diff --git a/mknes_mapper.c b/mknes_mapper.c index 5ae55fe..7dfb32b 100644 --- a/mknes_mapper.c +++ b/mknes_mapper.c @@ -50,6 +50,7 @@ __attribute__((naked, no_instrument_function, no_profile_instrument_function)) s #include "mappers/mapper_003_1.c" #include "mappers/mapper_003_2.c" #include "mappers/mapper_004_0.c" +#include "mappers/mapper_005_0.c" #include "mappers/mapper_007_2.c" #include "mappers/mapper_011_0.c" #include "mappers/mapper_066_0.c" @@ -69,6 +70,7 @@ static struct supported_mapper supported_mappers[] = { { MAPPER_ID( 3, 1), mapper_003_1_init }, { MAPPER_ID( 3, 2), mapper_003_2_init }, { MAPPER_ID( 4, 0), mapper_004_0_init }, + { MAPPER_ID( 5, 0), mapper_005_0_init }, { MAPPER_ID( 7, 2), mapper_007_2_init }, { MAPPER_ID(11, 0), mapper_011_0_init }, { MAPPER_ID(66, 0), mapper_066_0_init }, diff --git a/mknes_mapper.h b/mknes_mapper.h index 36d711a..923d7b8 100644 --- a/mknes_mapper.h +++ b/mknes_mapper.h @@ -6,6 +6,7 @@ #include "mappers/mapper_003_1.h" #include "mappers/mapper_003_2.h" #include "mappers/mapper_004_0.h" +#include "mappers/mapper_005_0.h" #include "mappers/mapper_007_2.h" #include "mappers/mapper_011_0.h" #include "mappers/mapper_066_0.h" @@ -21,7 +22,7 @@ struct mapper_functions { void (*chr_write)(struct nes_state *state, uint32_t addr, uint8_t value); uint8_t (*ciram_read)(struct nes_state *state, uint32_t addr); void (*ciram_write)(struct nes_state *state, uint32_t addr, uint8_t value); - void (*tick)(struct nes_state *state); + void (*tick)(struct nes_state *state, uint16_t scanline, uint16_t dot); } __attribute__((aligned(64))); union mapper_data { @@ -32,6 +33,7 @@ union mapper_data { struct mapper_003_1 m003_1; struct mapper_003_2 m003_2; struct mapper_004_0 m004_0; + struct mapper_005_0 m005_0; struct mapper_007_2 m007_2; struct mapper_011_0 m011_0; struct mapper_066_0 m066_0; diff --git a/mknes_memory.c b/mknes_memory.c index 20d77e8..cdeed3f 100644 --- a/mknes_memory.c +++ b/mknes_memory.c @@ -27,7 +27,7 @@ static inline uint8_t memory_read(struct nes_state *state, uint32_t offset) { state->ppu.input_bit[index]++; result = value | 0x40; // Bit 6 open bus high, bit 7 low - } else if(offset >= 0x6000 && offset <= 0x7fff) { + } else if(offset >= 0x5000 && offset <= 0x7fff) { result = state->mapper_function.prg_ram_read(state, offset); } @@ -88,7 +88,7 @@ static inline void memory_write(struct nes_state *state, uint32_t offset, uint8_ } else if(offset >= 0x4000 && offset <= 0x4017) { apu_write(state, offset, value); - } else if(offset >= 0x6000 && offset <= 0x7fff) { + } else if(offset >= 0x5000 && offset <= 0x7fff) { state->mapper_function.prg_ram_write(state, offset, value); } diff --git a/mknes_ppu.c b/mknes_ppu.c index 8006ff6..b0f6479 100644 --- a/mknes_ppu.c +++ b/mknes_ppu.c @@ -367,7 +367,7 @@ rendering_done: } if(state->mapper_function.tick) { - state->mapper_function.tick(state); // TODO(peter): This signature has to be changed to supply dot and scanline! + state->mapper_function.tick(state, scanline, dot); } } ppu->dot = dot; diff --git a/mknes_sdl.c b/mknes_sdl.c index bb0b5ad..98776b5 100644 --- a/mknes_sdl.c +++ b/mknes_sdl.c @@ -30,10 +30,10 @@ static FILE *state_dump_file; #define WINDOW_WIDTH (256 * 3 + 256 * 2) #define WINDOW_HEIGHT (240 * 3) -// #define PRG_ROM_SIZE (2 * 1024 * 1024) -// #define CHR_ROM_SIZE (1 * 1024 * 1024) -#define PRG_ROM_SIZE (512 * 1024) -#define CHR_ROM_SIZE (256 * 1024) +#define PRG_ROM_SIZE (2 * 1024 * 1024) +#define CHR_ROM_SIZE (1 * 1024 * 1024) +// #define PRG_ROM_SIZE (512 * 1024) +// #define CHR_ROM_SIZE (256 * 1024) #define PIXELS_SIZE (256 * 240) #define RAM_SIZE 0x1000 // 0x800 in reality, but for aligned alloc it must be the size of the alignment (4096) @@ -62,8 +62,8 @@ static void audio_callback(int16_t *data, size_t frames) { } // Embed the ROM for benchmarking to eliminate file I/O overhead // Uncomment the ROM you want to benchmark: // INCBIN_BYTES(benchmark_rom, "data/Life Force (USA).nes"); -INCBIN_BYTES(benchmark_rom, "data/0001/Metroid (U) [!].nes"); -// INCBIN_BYTES(benchmark_rom, "data/0000/Super Mario Bros. (World) (HVC-SM).nes"); +// INCBIN_BYTES(benchmark_rom, "data/0001/Metroid (U) [!].nes"); +INCBIN_BYTES(benchmark_rom, "data/0000/Super Mario Bros. (World) (HVC-SM).nes"); // INCBIN_BYTES(benchmark_rom, "data/0003/Gradius (USA).nes"); #endif @@ -88,20 +88,20 @@ static int32_t frames; // debug information #include "mknes_bench.c" #endif -static void dump_state(struct nes_state *state) { - size_t state_size = offsetof(struct nes_state, ram); +// static void dump_state(struct nes_state *state) { +// size_t state_size = offsetof(struct nes_state, ram); - if(!state_dump_file) { - state_dump_file = fopen("state_dump.bin", "wb"); - if(!state_dump_file) { - fprintf(stderr, "Failed to open state_dump.bin for writing\n"); - return; - } - } +// if(!state_dump_file) { +// state_dump_file = fopen("state_dump.bin", "wb"); +// if(!state_dump_file) { +// fprintf(stderr, "Failed to open state_dump.bin for writing\n"); +// return; +// } +// } - fwrite(state, 1, state_size, state_dump_file); - state_dump_count++; -} +// fwrite(state, 1, state_size, state_dump_file); +// state_dump_count++; +// } int main(int argc, char **argv) { setbuf(stdout, 0); @@ -166,13 +166,13 @@ int main(int argc, char **argv) { // ines2_load(nstate, "data/0003/Gradius (USA).zip"); // ines2_load(nstate, "data/0004/Mega Man 3 (USA).zip"); - ines2_load(nstate, "data/0004/Mega Man 4 (USA).zip"); + // ines2_load(nstate, "data/0004/Mega Man 4 (USA).zip"); // ines2_load(nstate, "data/0004/Mega Man 5 (USA).zip"); // ines2_load(nstate, "data/0004/Mega Man 6 (USA).zip"); // ines2_load(nstate, "data/0004/Super Mario Bros. 2 (USA).zip"); - // ines2_load(nstate, "data/0004/Castlevania III - Dracula's Curse (USA).zip"); // ACTUALLY mapper 5 - + ines2_load(nstate, "data/0005/Castlevania III - Dracula's Curse (USA).zip"); + // ines2_load(nstate, "data/0005/Metal Slader Glory (Japan).zip"); // ines2_load(nstate, "data/0007/Battletoads (USA).zip"); // ines2_load(nstate, "data/0007/Beetlejuice (USA).zip"); // ines2_load(nstate, "data/0007/Cabal (USA).zip"); @@ -307,10 +307,10 @@ int main(int argc, char **argv) { frames++; - // Dump state every frame starting from 2400 - if(frames >= 2400 && frames <= 3100) { - dump_state(nstate); - } + // // Dump state every frame starting from 2400 + // if(frames >= 2400 && frames <= 3100) { + // dump_state(nstate); + // } // Convert NES pixels to display buffer uint32_t * restrict dst = display_buffer; @@ -344,11 +344,11 @@ int main(int argc, char **argv) { } printf("total frames: %6d total cycles: %12llu\n", frames, (unsigned long long)nstate->cpu.cycles); - printf("state dumps created: %zu\n", state_dump_count); + // printf("state dumps created: %zu\n", state_dump_count); - if(state_dump_file) { - fclose(state_dump_file); - } + // if(state_dump_file) { + // fclose(state_dump_file); + // } timer_destroy(timer); for(int i = 0; i < 4; i++) { |
