From 46d0f6aeb1588b85852487e581a8b4c9c2401646 Mon Sep 17 00:00:00 2001 From: Peter Fors Date: Sun, 2 Nov 2025 13:54:50 +0100 Subject: Add MMC5, not in a working state, but can start castlevania iii, this is a horrible mapper to implement. --- mappers/mapper_004_0.c | 4 +- mappers/mapper_005_0.c | 313 +++++++++++++++++++++++++++++++++++++++++++++++++ mappers/mapper_005_0.h | 27 +++++ 3 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 mappers/mapper_005_0.c create mode 100644 mappers/mapper_005_0.h (limited to 'mappers') 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))); -- cgit v1.2.3