__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; }