diff options
| author | Peter Fors <peter.fors@mindkiller.com> | 2025-11-02 10:15:54 +0100 |
|---|---|---|
| committer | Peter Fors <peter.fors@mindkiller.com> | 2025-11-02 10:15:54 +0100 |
| commit | fc41466fe825eae4e5c2e2f4764482c53c687679 (patch) | |
| tree | 82f238d9e977c589184864ce043b3af355bbff32 | |
| parent | a4bc9dcb3fee68c45fec1feb54a9b00c885dd68e (diff) | |
add new mapper, 004_0 MMC3
| -rw-r--r-- | mappers/mapper_004_0.c | 207 | ||||
| -rw-r--r-- | mappers/mapper_004_0.h | 17 | ||||
| -rw-r--r-- | mknes_mapper.c | 2 | ||||
| -rw-r--r-- | mknes_mapper.h | 2 | ||||
| -rw-r--r-- | mknes_sdl.c | 11 |
5 files changed, 238 insertions, 1 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))); diff --git a/mknes_mapper.c b/mknes_mapper.c index a05707e..5ae55fe 100644 --- a/mknes_mapper.c +++ b/mknes_mapper.c @@ -49,6 +49,7 @@ __attribute__((naked, no_instrument_function, no_profile_instrument_function)) s #include "mappers/mapper_003_0.c" #include "mappers/mapper_003_1.c" #include "mappers/mapper_003_2.c" +#include "mappers/mapper_004_0.c" #include "mappers/mapper_007_2.c" #include "mappers/mapper_011_0.c" #include "mappers/mapper_066_0.c" @@ -67,6 +68,7 @@ static struct supported_mapper supported_mappers[] = { { MAPPER_ID( 3, 0), mapper_003_0_init }, { 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( 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 88351f5..36d711a 100644 --- a/mknes_mapper.h +++ b/mknes_mapper.h @@ -5,6 +5,7 @@ #include "mappers/mapper_003_0.h" #include "mappers/mapper_003_1.h" #include "mappers/mapper_003_2.h" +#include "mappers/mapper_004_0.h" #include "mappers/mapper_007_2.h" #include "mappers/mapper_011_0.h" #include "mappers/mapper_066_0.h" @@ -30,6 +31,7 @@ union mapper_data { struct mapper_003_0 m003_0; struct mapper_003_1 m003_1; struct mapper_003_2 m003_2; + struct mapper_004_0 m004_0; struct mapper_007_2 m007_2; struct mapper_011_0 m011_0; struct mapper_066_0 m066_0; diff --git a/mknes_sdl.c b/mknes_sdl.c index de8d0d1..bb0b5ad 100644 --- a/mknes_sdl.c +++ b/mknes_sdl.c @@ -164,13 +164,22 @@ int main(int argc, char **argv) { // ines2_load(nstate, "data/0003/Friday the 13th (USA).zip"); // ines2_load(nstate, "data/0003/Ghostbusters (Japan).zip"); // 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 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/0007/Battletoads (USA).zip"); // ines2_load(nstate, "data/0007/Beetlejuice (USA).zip"); // ines2_load(nstate, "data/0007/Cabal (USA).zip"); // ines2_load(nstate, "data/000b/Baby Boomer (USA) (Unl).zip"); // ines2_load(nstate, "data/000b/Captain Comic - The Adventure (USA) (Unl).zip"); - ines2_load(nstate, "data/000b/King Neptune's Adventure (USA) (Unl).nes"); + // ines2_load(nstate, "data/000b/King Neptune's Adventure (USA) (Unl).nes"); // ines2_load(nstate, "data/2002/Attack Animal Gakuen (Japan).zip"); // ines2_load(nstate, "data/2002/Ballblazer (Japan).zip"); |
