summaryrefslogtreecommitdiff
path: root/mappers/mapper_005_0.c
diff options
context:
space:
mode:
authorPeter Fors <peter.fors@mindkiller.com>2025-11-02 13:54:50 +0100
committerPeter Fors <peter.fors@mindkiller.com>2025-11-02 13:54:50 +0100
commit46d0f6aeb1588b85852487e581a8b4c9c2401646 (patch)
treeca93ed439b297fe8f59841b6885ba65ded81f9a3 /mappers/mapper_005_0.c
parentfc41466fe825eae4e5c2e2f4764482c53c687679 (diff)
Add MMC5, not in a working state, but can start castlevania iii, this is a horrible mapper to implement.
Diffstat (limited to 'mappers/mapper_005_0.c')
-rw-r--r--mappers/mapper_005_0.c313
1 files changed, 313 insertions, 0 deletions
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;
+}