|
|
|
@@ -0,0 +1,270 @@ |
|
|
|
#include <stdlib.h> |
|
|
|
|
|
|
|
#include "map.h" |
|
|
|
|
|
|
|
typedef enum { |
|
|
|
mmc3_Flag_Horizontal = 0b00000001, |
|
|
|
mmc3_Flag_IRQ_Enabled = 0b00000010, |
|
|
|
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* prg_rom; |
|
|
|
int prg_rom_banks; // 16 KB / 8 KB = 2 |
|
|
|
uint8_t* chr_rom; |
|
|
|
int chr_rom_banks; // 4 KB / 1 KB = 4 |
|
|
|
|
|
|
|
uint8_t* prg_bank[4]; // 8 KB |
|
|
|
uint8_t* chr_bank[8]; // 1 KB |
|
|
|
uint8_t* vram_bank[4]; |
|
|
|
|
|
|
|
mmc3_Flag flags; |
|
|
|
uint8_t bank_select; |
|
|
|
uint8_t irq_count; |
|
|
|
uint8_t irq_latch; |
|
|
|
|
|
|
|
uint8_t wram[nes_mem_wram_size]; |
|
|
|
uint8_t vram[2][nes_vram_page_size]; |
|
|
|
} mmc3_mapper; |
|
|
|
|
|
|
|
static inline uint8_t* mmc3_prg_bank(mmc3_mapper* map, int bank) { |
|
|
|
return &map->prg_rom[bank << 13]; |
|
|
|
} |
|
|
|
|
|
|
|
static inline void mmc3_map_prg(mmc3_mapper* map, |
|
|
|
int reg, int bank) { |
|
|
|
MAP_LOG("PRG ROM: 8k $%04x <- bank %d", reg << 13, bank); |
|
|
|
map->prg_bank[reg] = mmc3_prg_bank(map, bank); |
|
|
|
} |
|
|
|
|
|
|
|
static inline void mmc3_update_rom_mode(mmc3_mapper* map) { |
|
|
|
if (!(map->bank_select & mmc3_Bank_Select_PRG)) { |
|
|
|
mmc3_map_prg(map, 2, map->prg_rom_banks - 2); |
|
|
|
} else { |
|
|
|
mmc3_map_prg(map, 0, map->prg_rom_banks - 2); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static inline void mmc3_update_prg(mmc3_mapper* map, uint8_t bank) { |
|
|
|
int reg = (map->bank_select & mmc3_Bank_Select_Reg); |
|
|
|
|
|
|
|
if (!(map->bank_select & mmc3_Bank_Select_PRG)) { |
|
|
|
if (reg == 7) { |
|
|
|
mmc3_map_prg(map, 1, bank); |
|
|
|
} else { |
|
|
|
mmc3_map_prg(map, 0, bank); |
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
if (reg == 7) { |
|
|
|
mmc3_map_prg(map, 1, bank); |
|
|
|
} else { |
|
|
|
mmc3_map_prg(map, 2, bank); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
static inline uint8_t* mmc3_chr_bank(mmc3_mapper* map, int bank) { |
|
|
|
return &map->chr_rom[bank << 10]; |
|
|
|
} |
|
|
|
|
|
|
|
static inline void mmc3_map_2k_chr(mmc3_mapper* map, |
|
|
|
int reg, int bank) { |
|
|
|
MAP_LOG("CHR ROM: 2k $%04x <- bank %d + %d", reg << 10, bank & ~1, bank | 1); |
|
|
|
map->chr_bank[reg + 0] = mmc3_chr_bank(map, bank & ~1); |
|
|
|
map->chr_bank[reg + 1] = mmc3_chr_bank(map, bank | 1); |
|
|
|
} |
|
|
|
|
|
|
|
static inline void mmc3_map_1k_chr(mmc3_mapper* map, |
|
|
|
int reg, int bank) { |
|
|
|
MAP_LOG("CHR ROM: 1k $%04x <- bank %d", reg << 10, bank); |
|
|
|
map->chr_bank[reg] = mmc3_chr_bank(map, bank); |
|
|
|
} |
|
|
|
|
|
|
|
static inline void mmc3_update_chr(mmc3_mapper* map, uint8_t bank) { |
|
|
|
int reg = (map->bank_select & mmc3_Bank_Select_Reg); |
|
|
|
|
|
|
|
if (!(map->bank_select & mmc3_Bank_Select_CHR)) { |
|
|
|
if (1 >= reg) { |
|
|
|
mmc3_map_2k_chr(map, reg * 2, bank); |
|
|
|
} else { |
|
|
|
mmc3_map_1k_chr(map, reg + 2, bank); |
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
if (1 >= reg) { |
|
|
|
mmc3_map_2k_chr(map, 4 + (reg * 2), bank); |
|
|
|
} else { |
|
|
|
mmc3_map_1k_chr(map, reg - 2, bank); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static inline void mmc3_update_vram(mmc3_mapper* map) { |
|
|
|
if (!(map->flags & mmc3_Flag_Horizontal)) { |
|
|
|
// Vertical mirroring |
|
|
|
MAP_LOG("Vertical mirroring"); |
|
|
|
map->vram_bank[0] = map->vram_bank[2] = map->vram[0]; |
|
|
|
map->vram_bank[1] = map->vram_bank[3] = map->vram[1]; |
|
|
|
} else { |
|
|
|
// Horizontal mirroring |
|
|
|
MAP_LOG("Horizontal mirroring"); |
|
|
|
map->vram_bank[0] = map->vram_bank[1] = map->vram[0]; |
|
|
|
map->vram_bank[2] = map->vram_bank[3] = map->vram[1]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void mmc3_reset(nes_mapper* nes_map) { |
|
|
|
mmc3_mapper* map = (mmc3_mapper*)nes_map->data; |
|
|
|
map->flags = 0; |
|
|
|
map->bank_select = 0; |
|
|
|
map->irq_count = 0; |
|
|
|
map->irq_latch = 0; |
|
|
|
map->prg_bank[3] = mmc3_prg_bank(map, map->prg_rom_banks - 1); |
|
|
|
mmc3_update_vram(map); |
|
|
|
} |
|
|
|
|
|
|
|
static int mmc3_init(nes_mapper* nes_map, nes_cart* cart) { |
|
|
|
mmc3_mapper* map = calloc(1, sizeof(mmc3_mapper)); |
|
|
|
nes_map->data = map; |
|
|
|
if (NULL != map) { |
|
|
|
map->prg_rom = cart->prg_rom; |
|
|
|
map->prg_rom_banks = cart->prg_rom_banks * 2; |
|
|
|
map->chr_rom = cart->chr_rom; |
|
|
|
map->chr_rom_banks = cart->chr_rom_banks * 4; |
|
|
|
map->flags = (cart->flags & Cart_Flag_Horizontal) ? |
|
|
|
mmc3_Flag_Horizontal : 0; |
|
|
|
mmc3_reset(nes_map); |
|
|
|
} |
|
|
|
return (NULL == map ? -1 : 0); |
|
|
|
} |
|
|
|
|
|
|
|
static void mmc3_done(nes_mapper* nes_map) { |
|
|
|
free(nes_map->data); |
|
|
|
} |
|
|
|
|
|
|
|
static inline uint8_t* mmc3_prg_addr(mmc3_mapper* map, |
|
|
|
uint16_t addr) { |
|
|
|
return &(map->prg_bank[(addr >> 13) & 3][addr & 0x1FFFU]); |
|
|
|
} |
|
|
|
|
|
|
|
static inline uint8_t* mmc3_wram_addr(mmc3_mapper* map, |
|
|
|
uint16_t addr) { |
|
|
|
return &(map->wram[addr & 0x1FFFU]); |
|
|
|
} |
|
|
|
|
|
|
|
static uint8_t mmc3_read(nes_mapper* nes_map, uint16_t addr) { |
|
|
|
uint8_t* ptr = NULL; |
|
|
|
mmc3_mapper* map = (mmc3_mapper*)nes_map->data; |
|
|
|
if (addr >= nes_mem_rom_start) { |
|
|
|
ptr = mmc3_prg_addr(map, addr); |
|
|
|
} else if ( addr >= nes_mem_wram_start && |
|
|
|
(map->flags & mmc3_Flag_WRAM_Enabled)) { |
|
|
|
ptr = mmc3_wram_addr(map, addr); |
|
|
|
MAP_LOG("WRAM: $%04x > %02x", addr, *ptr); |
|
|
|
} |
|
|
|
|
|
|
|
uint8_t val = (NULL == ptr ? 0 : *ptr); |
|
|
|
|
|
|
|
// MAP_LOG("$%04x -> %04lx > %02x", addr, ptr - map->prg_rom, val); |
|
|
|
|
|
|
|
return val; |
|
|
|
} |
|
|
|
|
|
|
|
static void mmc3_write(nes_mapper* nes_map, |
|
|
|
uint16_t addr, uint8_t val) { |
|
|
|
mmc3_mapper* map = (mmc3_mapper*)nes_map->data; |
|
|
|
|
|
|
|
if (addr >= nes_mem_rom_start) MAP_LOG("$%04x < %02x", addr, val); |
|
|
|
|
|
|
|
if (addr < nes_mem_wram_start) { |
|
|
|
// Nothing prior to WRAM |
|
|
|
|
|
|
|
} else if (addr < nes_mem_rom_start) { |
|
|
|
if ( (map->flags & mmc3_Flag_WRAM_Enabled) && |
|
|
|
!(map->flags & mmc3_Flag_WRAM_Protect)) { |
|
|
|
// MAP_LOG("WRAM: $%04x < %02x", addr, val); |
|
|
|
*(mmc3_wram_addr(map, addr)) = val; |
|
|
|
} |
|
|
|
|
|
|
|
} else if (addr < 0xA000U) { |
|
|
|
if (addr & 1) { |
|
|
|
// Bank data |
|
|
|
if ((map->bank_select & mmc3_Bank_Select_Reg) >= 6) { |
|
|
|
mmc3_update_prg(map, val); |
|
|
|
} else { |
|
|
|
mmc3_update_chr(map, val); |
|
|
|
} |
|
|
|
} else { |
|
|
|
// Bank select |
|
|
|
map->bank_select = val; |
|
|
|
mmc3_update_rom_mode(map); |
|
|
|
} |
|
|
|
|
|
|
|
} else if (addr < 0xC000U) { |
|
|
|
if (addr & 1) { |
|
|
|
MAP_LOG("WRAM %s, %s", (val & mmc3_Flag_WRAM_Enabled) ? "enabled" : "disabled", (val & mmc3_Flag_WRAM_Protect) ? "protected" : "writable"); |
|
|
|
// WRAM protection |
|
|
|
map->flags &= ~(mmc3_Flag_WRAM_Enabled | |
|
|
|
mmc3_Flag_WRAM_Protect); |
|
|
|
map->flags |= (val & (mmc3_Flag_WRAM_Enabled | |
|
|
|
mmc3_Flag_WRAM_Protect)); |
|
|
|
} else { |
|
|
|
// Mirroring |
|
|
|
map->flags &= ~mmc3_Flag_Horizontal; |
|
|
|
map->flags |= (val & mmc3_Flag_Horizontal); |
|
|
|
mmc3_update_vram(map); |
|
|
|
} |
|
|
|
|
|
|
|
} else if (addr < 0xE000U) { |
|
|
|
if (addr & 1) { |
|
|
|
// TODO: IRQ reload |
|
|
|
} else { |
|
|
|
// TODO: IRQ latch |
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
if (addr & 1) { |
|
|
|
// TODO: IRQ enable |
|
|
|
} else { |
|
|
|
// TODO: IRQ disable |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static uint8_t* mmc3_chr_addr(nes_mapper* nes_map, |
|
|
|
uint16_t addr) { |
|
|
|
mmc3_mapper* map = (mmc3_mapper*)nes_map->data; |
|
|
|
return &map->chr_bank[(addr >> 10) & 7][addr & 0x3FFU]; |
|
|
|
} |
|
|
|
|
|
|
|
static uint8_t* mmc3_vram_addr(nes_mapper* nes_map, |
|
|
|
uint16_t addr) { |
|
|
|
mmc3_mapper* map = (mmc3_mapper*)nes_map->data; |
|
|
|
return &map->vram_bank[(addr >> 10) & 3][addr & 0x3FFU]; |
|
|
|
} |
|
|
|
|
|
|
|
static void mmc3_chr_write(nes_mapper* map, |
|
|
|
uint16_t addr, uint8_t val) { |
|
|
|
// ROM only. |
|
|
|
printf("CHR ROM Write: $%04x < %02x\n", addr, val); |
|
|
|
} |
|
|
|
|
|
|
|
nes_mapper mapper_mmc3 = { |
|
|
|
.init = mmc3_init, |
|
|
|
.reset = mmc3_reset, |
|
|
|
.done = mmc3_done, |
|
|
|
.read = mmc3_read, |
|
|
|
.write = mmc3_write, |
|
|
|
.chr_addr = mmc3_chr_addr, |
|
|
|
.vram_addr = mmc3_vram_addr, |
|
|
|
.chr_write = mmc3_chr_write, |
|
|
|
}; |