From fc41466fe825eae4e5c2e2f4764482c53c687679 Mon Sep 17 00:00:00 2001 From: Peter Fors Date: Sun, 2 Nov 2025 10:15:54 +0100 Subject: add new mapper, 004_0 MMC3 --- mappers/mapper_004_0.c | 207 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 mappers/mapper_004_0.c (limited to 'mappers/mapper_004_0.c') diff --git a/mappers/mapper_004_0.c b/mappers/mapper_004_0.c new file mode 100644 index 0000000..34970d6 --- /dev/null +++ b/mappers/mapper_004_0.c @@ -0,0 +1,207 @@ + + +__attribute__((always_inline)) +static inline void mapper_004_0_update_prg_banks(struct nes_state *state) { + struct mapper_004_0 *mapper = &state->mapper_data.m004_0; + + uint32_t prg_bank_count = state->ines.prg_size / 0x2000; + uint8_t r6 = mapper->bank_registers[6] & (prg_bank_count - 1); + uint8_t r7 = mapper->bank_registers[7] & (prg_bank_count - 1); + uint8_t last_bank = (prg_bank_count - 1); + uint8_t second_last = (prg_bank_count - 2); + + if(mapper->bank_select & 0x40) { + // PRG mode 1: $c000 swappable, $8000 fixed to second-last + mapper->prg_banks[0] = state->prg_rom + (second_last * 0x2000); + mapper->prg_banks[1] = state->prg_rom + (r7 * 0x2000); + mapper->prg_banks[2] = state->prg_rom + (r6 * 0x2000); + mapper->prg_banks[3] = state->prg_rom + (last_bank * 0x2000); + } else { + // PRG mode 0: $8000 swappable, $c000 fixed to second-last + mapper->prg_banks[0] = state->prg_rom + (r6 * 0x2000); + mapper->prg_banks[1] = state->prg_rom + (r7 * 0x2000); + mapper->prg_banks[2] = state->prg_rom + (second_last * 0x2000); + mapper->prg_banks[3] = state->prg_rom + (last_bank * 0x2000); + } +} + +__attribute__((always_inline)) +static inline void mapper_004_0_update_chr_banks(struct nes_state *state) { + struct mapper_004_0 *mapper = &state->mapper_data.m004_0; + + if(!state->ines.chr_size) { + return; + } + + uint32_t chr_bank_count = state->ines.chr_size / 0x400; + + if(mapper->bank_select & 0x80) { + // CHR mode 1: four 1kb banks at $0000, two 2kb banks at $1000 + mapper->chr_banks[0] = state->chr_rom + ((mapper->bank_registers[2] & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[1] = state->chr_rom + ((mapper->bank_registers[3] & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[2] = state->chr_rom + ((mapper->bank_registers[4] & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[3] = state->chr_rom + ((mapper->bank_registers[5] & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[4] = state->chr_rom + (((mapper->bank_registers[0] & 0xfe) & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[5] = state->chr_rom + (((mapper->bank_registers[0] | 0x01) & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[6] = state->chr_rom + (((mapper->bank_registers[1] & 0xfe) & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[7] = state->chr_rom + (((mapper->bank_registers[1] | 0x01) & (chr_bank_count - 1)) * 0x400); + } else { + // CHR mode 0: two 2kb banks at $0000, four 1kb banks at $1000 + mapper->chr_banks[0] = state->chr_rom + (((mapper->bank_registers[0] & 0xfe) & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[1] = state->chr_rom + (((mapper->bank_registers[0] | 0x01) & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[2] = state->chr_rom + (((mapper->bank_registers[1] & 0xfe) & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[3] = state->chr_rom + (((mapper->bank_registers[1] | 0x01) & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[4] = state->chr_rom + ((mapper->bank_registers[2] & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[5] = state->chr_rom + ((mapper->bank_registers[3] & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[6] = state->chr_rom + ((mapper->bank_registers[4] & (chr_bank_count - 1)) * 0x400); + mapper->chr_banks[7] = state->chr_rom + ((mapper->bank_registers[5] & (chr_bank_count - 1)) * 0x400); + } +} + +static uint8_t mapper_004_0_prg_rom_read(struct nes_state *state, uint32_t addr) { + struct mapper_004_0 *mapper = &state->mapper_data.m004_0; + uint32_t bank = (addr >> 13) & 3; + return mapper->prg_banks[bank][addr & 0x1fff]; +} + +static void mapper_004_0_prg_rom_write(struct nes_state *state, uint32_t addr, uint8_t value) { + struct mapper_004_0 *mapper = &state->mapper_data.m004_0; + + if(addr >= 0x8000 && addr <= 0x9fff) { + if(addr & 1) { + // $8001/$9fff (odd): bank data + uint8_t reg = mapper->bank_select & 7; + mapper->bank_registers[reg] = value; + + if(reg <= 5) { + mapper_004_0_update_chr_banks(state); + } else { + mapper_004_0_update_prg_banks(state); + } + } else { + // $8000/$9ffe (even): bank select + mapper->bank_select = value; + mapper_004_0_update_prg_banks(state); + mapper_004_0_update_chr_banks(state); + } + } else if(addr >= 0xa000 && addr <= 0xbfff) { + if(addr & 1) { + // $a001/$bfff (odd): prg ram protect (ignored for now) + } else { + // $a000/$bffe (even): mirroring + mapper->mirroring = value & 1; + } + } else if(addr >= 0xc000 && addr <= 0xdfff) { + if(addr & 1) { + // $c001/$dfff (odd): irq reload + mapper->irq_counter = 0; + mapper->irq_reload = 1; + } else { + // $c000/$dffe (even): irq latch + mapper->irq_latch = value; + } + } else if(addr >= 0xe000) { + if(addr & 1) { + // $e001/$ffff (odd): irq enable + mapper->irq_enabled = 1; + } else { + // $e000/$fffe (even): irq disable + mapper->irq_enabled = 0; + state->cpu.irq_pending = 0; + } + } +} + +static uint8_t mapper_004_0_chr_read(struct nes_state *state, uint32_t addr) { + struct mapper_004_0 *mapper = &state->mapper_data.m004_0; + + if(state->ines.chr_size == 0) { + return state->chr_ram[addr]; + } + + uint32_t bank = (addr >> 10) & 7; + return mapper->chr_banks[bank][addr & 0x3ff]; +} + +static void mapper_004_0_chr_write(struct nes_state *state, uint32_t addr, uint8_t value) { + state->chr_ram[addr] = value; +} + +static uint8_t mapper_004_0_prg_ram_read(struct nes_state *state, uint32_t addr) { + return state->sram[addr & 0x1fff]; +} + +static void mapper_004_0_prg_ram_write(struct nes_state *state, uint32_t addr, uint8_t value) { + state->sram[addr & 0x1fff] = value; +} + +static uint8_t mapper_004_0_ciram_read(struct nes_state *state, uint32_t addr) { + struct mapper_004_0 *mapper = &state->mapper_data.m004_0; + + if(mapper->mirroring == 0) { + // vertical + return state->ciram[addr & 0x7ff]; + } else { + // horizontal + return state->ciram[((addr >> 1) & 0x400) | (addr & 0x3ff)]; + } +} + +static void mapper_004_0_ciram_write(struct nes_state *state, uint32_t addr, uint8_t value) { + struct mapper_004_0 *mapper = &state->mapper_data.m004_0; + + if(mapper->mirroring == 0) { + // vertical + state->ciram[addr & 0x7ff] = value; + } else { + // horizontal + state->ciram[((addr >> 1) & 0x400) | (addr & 0x3ff)] = value; + } +} + +static void mapper_004_0_tick(struct nes_state *state) { + 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(mapper->irq_counter == 0 || mapper->irq_reload) { + mapper->irq_counter = mapper->irq_latch; + if(mapper->irq_reload) { + mapper->irq_reload = 0; + } + } else { + mapper->irq_counter--; + if(mapper->irq_counter == 0 && mapper->irq_enabled) { + state->cpu.irq_pending = 1; + } + } + } +} + +static void mapper_004_0_init(struct nes_state *state) { + struct mapper_004_0 *mapper = &state->mapper_data.m004_0; + + memset(mapper, 0, sizeof(struct mapper_004_0)); + + mapper_004_0_update_prg_banks(state); + + if(state->ines.chr_size) { + mapper_004_0_update_chr_banks(state); + } else { + // chr-ram: point all banks to chr_ram + for(int i = 0; i < 8; i++) { + mapper->chr_banks[i] = state->chr_ram + (i * 0x400); + } + } + + state->mapper_function.prg_rom_read = mapper_004_0_prg_rom_read; + state->mapper_function.prg_rom_write = mapper_004_0_prg_rom_write; + state->mapper_function.prg_ram_read = mapper_004_0_prg_ram_read; + state->mapper_function.prg_ram_write = mapper_004_0_prg_ram_write; + state->mapper_function.chr_read = mapper_004_0_chr_read; + state->mapper_function.chr_write = mapper_004_0_chr_write; + state->mapper_function.ciram_read = mapper_004_0_ciram_read; + state->mapper_function.ciram_write = mapper_004_0_ciram_write; + state->mapper_function.tick = mapper_004_0_tick; +} -- cgit v1.2.3