summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mappers/mapper_004_0.c207
-rw-r--r--mappers/mapper_004_0.h17
-rw-r--r--mknes_mapper.c2
-rw-r--r--mknes_mapper.h2
-rw-r--r--mknes_sdl.c11
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");