#include "memory.h" #define NESE_DEBUG "MMC3" #include "log.h" typedef enum { mmc3_Flag_Horizontal = 0b00000001, mmc3_Flag_IRQ_Enabled = 0b00000010, mmc3_Flag_IRQ_Reload = 0b00000100, mmc3_Flag_CHR_RAM = 0b00001000, mmc3_Flag_Battery = 0b00100000, mmc3_Flag_WRAM_Protect = 0b01000000, mmc3_Flag_WRAM_Enabled = 0b10000000, } mmc3_Flag; typedef enum { mmc3_Bank_Select_Reg = 0b00000111, mmc3_Bank_Select_PRG = 0b01000000, mmc3_Bank_Select_CHR = 0b10000000, } mmc3_Bank_Select; typedef struct { uint8_t r[8]; uint8_t flags; uint8_t bank_select; uint8_t irq_count; uint8_t irq_latch; } map004_data; static inline void mmc3_update_vram(map004_data* data, nes_Memory* mem) { nes_ppu_set_mirroring( &mem->ppu, data->flags & mmc3_Flag_Horizontal ? nes_Mirror_Horizontal : nes_Mirror_Vertical ); } static inline void mmc3_map_prg(nes_Memory* mem, int bank, int page) { LOGD("PRG ROM: 8k $%04x <- bank %d", 0x8000 + (bank << 13), page); mem->rom_bank[bank] = prg_rom_page(mem, page % mem->n_rom_banks); } static inline void mmc3_update_prg(map004_data* data, nes_Memory* mem, int reg, uint8_t bank) { if (reg == 7) { mmc3_map_prg(mem, 1, bank); } else { if (!(data->bank_select & mmc3_Bank_Select_PRG)) { mmc3_map_prg(mem, 0, bank); } else { mmc3_map_prg(mem, 2, bank); } } } static inline void mmc3_map_2k_chr(nes_Memory* mem, int reg, int bank) { bank = (bank & ~1) % mem->ppu.n_chr_banks; LOGD("CHR ROM: 2k $%04x <- bank %d + %d", reg << 10, bank, bank | 1); mem->ppu.bank[reg + 0] = chr_page(&mem->ppu, bank); mem->ppu.bank[reg + 1] = chr_page(&mem->ppu, bank | 1); } static inline void mmc3_map_1k_chr(nes_Memory* mem, int reg, int bank) { bank = bank % mem->ppu.n_chr_banks; LOGD("CHR ROM: 1k $%04x <- bank %d", reg << 10, bank); mem->ppu.bank[reg] = chr_page(&mem->ppu, bank); } static inline void mmc3_update_chr(map004_data* data, nes_Memory* mem, int reg, uint8_t bank) { if (!(data->bank_select & mmc3_Bank_Select_CHR)) { if (1 >= reg) { mmc3_map_2k_chr(mem, reg * 2, bank); } else { mmc3_map_1k_chr(mem, reg + 2, bank); } } else { if (1 >= reg) { mmc3_map_2k_chr(mem, 4 + (reg * 2), bank); } else { mmc3_map_1k_chr(mem, reg - 2, bank); } } } static inline void mmc3_update_rom_mode(map004_data* data, nes_Memory* mem, uint8_t val) { uint8_t delta = (data->bank_select ^ val); data->bank_select = val; if (delta & mmc3_Bank_Select_PRG) { mmc3_map_prg(mem, 1, data->r[7]); if (!(val & mmc3_Bank_Select_PRG)) { mmc3_map_prg(mem, 0, data->r[6]); mmc3_map_prg(mem, 2, mem->n_rom_banks - 2); } else { mmc3_map_prg(mem, 0, mem->n_rom_banks - 2); mmc3_map_prg(mem, 2, data->r[6]); } } if (delta & mmc3_Bank_Select_CHR) { mmc3_update_chr(data, mem, 0, data->r[0]); mmc3_update_chr(data, mem, 1, data->r[1]); mmc3_update_chr(data, mem, 2, data->r[2]); mmc3_update_chr(data, mem, 3, data->r[3]); mmc3_update_chr(data, mem, 4, data->r[4]); mmc3_update_chr(data, mem, 5, data->r[5]); } } static void map004_reset(nes_Mapper* map, nes_Memory* mem) { map004_data* data = (map004_data*)map->data; data->irq_count = 0; data->irq_latch = 0; mmc3_update_rom_mode(data, mem, 0); mmc3_map_prg(mem, 3, mem->n_rom_banks - 1); mmc3_update_vram(data, mem); } static int map004_init(nes_Mapper* map, const ines_Header* hdr, nes_Memory* mem) { map004_data* data = (map004_data*)map->data; data->flags = (hdr->flags_6 & ines_Flag_Vert_Mirror) ? mmc3_Flag_Horizontal : 0; data->bank_select = mmc3_Bank_Select_PRG | mmc3_Bank_Select_CHR; map004_reset(map, mem); return 0; } static int map004_protect_sram(nes_Mapper* map, nes_Memory* mem, uint16_t addr, uint8_t val) { // Intercept any writes! return 0; } static int map004_write_rom(nes_Mapper* map, nes_Memory* mem, uint16_t addr, uint8_t val) { map004_data* data = (map004_data*)map->data; LOGD("$%04x < %02x", (int)addr, (int)val); switch ((addr >> 13) & 3) { case 0: // 0x8000 - 0x9FFF if (addr & 1) { // Bank data int reg = (data->bank_select & mmc3_Bank_Select_Reg); if (reg >= 6) { mmc3_update_prg(data, mem, reg, val); } else { mmc3_update_chr(data, mem, reg, val); } data->r[reg] = val; } else { // Bank Select mmc3_update_rom_mode(data, mem, val); } break; case 1: // 0xA000 - 0xBFFF if (addr & 1) { LOGD("SRAM %s, %s", (val & mmc3_Flag_WRAM_Enabled) ? "enabled" : "disabled", (val & mmc3_Flag_WRAM_Protect) ? "protected" : "writable"); // WRAM protection data->flags &= ~(mmc3_Flag_WRAM_Enabled | mmc3_Flag_WRAM_Protect); data->flags |= (val & (mmc3_Flag_WRAM_Enabled | mmc3_Flag_WRAM_Protect)); // TODO: Set these on load / reset if (val & mmc3_Flag_WRAM_Enabled) { mem->sram_bank = mem->sram; } else { mem->sram_bank = NULL; } if (val & mmc3_Flag_WRAM_Protect) { map->write_sram = map004_protect_sram; } else { map->write_sram = NULL; } } else { // Mirroring data->flags &= ~mmc3_Flag_Horizontal; data->flags |= (val & mmc3_Flag_Horizontal); mmc3_update_vram(data, mem); } break; case 2: // 0xC000 - 0xDFFF if (addr & 1) { LOGD("IRQ Reload"); data->flags |= mmc3_Flag_IRQ_Reload; } else { LOGD("IRQ Latch: %d", val); data->irq_latch = val; } break; case 3: // 0xE000 - 0xFFFF LOGD("IRQ %s", (addr & 1) ? "Enable" : "Disable"); if (addr & 1) { data->flags |= mmc3_Flag_IRQ_Enabled; } else { data->flags &= ~mmc3_Flag_IRQ_Enabled; map->irq_callback(map->irq_arg, 0); } break; } return 0; } static int map004_hsync(nes_Mapper* map) { map004_data* data = (map004_data*)map->data; if ( data->irq_count <= 0 || (data->flags & mmc3_Flag_IRQ_Reload)) { data->irq_count = data->irq_latch; data->flags &= ~mmc3_Flag_IRQ_Reload; } else { data->irq_count--; } if ( data->irq_count <= 0 && (data->flags & mmc3_Flag_IRQ_Enabled)) { LOGD("IRQ Trigger"); map->irq_callback(map->irq_arg, 1); data->irq_count = 0; } return 0; } const nes_Mapper map004 = { .name = "MMC3", .max_chr_banks = 256, .data_size = sizeof(map004_data), .init = map004_init, .reset = map004_reset, .write_rom = map004_write_rom, .hsync = map004_hsync, };