#include #include #define MAPPER_NAME "MMC1" #include "map.h" typedef struct { uint8_t* prg_rom; int prg_rom_banks; uint8_t* chr_rom; int chr_rom_banks; uint8_t battery; uint8_t* chr_bank[2]; uint8_t* vram_bank[4]; uint8_t* prg_bank[2]; uint8_t* chr; int chr_bank_offset[2]; int chr_ram_lim; 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; uint8_t vram[2][nes_vram_page_size]; uint8_t wram[nes_mem_wram_size]; uint8_t chr_ram[]; } mmc1_mapper; static void mmc1_update_vram(mmc1_mapper* map) { // VRAM selection int nametable = (map->reg_control & 0b11); if (nametable == 0) { MAP_LOG("VRAM: 1-screen, lower bank"); map->vram_bank[0] = map->vram_bank[1] = map->vram_bank[2] = map->vram_bank[3] = map->vram[0]; } else if (nametable == 1) { MAP_LOG("VRAM: 1-screen, upper bank"); map->vram_bank[0] = map->vram_bank[1] = map->vram_bank[2] = map->vram_bank[3] = map->vram[1]; } else if (nametable == 2) { MAP_LOG("VRAM: 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 if (nametable == 3) { MAP_LOG("VRAM: 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 mmc1_update_chr(mmc1_mapper* map) { int n_banks = map->chr_rom_banks; if (n_banks <= 0) n_banks = 32; int banks[2] = {0}; if (!(map->reg_control & 0b10000)) { banks[0] = (map->reg_chr_0 & 0b11110) % n_banks; banks[1] = banks[0] + 1; } else { banks[0] = map->reg_chr_0 % n_banks; banks[1] = map->reg_chr_1 % n_banks; } MAP_LOG("CHR: %d + %d", banks[0], banks[1]); map->chr_bank_offset[0] = banks[0] * nes_chr_page_size; map->chr_bank_offset[1] = banks[1] * nes_chr_page_size; map->chr_bank[0] = &map->chr[map->chr_bank_offset[0]]; map->chr_bank[1] = &map->chr[map->chr_bank_offset[1]]; } static void mmc1_update_prg(mmc1_mapper* map) { // PRG ROM selection int mode = (map->reg_control >> 2) & 3; int bank_0 = (map->reg_prg & 0b01111) % map->prg_rom_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 = (map->prg_rom_banks - 1); } MAP_LOG("PRG: %d + %d", bank_0, bank_1); map->prg_bank[0] = &map->prg_rom[bank_0 * nes_prg_rom_page_size]; map->prg_bank[1] = &map->prg_rom[bank_1 * nes_prg_rom_page_size]; } static void mmc1_reset(void* data) { mmc1_mapper* map = (mmc1_mapper*)data; map->reg_shift = 0b10000; map->reg_control = 0b01100; map->reg_chr_0 = 0; map->reg_chr_1 = 0; map->reg_prg = 0; mmc1_update_prg(map); mmc1_update_chr(map); mmc1_update_vram(map); } static void* mmc1_init(nes_mapper* nes_map, nes_cart* cart) { int chr_ram_size = ( cart->chr_rom_banks > 0 ? 0 : 128 * 1024); mmc1_mapper* map = calloc(1, sizeof(mmc1_mapper) + chr_ram_size); if (NULL != map) { map->prg_rom = cart->prg_rom; map->prg_rom_banks = cart->prg_rom_banks; map->chr_rom_banks = cart->chr_rom_banks; if (map->chr_rom_banks > 0) { map->chr_rom = cart->chr_rom; map->chr = map->chr_rom; } else { map->chr_rom = NULL; map->chr = map->chr_ram; } map->chr_ram_lim = 0; map->battery = !!(cart->flags & Cart_Flag_Battery); mmc1_reset(map); } return map; } static void mmc1_done(void* data) { free(data); } static inline uint8_t* mmc1_prg_addr(mmc1_mapper* map, uint16_t addr) { int bank = (addr >> 14) & 1; addr &= 0x3FFFU; return &map->prg_bank[bank][addr]; } static inline uint8_t* mmc1_wram_addr(mmc1_mapper* map, uint16_t addr) { return &(map->wram[addr & 0x1FFFU]); } static uint8_t mmc1_read(void* data, uint16_t addr) { uint8_t val = 0; if (addr >= nes_mem_rom_start) { val = *(mmc1_prg_addr((mmc1_mapper*)data, addr)); } else if (addr >= nes_mem_wram_start) { val = *(mmc1_wram_addr((mmc1_mapper*)data, addr)); } return val; } static void mmc1_write(void* data, uint16_t addr, uint8_t val) { mmc1_mapper* map = (mmc1_mapper*)data; if (addr >= nes_mem_rom_start) { MAP_LOG("Write $%04x < %02x", addr, val); if (val & 0x80U) { MAP_LOG("Resetting"); map->reg_shift = 0b10000; map->reg_control |= 0b01100; mmc1_update_prg(map); } else { // TODO: Handle consecutive-cycle writes? int done = (map->reg_shift & 1); map->reg_shift = (map->reg_shift >> 1) | ((val & 1) << 4); if (done) { int reg = (addr >> 13) & 0b11; if (reg == 0) { MAP_LOG("Control %02x", map->reg_shift); map->reg_control = map->reg_shift; mmc1_update_chr(map); mmc1_update_vram(map); mmc1_update_prg(map); } else if (reg == 1) { MAP_LOG("CHR_0 %02x", map->reg_shift); map->reg_chr_0 = map->reg_shift; mmc1_update_chr(map); } else if (reg == 2) { MAP_LOG("CHR_1 %02x", map->reg_shift); map->reg_chr_1 = map->reg_shift; mmc1_update_chr(map); } else { MAP_LOG("PRG %02x", map->reg_shift); map->reg_prg = map->reg_shift; mmc1_update_prg(map); } map->reg_shift = 0b10000; } } } else if ( addr < nes_mem_rom_start && addr >= nes_mem_wram_start) { *(mmc1_wram_addr(map, addr)) = val; } } static uint8_t* mmc1_chr_addr(void* data, uint16_t addr) { int page = (addr >> 12) & 1; addr &= 0xFFFU; return &((mmc1_mapper*)data)->chr_bank[page][addr]; } static void mmc1_chr_write(void* data, uint16_t addr, uint8_t val) { mmc1_mapper* map = (mmc1_mapper*)data; if (NULL == map->chr_rom) { int pos = map->chr_bank_offset[(addr >> 12) & 1] + (addr & 0xFFFU); if (pos >= map->chr_ram_lim) map->chr_ram_lim = pos + 1; *(mmc1_chr_addr(data, addr)) = val; } } static uint8_t* mmc1_vram_addr(void* data, uint16_t addr) { int page = (addr >> 10) & 3; int loc = addr & 0x3FFU; // MAP_LOG("VRAM $%04x -> %p", addr, ((mmc1_mapper*)nes_map->data)->vram_bank[page]); return &((mmc1_mapper*)data)->vram_bank[page][loc]; } static void* mmc1_sram(void* data) { mmc1_mapper* map = (mmc1_mapper*)data; return (map->battery ? map->wram : NULL); } static int mmc1_sram_size(void* data) { mmc1_mapper* map = (mmc1_mapper*)data; return (map->battery ? sizeof(map->wram) : 0); } /* Save State */ static inline int mmc1_chr_ram_size(const mmc1_mapper* map) { return (map->chr_rom_banks <= 0 ? (128 * 1024) : 0); } static int mmc1_state_size(const void* _map) { mmc1_mapper* map = (mmc1_mapper*)_map; return ( map->chr_ram_lim + (void*)map + sizeof(*map) - (void*)&(map->reg_shift)); } static int mmc1_state_read(void* _map, const void* data, int data_len) { mmc1_mapper* map = (mmc1_mapper*)_map; int base_size = mmc1_state_size(map) - map->chr_ram_lim; int size = base_size + mmc1_chr_ram_size(map); if (size > data_len) size = data_len; if (data_len - base_size > map->chr_ram_lim) { map->chr_ram_lim = data_len - base_size; } memcpy(&(map->reg_shift), data, size); mmc1_update_prg(map); mmc1_update_chr(map); mmc1_update_vram(map); return size; } static int mmc1_state_write(const void* _map, void* data, int data_len) { mmc1_mapper* map = (mmc1_mapper*)_map; int size = mmc1_state_size(_map); if (size > data_len) size = data_len; memcpy(data, &(map->reg_shift), size); return size; } nes_mapper mapper_mmc1 = { .name = "MMC1", .init = mmc1_init, .reset = mmc1_reset, .done = mmc1_done, .read = mmc1_read, .write = mmc1_write, .chr_addr = mmc1_chr_addr, .vram_addr = mmc1_vram_addr, .chr_write = mmc1_chr_write, .sram_size = mmc1_sram_size, .sram = mmc1_sram, .state_size = mmc1_state_size, .state_read = mmc1_state_read, .state_write = mmc1_state_write, };