// license:BSD-3-Clause // copyright-holders:Fabio Priuli /*********************************************************************************************************** NES/Famicom cartridge emulation for Somari Team PCBs Here we emulate the Somari Team PCBs [mapper 116] ***********************************************************************************************************/ #include "emu.h" #include "somari.h" #ifdef NES_PCB_DEBUG #define VERBOSE 1 #else #define VERBOSE 0 #endif #define LOG_MMC(x) do { if (VERBOSE) logerror x; } while (0) #define SOMARI_VRC2_MODE 0 #define SOMARI_MMC3_MODE 1 #define SOMARI_MMC1_MODE 2 #define SOMARI_MMC1_MODE_AGAIN 3 //------------------------------------------------- // constructor //------------------------------------------------- DEFINE_DEVICE_TYPE(NES_SOMARI, nes_somari_device, "nes_somari", "NES Cart Team Somari PCB") nes_somari_device::nes_somari_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : nes_txrom_device(mconfig, NES_SOMARI, tag, owner, clock) , m_board_mode(0) , m_mmc3_mirror_reg(0) , m_count(0) , m_mmc1_latch(0) , m_vrc_mirror_reg(0) { } void nes_somari_device::device_start() { common_start(); save_item(NAME(m_board_mode)); // MMC3 save_item(NAME(m_mmc_prg_bank)); save_item(NAME(m_mmc_vrom_bank)); save_item(NAME(m_latch)); save_item(NAME(m_prg_base)); save_item(NAME(m_prg_mask)); save_item(NAME(m_chr_base)); save_item(NAME(m_chr_mask)); save_item(NAME(m_mmc3_mirror_reg)); save_item(NAME(m_irq_enable)); save_item(NAME(m_irq_count)); save_item(NAME(m_irq_count_latch)); save_item(NAME(m_irq_clear)); // MMC1 save_item(NAME(m_count)); save_item(NAME(m_mmc1_latch)); save_item(NAME(m_mmc1_reg)); // VRC2 save_item(NAME(m_vrc_prg_bank)); save_item(NAME(m_vrc_vrom_bank)); save_item(NAME(m_vrc_mirror_reg)); } void nes_somari_device::pcb_reset() { m_chr_source = m_vrom_chunks ? CHRROM : CHRRAM; prg32(0); chr8(0, m_chr_source); m_board_mode = 2; // mode // MMC3 m_prg_base = m_chr_base = 0; m_prg_mask = 0xff; m_chr_mask = 0xff; m_latch = 0; m_mmc_prg_bank[0] = 0x3c; m_mmc_prg_bank[1] = 0x3d; m_mmc_prg_bank[2] = 0x3e; m_mmc_prg_bank[3] = 0x3f; m_mmc_vrom_bank[0] = 0x00; m_mmc_vrom_bank[1] = 0x01; m_mmc_vrom_bank[2] = 0x04; m_mmc_vrom_bank[3] = 0x05; m_mmc_vrom_bank[4] = 0x06; m_mmc_vrom_bank[5] = 0x07; m_mmc3_mirror_reg = 0; m_alt_irq = 0; m_irq_enable = 0; m_irq_count = m_irq_count_latch = 0; m_irq_clear = 0; // MMC1 regs m_count = 0; m_mmc1_latch = 0; m_mmc1_reg[0] = 0x0c; m_mmc1_reg[1] = 0x00; m_mmc1_reg[2] = 0x00; m_mmc1_reg[3] = 0x00; // VRC2 regs m_vrc_prg_bank[0] = 0x00; m_vrc_prg_bank[1] = 0x01; for (int i = 0; i < 8; ++i) m_vrc_vrom_bank[i] = i; bank_update_switchmode(); m_vrc_mirror_reg = 0; } /*------------------------------------------------- mapper specific handlers -------------------------------------------------*/ /*------------------------------------------------- SOMERI TEAM iNES: mapper 116 Emulation note about regs in MESS: currently, - m_mmc_prg_bank[n] for n=0,...,3 represent the MMC3 PRG banks (inherited from base class) - m_mmc_vrom_bank[n] for n=0,...,5 represent the MMC3 CHR banks (inherited from base class) - m_mmc1_reg[n] for n=0,1,2,3 represent the MMC1 regs - m_count and m_mmc1_latch are additional variables for MMC1 (notice that MMC3 uses a diff m_latch!) - m_vrc_prg_bank[n] for n=0,1 represent the VRC2 PRG banks - m_vrc_vrom_bank[n] for n=0,...,7 represent the VRC2 CHR banks In MESS: Preliminary support -------------------------------------------------*/ // MMC1 Mode emulation void nes_somari_device::mmc1_w(offs_t offset, uint8_t data) { assert(m_board_mode == 2); if (data & 0x80) { m_count = 0; m_mmc1_latch = 0; m_mmc1_reg[0] |= 0x0c; update_prg(); return; } if (m_count < 5) { if (m_count == 0) m_mmc1_latch = 0; m_mmc1_latch >>= 1; m_mmc1_latch |= (data & 0x01) ? 0x10 : 0x00; m_count++; } if (m_count == 5) { m_mmc1_reg[(offset & 0x6000) >> 13] = m_mmc1_latch; update_mirror(); update_prg(); update_chr(); m_count = 0; } } // MMC3 Mode emulation void nes_somari_device::mmc3_w(offs_t offset, uint8_t data) { uint8_t mmc_helper, cmd; assert(m_board_mode == 1); switch (offset & 0x6001) { case 0x0000: mmc_helper = m_latch ^ data; m_latch = data; if (mmc_helper & 0x40) update_prg(); if (mmc_helper & 0x80) update_chr(); break; case 0x0001: cmd = m_latch & 0x07; switch (cmd) { case 0: case 1: case 2: case 3: case 4: case 5: m_mmc_vrom_bank[cmd] = data; update_chr(); break; case 6: case 7: m_mmc_prg_bank[cmd - 6] = data & 0x3f; update_prg(); break; } break; case 0x2000: m_mmc3_mirror_reg = data & 1; update_mirror(); break; case 0x2001: break; case 0x4000: m_irq_count_latch = data; break; case 0x4001: m_irq_count = 0; break; case 0x6000: m_irq_enable = 0; set_irq_line(CLEAR_LINE); break; case 0x6001: m_irq_enable = 1; break; } } // VRC2 Mode emulation void nes_somari_device::vrc2_w(offs_t offset, uint8_t data) { uint8_t bank, shift; assert(m_board_mode == 0); switch (offset & 0x7000) { case 0x0000: m_vrc_prg_bank[0] = data & 0x1f; update_prg(); break; case 0x1000: m_vrc_mirror_reg = data & 1; update_mirror(); break; case 0x2000: m_vrc_prg_bank[1] = data & 0x1f; update_prg(); break; case 0x3000: case 0x4000: case 0x5000: case 0x6000: // this makes no sense for vrc2 and breaks somari, but it's ok for garousp!! bank = ((offset & 0x7000) - 0x3000) / 0x0800 + BIT(offset, 1); shift = BIT(offset, 2) * 4; data = (data & 0x0f) << shift; m_vrc_vrom_bank[bank] = data; update_chr(); break; } } void nes_somari_device::update_prg() { switch (m_board_mode) { case SOMARI_VRC2_MODE: prg8_89(m_vrc_prg_bank[0]); prg8_ab(m_vrc_prg_bank[1]); prg8_cd(0x3e); prg8_ef(0x3f); break; case SOMARI_MMC3_MODE: { uint8_t prg_flip = (m_latch & 0x40) ? 2 : 0; prg8_x(0, m_mmc_prg_bank[0 ^ prg_flip]); prg8_x(1, m_mmc_prg_bank[1]); prg8_x(2, m_mmc_prg_bank[2 ^ prg_flip]); prg8_x(3, m_mmc_prg_bank[3]); } break; case SOMARI_MMC1_MODE: // case SOMARI_MMC1_MODE_AGAIN: { uint8_t prg_offset = m_mmc1_reg[1] & 0x10; switch (m_mmc1_reg[0] & 0x0c) { case 0x00: case 0x04: prg32((prg_offset + m_mmc1_reg[3]) >> 1); break; case 0x08: prg16_89ab(prg_offset + 0); prg16_cdef(prg_offset + m_mmc1_reg[3]); break; case 0x0c: prg16_89ab(prg_offset + m_mmc1_reg[3]); prg16_cdef(prg_offset + 0x0f); break; } } break; } } void nes_somari_device::update_chr() { switch (m_board_mode) { case SOMARI_VRC2_MODE: for (int i = 0; i < 8; i++) chr1_x(i, m_chr_base | m_vrc_vrom_bank[i], CHRROM); break; case SOMARI_MMC3_MODE: { uint8_t chr_page = (m_latch & 0x80) >> 5; chr1_x(chr_page ^ 0, m_chr_base | ((m_mmc_vrom_bank[0] & ~0x01)), CHRROM); chr1_x(chr_page ^ 1, m_chr_base | ((m_mmc_vrom_bank[0] | 0x01)), CHRROM); chr1_x(chr_page ^ 2, m_chr_base | ((m_mmc_vrom_bank[1] & ~0x01)), CHRROM); chr1_x(chr_page ^ 3, m_chr_base | ((m_mmc_vrom_bank[1] | 0x01)), CHRROM); chr1_x(chr_page ^ 4, m_chr_base | (m_mmc_vrom_bank[2]), CHRROM); chr1_x(chr_page ^ 5, m_chr_base | (m_mmc_vrom_bank[3]), CHRROM); chr1_x(chr_page ^ 6, m_chr_base | (m_mmc_vrom_bank[4]), CHRROM); chr1_x(chr_page ^ 7, m_chr_base | (m_mmc_vrom_bank[5]), CHRROM); } break; case SOMARI_MMC1_MODE: // case SOMARI_MMC1_MODE_AGAIN: if (BIT(m_mmc1_reg[0], 4)) { chr4_0(m_mmc1_reg[1] & 0x1f, CHRROM); chr4_4(m_mmc1_reg[2] & 0x1f, CHRROM); } else chr8((m_mmc1_reg[1] & 0x1f) >> 1, CHRROM); break; } } void nes_somari_device::update_mirror() { switch (m_board_mode) { case SOMARI_VRC2_MODE: set_nt_mirroring(m_vrc_mirror_reg ? PPU_MIRROR_HORZ : PPU_MIRROR_VERT); break; case SOMARI_MMC3_MODE: set_nt_mirroring(m_mmc3_mirror_reg ? PPU_MIRROR_HORZ : PPU_MIRROR_VERT); break; case SOMARI_MMC1_MODE: // case SOMARI_MMC1_MODE_AGAIN: switch (m_mmc1_reg[0] & 0x03) { case 0x00: set_nt_mirroring(PPU_MIRROR_LOW); break; case 0x01: set_nt_mirroring(PPU_MIRROR_HIGH); break; case 0x02: set_nt_mirroring(PPU_MIRROR_VERT); break; case 0x03: set_nt_mirroring(PPU_MIRROR_HORZ); break; } break; } } void nes_somari_device::write_h(offs_t offset, uint8_t data) { LOG_MMC(("somari write_h, mode %d, offset: %04x, data: %02x\n", m_board_mode, offset, data)); switch (m_board_mode) { case SOMARI_VRC2_MODE: vrc2_w(offset, data); break; case SOMARI_MMC3_MODE: mmc3_w(offset, data); break; case SOMARI_MMC1_MODE: mmc1_w(offset, data); break; } } void nes_somari_device::bank_update_switchmode() { switch (m_board_mode) { case SOMARI_VRC2_MODE: break; case SOMARI_MMC3_MODE: break; case SOMARI_MMC1_MODE: break; } update_mirror(); update_prg(); update_chr(); } void nes_somari_device::write_m(offs_t offset, uint8_t data) { LOG_MMC(("somari write_m, offset: %04x, data: %02x\n", offset, data)); if (offset & 0x100) { m_board_mode = data & 0x03; m_chr_base = ((m_board_mode & 0x04) << 6); if (m_board_mode != 1) m_irq_enable = 0; bank_update_switchmode(); } }