using static AxibugEmuOnline.Client.UNES.Cartridge.VRAMMirroringMode; namespace AxibugEmuOnline.Client.UNES.Mapper { [MapperDef(4)] public class MMC3 : BaseMapper { // Different PRG RAM write/enable controls public enum ChipType { MMC3, MMC6 } public enum CHRBankingMode { TwoFour, FourTwo } public enum PRGBankingMode { SwitchFix, FixSwitch } private readonly Cartridge.VRAMMirroringMode[] _mirroringModes = { Vertical, Horizontal }; private readonly ChipType _type; protected CHRBankingMode _chrBankingMode; protected PRGBankingMode _prgBankingMode; protected readonly uint[] _chrBankOffsets = new uint[8]; protected uint[] _prgBankOffsets; protected readonly uint[] _banks = new uint[8]; protected uint _currentBank; private uint _irqReloadValue; private uint _irqCounter; protected bool _irqEnabled; private bool _prgRAMEnabled; public MMC3(Emulator emulator) : this(emulator, ChipType.MMC3) { } public MMC3(Emulator emulator, ChipType chipType) : base(emulator) { _type = chipType; _prgBankOffsets = new uint[] { 0, 0x2000, _lastBankOffset, _lastBankOffset + 0x2000 }; } public override void InitializeMemoryMap(CPU cpu) { cpu.MapReadHandler(0x6000, 0x7FFF, address => _prgRAM[address - 0x6000]); cpu.MapReadHandler(0x8000, 0xFFFF, address => _prgROM[_prgBankOffsets[(address - 0x8000) / 0x2000] + address % 0x2000]); cpu.MapWriteHandler(0x6000, 0xFFFF, WriteByte); } public override void InitializeMemoryMap(PPU ppu) { ppu.MapReadHandler(0x0000, 0x1FFF, address => _chrROM[_chrBankOffsets[address / 0x400] + address % 0x400]); ppu.MapWriteHandler(0x0000, 0x1FFF, (address, val) => _chrROM[_chrBankOffsets[address / 0x400] + address % 0x400] = val); } public override void ProcessCycle(int scanLine, int cycle) { if (_emulator.PPU.F.RenderingEnabled && cycle == 260 && (0 <= scanLine && scanLine < 240 || scanLine == -1)) { if (_irqCounter == 0) { _irqCounter = _irqReloadValue; } else { _irqCounter--; if (_irqEnabled && _irqCounter == 0) { _emulator.CPU.TriggerInterrupt(CPU.InterruptType.IRQ); } } } } protected void WriteByte(uint addr, byte value) { bool even = (addr & 0x1) == 0; if (addr < 0x8000) { if (_prgRAMEnabled) { _prgRAM[addr - 0x6000] = value; } } else if (addr < 0xA000) { if (even) { _currentBank = value & 0x7u; _prgBankingMode = (PRGBankingMode)((value >> 6) & 0x1); _chrBankingMode = (CHRBankingMode)((value >> 7) & 0x1); } else { _banks[_currentBank] = value; } UpdateOffsets(); } else if (addr < 0xC000) { if (even) { _emulator.Cartridge.MirroringMode = _mirroringModes[value & 0x1]; } else { _prgRAMEnabled = (value & 0xC0) == 0x80; } } else if (addr < 0xE000) { if (even) { _irqReloadValue = value; } else { _irqCounter = 0; } } else { _irqEnabled = !even; } } protected void UpdateOffsets() { switch (_prgBankingMode) { case PRGBankingMode.SwitchFix: _prgBankOffsets[0] = _banks[6] * 0x2000; _prgBankOffsets[1] = _banks[7] * 0x2000; _prgBankOffsets[2] = _lastBankOffset; _prgBankOffsets[3] = _lastBankOffset + 0x2000; break; case PRGBankingMode.FixSwitch: _prgBankOffsets[0] = _lastBankOffset; _prgBankOffsets[1] = _banks[7] * 0x2000; _prgBankOffsets[2] = _banks[6] * 0x2000; _prgBankOffsets[3] = _lastBankOffset + 0x2000; break; } switch (_chrBankingMode) { case CHRBankingMode.TwoFour: _chrBankOffsets[0] = _banks[0] & 0xFE; _chrBankOffsets[1] = _banks[0] | 0x01; _chrBankOffsets[2] = _banks[1] & 0xFE; _chrBankOffsets[3] = _banks[1] | 0x01; _chrBankOffsets[4] = _banks[2]; _chrBankOffsets[5] = _banks[3]; _chrBankOffsets[6] = _banks[4]; _chrBankOffsets[7] = _banks[5]; break; case CHRBankingMode.FourTwo: _chrBankOffsets[0] = _banks[2]; _chrBankOffsets[1] = _banks[3]; _chrBankOffsets[2] = _banks[4]; _chrBankOffsets[3] = _banks[5]; _chrBankOffsets[4] = _banks[0] & 0xFE; _chrBankOffsets[5] = _banks[0] | 0x01; _chrBankOffsets[6] = _banks[1] & 0xFE; _chrBankOffsets[7] = _banks[1] | 0x01; break; } for (var i = 0; i < _prgBankOffsets.Length; i++) { _prgBankOffsets[i] %= (uint)_prgROM.Length; } for (var i = 0; i < _chrBankOffsets.Length; i++) { _chrBankOffsets[i] = (uint) (_chrBankOffsets[i] * 0x400 % _chrROM.Length); } } } }