summaryrefslogtreecommitdiff
path: root/mappers
diff options
context:
space:
mode:
authorPeter Fors <peter.fors@mindkiller.com>2025-11-02 10:15:54 +0100
committerPeter Fors <peter.fors@mindkiller.com>2025-11-02 10:15:54 +0100
commitfc41466fe825eae4e5c2e2f4764482c53c687679 (patch)
tree82f238d9e977c589184864ce043b3af355bbff32 /mappers
parenta4bc9dcb3fee68c45fec1feb54a9b00c885dd68e (diff)
add new mapper, 004_0 MMC3
Diffstat (limited to 'mappers')
-rw-r--r--mappers/mapper_004_0.c207
-rw-r--r--mappers/mapper_004_0.h17
2 files changed, 224 insertions, 0 deletions
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;
+}
diff --git a/mappers/mapper_004_0.h b/mappers/mapper_004_0.h
new file mode 100644
index 0000000..227d8b8
--- /dev/null
+++ b/mappers/mapper_004_0.h
@@ -0,0 +1,17 @@
+
+
+struct mapper_004_0 {
+ uint8_t *prg_banks[4]; // 8kb banks at $8000, $a000, $c000, $e000
+ uint8_t *chr_banks[8]; // 1kb banks at $0000-$1fff
+
+ uint8_t bank_select;
+ uint8_t bank_registers[8];
+ uint8_t mirroring;
+
+ uint8_t irq_latch;
+ uint8_t irq_counter;
+ uint8_t irq_reload;
+ uint8_t irq_enabled;
+
+ uint8_t last_a12;
+} __attribute__((packed, aligned(64)));