171 lines
5.8 KiB
C#
171 lines
5.8 KiB
C#
using static AxibugEmuOnline.Client.UNES.Cartridge.VRAMMirroringMode;
|
|
|
|
namespace AxibugEmuOnline.Client.UNES.Mapper
|
|
{
|
|
[MapperDef(1)]
|
|
public class MMC1 : BaseMapper
|
|
{
|
|
// TODO: are MMC1 and MMC1A even different chip types?
|
|
public enum ChipType { MMC1, MMC1A, MMC1B, MMC1C }
|
|
public enum CHRBankingMode { Single, Double }
|
|
public enum PRGBankingMode { Switch32Kb, Switch16KbFixFirst, Switch16KbFixLast }
|
|
|
|
private readonly Cartridge.VRAMMirroringMode[] _mirroringModes = { Lower, Upper, Vertical, Horizontal };
|
|
|
|
private readonly ChipType _type;
|
|
private CHRBankingMode _chrBankingMode;
|
|
private PRGBankingMode _prgBankingMode;
|
|
|
|
private uint _serialData;
|
|
private int _serialPos;
|
|
|
|
private uint _control;
|
|
|
|
private readonly uint[] _chrBankOffsets = new uint[2];
|
|
private readonly uint[] _chrBanks = new uint[2];
|
|
|
|
private readonly uint[] _prgBankOffsets = new uint[2];
|
|
private uint _prgBank;
|
|
|
|
private bool _prgRAMEnabled;
|
|
|
|
private uint? _lastWritePC;
|
|
|
|
public MMC1(Emulator emulator) : this(emulator, ChipType.MMC1B)
|
|
{
|
|
|
|
}
|
|
|
|
public MMC1(Emulator emulator, ChipType chipType) : base(emulator)
|
|
{
|
|
_type = chipType;
|
|
if (chipType == ChipType.MMC1B) _prgRAMEnabled = true;
|
|
UpdateControl(0x0F);
|
|
_emulator.Cartridge.MirroringMode = Horizontal;
|
|
}
|
|
|
|
public override void InitializeMemoryMap(CPU cpu)
|
|
{
|
|
cpu.MapReadHandler(0x6000, 0x7FFF, address => _prgRAM[address - 0x6000]);
|
|
cpu.MapReadHandler(0x8000, 0xFFFF, address => _prgROM[_prgBankOffsets[(address - 0x8000) / 0x4000] + address % 0x4000]);
|
|
|
|
cpu.MapWriteHandler(0x6000, 0x7FFF, (address, val) =>
|
|
{
|
|
// PRG RAM is always enabled on MMC1A
|
|
if (_type == ChipType.MMC1A || _prgRAMEnabled)
|
|
_prgRAM[address - 0x6000] = val;
|
|
});
|
|
|
|
cpu.MapWriteHandler(0x8000, 0xFFFF, (address, val) =>
|
|
{
|
|
// Explicitly ignore the second write happening on consecutive cycles
|
|
// of an RMW instruction
|
|
var cycle = _emulator.CPU.PC;
|
|
if (cycle == _lastWritePC)
|
|
return;
|
|
_lastWritePC = cycle;
|
|
|
|
if ((val & 0x80) > 0)
|
|
{
|
|
_serialData = 0;
|
|
_serialPos = 0;
|
|
UpdateControl(_control | 0x0C);
|
|
}
|
|
else
|
|
{
|
|
_serialData |= (uint)((val & 0x1) << _serialPos);
|
|
_serialPos++;
|
|
|
|
if (_serialPos == 5)
|
|
{
|
|
// Address is incompletely decoded
|
|
address &= 0x6000;
|
|
if (address == 0x0000)
|
|
UpdateControl(_serialData);
|
|
else if (address == 0x2000)
|
|
UpdateCHRBank(0, _serialData);
|
|
else if (address == 0x4000)
|
|
UpdateCHRBank(1, _serialData);
|
|
else if (address == 0x6000)
|
|
UpdatePRGBank(_serialData);
|
|
|
|
_serialData = 0;
|
|
_serialPos = 0;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public override void InitializeMemoryMap(PPU ppu)
|
|
{
|
|
ppu.MapReadHandler(0x0000, 0x1FFF, address => _chrROM[_chrBankOffsets[address / 0x1000] + address % 0x1000]);
|
|
ppu.MapWriteHandler(0x0000, 0x1FFF, (address, val) => _chrROM[_chrBankOffsets[address / 0x1000] + address % 0x1000] = val);
|
|
}
|
|
|
|
private void UpdateControl(uint value)
|
|
{
|
|
_control = value;
|
|
|
|
_emulator.Cartridge.MirroringMode = _mirroringModes[value & 0x3];
|
|
|
|
_chrBankingMode = (CHRBankingMode)((value >> 4) & 0x1);
|
|
|
|
var prgMode = (value >> 2) & 0x3;
|
|
// Both 0 and 1 are 32Kb switch
|
|
if (prgMode == 0) prgMode = 1;
|
|
_prgBankingMode = (PRGBankingMode)(prgMode - 1);
|
|
|
|
UpdateCHRBank(1, _chrBanks[1]);
|
|
UpdateCHRBank(0, _chrBanks[0]);
|
|
UpdatePRGBank(_prgBank);
|
|
}
|
|
|
|
private void UpdatePRGBank(uint value)
|
|
{
|
|
_prgBank = value;
|
|
|
|
_prgRAMEnabled = (value & 0x10) == 0;
|
|
value &= 0xF;
|
|
|
|
switch (_prgBankingMode)
|
|
{
|
|
case PRGBankingMode.Switch32Kb:
|
|
value >>= 1;
|
|
value *= 0x4000;
|
|
_prgBankOffsets[0] = value;
|
|
_prgBankOffsets[1] = value + 0x4000;
|
|
break;
|
|
case PRGBankingMode.Switch16KbFixFirst:
|
|
_prgBankOffsets[0] = 0;
|
|
_prgBankOffsets[1] = value * 0x4000;
|
|
break;
|
|
case PRGBankingMode.Switch16KbFixLast:
|
|
_prgBankOffsets[0] = value * 0x4000;
|
|
_prgBankOffsets[1] = _lastBankOffset;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void UpdateCHRBank(uint bank, uint value)
|
|
{
|
|
_chrBanks[bank] = value;
|
|
|
|
// TODO FIXME: I feel like this branch should only be taken
|
|
// when bank == 0, but this breaks Final Fantasy
|
|
// When can banking mode change without UpdateCHRBank being called?
|
|
if (_chrBankingMode == CHRBankingMode.Single)
|
|
{
|
|
value = _chrBanks[0];
|
|
value >>= 1;
|
|
value *= 0x1000;
|
|
_chrBankOffsets[0] = value;
|
|
_chrBankOffsets[1] = value + 0x1000;
|
|
}
|
|
else
|
|
{
|
|
_chrBankOffsets[bank] = value * 0x1000;
|
|
}
|
|
}
|
|
}
|
|
}
|