|
|
|
@@ -0,0 +1,257 @@ |
|
|
|
#include "memory.h" |
|
|
|
|
|
|
|
#define 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, |
|
|
|
}; |