|
|
|
@@ -0,0 +1,162 @@ |
|
|
|
#include <stdlib.h> |
|
|
|
|
|
|
|
#include "memory.h" |
|
|
|
|
|
|
|
//#define DEBUG "MMC1" |
|
|
|
#include "log.h" |
|
|
|
|
|
|
|
|
|
|
|
typedef struct { |
|
|
|
uint8_t reg_shift; |
|
|
|
uint8_t reg_n_shift; |
|
|
|
uint8_t reg_control; |
|
|
|
uint8_t reg_chr_0; |
|
|
|
uint8_t reg_chr_1; |
|
|
|
uint8_t reg_prg; |
|
|
|
} map001_data; |
|
|
|
|
|
|
|
|
|
|
|
static inline void mmc1_update_vram(map001_data* data, |
|
|
|
nes_Memory* mem) { |
|
|
|
const nes_Nametable_Mirroring mirror[4] = { |
|
|
|
nes_Mirror_Only_0, |
|
|
|
nes_Mirror_Only_1, |
|
|
|
nes_Mirror_Vertical, |
|
|
|
nes_Mirror_Horizontal, |
|
|
|
}; |
|
|
|
LOGD("Mirroring %d", mirror[data->reg_control & 0b11]); |
|
|
|
nes_ppu_set_mirroring(&mem->ppu, |
|
|
|
mirror[data->reg_control & 0b11]); |
|
|
|
} |
|
|
|
|
|
|
|
static inline void mmc1_update_chr(map001_data* data, |
|
|
|
nes_Memory* mem) { |
|
|
|
int n_banks = mem->ppu.n_chr_banks / 4; |
|
|
|
int bank_0 = 0, bank_1 = 0; |
|
|
|
if (!(data->reg_control & 0b10000)) { |
|
|
|
bank_0 = (data->reg_chr_0 & 0b11110) % n_banks; |
|
|
|
bank_1 = bank_0 + 1; |
|
|
|
} else { |
|
|
|
bank_0 = data->reg_chr_0 % n_banks; |
|
|
|
bank_1 = data->reg_chr_1 % n_banks; |
|
|
|
} |
|
|
|
|
|
|
|
LOGD("CHR: %d + %d", bank_0, bank_1); |
|
|
|
|
|
|
|
mem->ppu.bank[0] = chr_page(&mem->ppu, (bank_0 * 4) + 0); |
|
|
|
mem->ppu.bank[1] = chr_page(&mem->ppu, (bank_0 * 4) + 1); |
|
|
|
mem->ppu.bank[2] = chr_page(&mem->ppu, (bank_0 * 4) + 2); |
|
|
|
mem->ppu.bank[3] = chr_page(&mem->ppu, (bank_0 * 4) + 3); |
|
|
|
mem->ppu.bank[4] = chr_page(&mem->ppu, (bank_1 * 4) + 0); |
|
|
|
mem->ppu.bank[5] = chr_page(&mem->ppu, (bank_1 * 4) + 1); |
|
|
|
mem->ppu.bank[6] = chr_page(&mem->ppu, (bank_1 * 4) + 2); |
|
|
|
mem->ppu.bank[7] = chr_page(&mem->ppu, (bank_1 * 4) + 3); |
|
|
|
} |
|
|
|
|
|
|
|
static inline void mmc1_update_prg(map001_data* data, |
|
|
|
nes_Memory* mem) { |
|
|
|
int n_banks = mem->n_rom_banks / 2; |
|
|
|
int mode = (data->reg_control >> 2) & 3; |
|
|
|
int bank_0 = (data->reg_prg & 0b01111) % n_banks; |
|
|
|
int bank_1 = bank_0; |
|
|
|
if (!(mode & 0b10)) { |
|
|
|
bank_0 = (bank_0 & 0b01110); |
|
|
|
bank_1 = bank_0 + 1; |
|
|
|
} else if (mode == 2) { |
|
|
|
bank_0 = 0; |
|
|
|
} else if (mode == 3) { |
|
|
|
bank_1 = n_banks - 1; |
|
|
|
} |
|
|
|
|
|
|
|
LOGD("PRG: %d + %d", bank_0, bank_1); |
|
|
|
|
|
|
|
mem->rom_bank[0] = prg_rom_page(mem, (bank_0 * 2) + 0); |
|
|
|
mem->rom_bank[1] = prg_rom_page(mem, (bank_0 * 2) + 1); |
|
|
|
mem->rom_bank[2] = prg_rom_page(mem, (bank_1 * 2) + 0); |
|
|
|
mem->rom_bank[3] = prg_rom_page(mem, (bank_1 * 2) + 1); |
|
|
|
} |
|
|
|
|
|
|
|
static void map001_reset(nes_Mapper* map, nes_Memory* mem) { |
|
|
|
map001_data* data = (map001_data*)map->data; |
|
|
|
data->reg_shift = 0b10000; |
|
|
|
data->reg_control = 0b01100; |
|
|
|
data->reg_chr_0 = 0; |
|
|
|
data->reg_chr_1 = 0; |
|
|
|
data->reg_prg = 0; |
|
|
|
mmc1_update_prg(data, mem); |
|
|
|
mmc1_update_chr(data, mem); |
|
|
|
mmc1_update_vram(data, mem); |
|
|
|
} |
|
|
|
|
|
|
|
static int map001_init(nes_Mapper* map, const ines_Header* hdr, |
|
|
|
nes_Memory* mem) { |
|
|
|
int status = -1; |
|
|
|
|
|
|
|
map->data = calloc(1, sizeof(map001_data)); |
|
|
|
|
|
|
|
if (NULL != map->data) { |
|
|
|
map001_reset(map, mem); |
|
|
|
status = 0; |
|
|
|
} |
|
|
|
|
|
|
|
return status; |
|
|
|
} |
|
|
|
|
|
|
|
static int map001_write_rom(nes_Mapper* map, nes_Memory* mem, |
|
|
|
uint16_t addr, uint8_t val) { |
|
|
|
map001_data* data = (map001_data*)map->data; |
|
|
|
|
|
|
|
LOGD("Write $%04x < %02x", addr, val); |
|
|
|
|
|
|
|
if (val & 0x80U) { |
|
|
|
data->reg_shift = 0b10000; |
|
|
|
data->reg_control |= 0b01100; |
|
|
|
mmc1_update_prg(data, mem); |
|
|
|
} else { |
|
|
|
// TODO: Handle consecutive-cycle writes? |
|
|
|
int done = (data->reg_shift & 1); |
|
|
|
data->reg_shift = (data->reg_shift >> 1) | |
|
|
|
((val & 1) << 4); |
|
|
|
if (done) { |
|
|
|
switch ((addr >> 13) & 3) { |
|
|
|
case 0: |
|
|
|
LOGD("Control %02x", data->reg_shift); |
|
|
|
data->reg_control = data->reg_shift; |
|
|
|
mmc1_update_chr(data, mem); |
|
|
|
mmc1_update_vram(data, mem); |
|
|
|
mmc1_update_prg(data, mem); |
|
|
|
break; |
|
|
|
|
|
|
|
case 1: |
|
|
|
LOGD("CHR_0 %02x", data->reg_shift); |
|
|
|
data->reg_chr_0 = data->reg_shift; |
|
|
|
mmc1_update_chr(data, mem); |
|
|
|
break; |
|
|
|
|
|
|
|
case 2: |
|
|
|
LOGD("CHR_1 %02x", data->reg_shift); |
|
|
|
data->reg_chr_1 = data->reg_shift; |
|
|
|
mmc1_update_chr(data, mem); |
|
|
|
break; |
|
|
|
|
|
|
|
case 3: |
|
|
|
LOGD("PRG %02x", data->reg_shift); |
|
|
|
data->reg_prg = data->reg_shift; |
|
|
|
mmc1_update_prg(data, mem); |
|
|
|
break; |
|
|
|
} |
|
|
|
data->reg_shift = 0b10000; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const nes_Mapper map001 = { |
|
|
|
.name = "MMC1", |
|
|
|
.max_chr_banks = 128, |
|
|
|
.init = map001_init, |
|
|
|
.reset = map001_reset, |
|
|
|
.write_rom = map001_write_rom, |
|
|
|
}; |