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