feat(cartridge): add MBC1/MBC3/MBC5 mapper banking
This commit is contained in:
1 parent
8558eefe08
commit
ab8f98cc3d
1 file changed
+289
@@ -0,0 +1,289 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace GameBoy.Cartridge
|
||||||
|
{
|
||||||
|
public sealed class NoMbc : ICartridgeMapper
|
||||||
|
{
|
||||||
|
private readonly Cartridge cart;
|
||||||
|
|
||||||
|
public NoMbc(Cartridge cart)
|
||||||
|
{
|
||||||
|
this.cart = cart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte Read(ushort address)
|
||||||
|
{
|
||||||
|
if (address < 0x8000)
|
||||||
|
return address < cart.Rom.Length ? cart.Rom[address] : (byte)0xFF;
|
||||||
|
if (address >= 0xA000 && address < 0xC000 && cart.Ram.Length > 0)
|
||||||
|
return cart.Ram[address - 0xA000];
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(ushort address, byte value)
|
||||||
|
{
|
||||||
|
if (address >= 0xA000 && address < 0xC000 && cart.Ram.Length > 0)
|
||||||
|
cart.Ram[address - 0xA000] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveState(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadState(BinaryReader reader)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Mbc1 : ICartridgeMapper
|
||||||
|
{
|
||||||
|
private readonly Cartridge cart;
|
||||||
|
private readonly int romBankCount;
|
||||||
|
private readonly int ramBankCount;
|
||||||
|
|
||||||
|
private int bankLow = 1;
|
||||||
|
private int bankHigh;
|
||||||
|
private int mode;
|
||||||
|
private bool ramEnabled;
|
||||||
|
|
||||||
|
public Mbc1(Cartridge cart)
|
||||||
|
{
|
||||||
|
this.cart = cart;
|
||||||
|
romBankCount = cart.Rom.Length / 0x4000;
|
||||||
|
ramBankCount = cart.Ram.Length > 0 ? cart.Ram.Length / 0x2000 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte Read(ushort address)
|
||||||
|
{
|
||||||
|
if (address < 0x4000)
|
||||||
|
{
|
||||||
|
int bank = mode == 1 ? (bankHigh << 5) : 0;
|
||||||
|
return cart.Rom[MapRom(bank, address)];
|
||||||
|
}
|
||||||
|
if (address < 0x8000)
|
||||||
|
{
|
||||||
|
int low = bankLow == 0 ? 1 : bankLow;
|
||||||
|
int bank = (bankHigh << 5) | low;
|
||||||
|
return cart.Rom[MapRom(bank, address - 0x4000)];
|
||||||
|
}
|
||||||
|
if (address >= 0xA000 && address < 0xC000)
|
||||||
|
{
|
||||||
|
if (!ramEnabled || ramBankCount == 0)
|
||||||
|
return 0xFF;
|
||||||
|
int bank = mode == 1 ? bankHigh % ramBankCount : 0;
|
||||||
|
return cart.Ram[bank * 0x2000 + (address - 0xA000)];
|
||||||
|
}
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(ushort address, byte value)
|
||||||
|
{
|
||||||
|
if (address < 0x2000)
|
||||||
|
ramEnabled = (value & 0x0F) == 0x0A;
|
||||||
|
else if (address < 0x4000)
|
||||||
|
bankLow = value & 0x1F;
|
||||||
|
else if (address < 0x6000)
|
||||||
|
bankHigh = value & 0x03;
|
||||||
|
else if (address < 0x8000)
|
||||||
|
mode = value & 0x01;
|
||||||
|
else if (address >= 0xA000 && address < 0xC000)
|
||||||
|
{
|
||||||
|
if (!ramEnabled || ramBankCount == 0)
|
||||||
|
return;
|
||||||
|
int bank = mode == 1 ? bankHigh % ramBankCount : 0;
|
||||||
|
cart.Ram[bank * 0x2000 + (address - 0xA000)] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int MapRom(int bank, int offset)
|
||||||
|
{
|
||||||
|
int wrapped = bank % romBankCount;
|
||||||
|
return wrapped * 0x4000 + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveState(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(bankLow);
|
||||||
|
writer.Write(bankHigh);
|
||||||
|
writer.Write(mode);
|
||||||
|
writer.Write(ramEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadState(BinaryReader reader)
|
||||||
|
{
|
||||||
|
bankLow = reader.ReadInt32();
|
||||||
|
bankHigh = reader.ReadInt32();
|
||||||
|
mode = reader.ReadInt32();
|
||||||
|
ramEnabled = reader.ReadBoolean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Mbc3 : ICartridgeMapper
|
||||||
|
{
|
||||||
|
private readonly Cartridge cart;
|
||||||
|
private readonly int romBankCount;
|
||||||
|
private readonly int ramBankCount;
|
||||||
|
private readonly Mbc3Rtc? rtc;
|
||||||
|
|
||||||
|
private int romBank = 1;
|
||||||
|
private int ramBank;
|
||||||
|
private int rtcSelect = -1;
|
||||||
|
private bool ramEnabled;
|
||||||
|
|
||||||
|
public Mbc3(Cartridge cart, bool hasTimer)
|
||||||
|
{
|
||||||
|
this.cart = cart;
|
||||||
|
romBankCount = cart.Rom.Length / 0x4000;
|
||||||
|
ramBankCount = cart.Ram.Length > 0 ? cart.Ram.Length / 0x2000 : 0;
|
||||||
|
rtc = hasTimer ? new Mbc3Rtc() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mbc3Rtc? Rtc => rtc;
|
||||||
|
|
||||||
|
public byte Read(ushort address)
|
||||||
|
{
|
||||||
|
if (address < 0x4000)
|
||||||
|
return cart.Rom[address];
|
||||||
|
if (address < 0x8000)
|
||||||
|
{
|
||||||
|
int bank = (romBank == 0 ? 1 : romBank) % romBankCount;
|
||||||
|
return cart.Rom[bank * 0x4000 + (address - 0x4000)];
|
||||||
|
}
|
||||||
|
if (address >= 0xA000 && address < 0xC000)
|
||||||
|
{
|
||||||
|
if (!ramEnabled)
|
||||||
|
return 0xFF;
|
||||||
|
if (rtcSelect >= 0 && rtc != null)
|
||||||
|
return rtc.ReadRegister(rtcSelect);
|
||||||
|
if (ramBank < ramBankCount)
|
||||||
|
return cart.Ram[ramBank * 0x2000 + (address - 0xA000)];
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(ushort address, byte value)
|
||||||
|
{
|
||||||
|
if (address < 0x2000)
|
||||||
|
ramEnabled = (value & 0x0F) == 0x0A;
|
||||||
|
else if (address < 0x4000)
|
||||||
|
romBank = value & 0x7F;
|
||||||
|
else if (address < 0x6000)
|
||||||
|
{
|
||||||
|
if (value <= 0x03)
|
||||||
|
{
|
||||||
|
ramBank = value;
|
||||||
|
rtcSelect = -1;
|
||||||
|
}
|
||||||
|
else if (value >= 0x08 && value <= 0x0C)
|
||||||
|
rtcSelect = value;
|
||||||
|
}
|
||||||
|
else if (address < 0x8000)
|
||||||
|
rtc?.WriteLatch(value);
|
||||||
|
else if (address >= 0xA000 && address < 0xC000)
|
||||||
|
{
|
||||||
|
if (!ramEnabled)
|
||||||
|
return;
|
||||||
|
if (rtcSelect >= 0 && rtc != null)
|
||||||
|
{
|
||||||
|
rtc.WriteRegister(rtcSelect, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ramBank < ramBankCount)
|
||||||
|
cart.Ram[ramBank * 0x2000 + (address - 0xA000)] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveState(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(romBank);
|
||||||
|
writer.Write(ramBank);
|
||||||
|
writer.Write(rtcSelect);
|
||||||
|
writer.Write(ramEnabled);
|
||||||
|
writer.Write(rtc != null);
|
||||||
|
rtc?.SaveState(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadState(BinaryReader reader)
|
||||||
|
{
|
||||||
|
romBank = reader.ReadInt32();
|
||||||
|
ramBank = reader.ReadInt32();
|
||||||
|
rtcSelect = reader.ReadInt32();
|
||||||
|
ramEnabled = reader.ReadBoolean();
|
||||||
|
bool hasRtc = reader.ReadBoolean();
|
||||||
|
if (hasRtc)
|
||||||
|
rtc?.LoadState(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Mbc5 : ICartridgeMapper
|
||||||
|
{
|
||||||
|
private readonly Cartridge cart;
|
||||||
|
private readonly int romBankCount;
|
||||||
|
private readonly int ramBankCount;
|
||||||
|
|
||||||
|
private int romBankLow = 1;
|
||||||
|
private int romBankHigh;
|
||||||
|
private int ramBank;
|
||||||
|
private bool ramEnabled;
|
||||||
|
|
||||||
|
public Mbc5(Cartridge cart)
|
||||||
|
{
|
||||||
|
this.cart = cart;
|
||||||
|
romBankCount = cart.Rom.Length / 0x4000;
|
||||||
|
ramBankCount = cart.Ram.Length > 0 ? cart.Ram.Length / 0x2000 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte Read(ushort address)
|
||||||
|
{
|
||||||
|
if (address < 0x4000)
|
||||||
|
return cart.Rom[address];
|
||||||
|
if (address < 0x8000)
|
||||||
|
{
|
||||||
|
int bank = ((romBankHigh << 8) | romBankLow) % romBankCount;
|
||||||
|
return cart.Rom[bank * 0x4000 + (address - 0x4000)];
|
||||||
|
}
|
||||||
|
if (address >= 0xA000 && address < 0xC000)
|
||||||
|
{
|
||||||
|
if (!ramEnabled || ramBank >= ramBankCount)
|
||||||
|
return 0xFF;
|
||||||
|
return cart.Ram[ramBank * 0x2000 + (address - 0xA000)];
|
||||||
|
}
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(ushort address, byte value)
|
||||||
|
{
|
||||||
|
if (address < 0x2000)
|
||||||
|
ramEnabled = (value & 0x0F) == 0x0A;
|
||||||
|
else if (address < 0x3000)
|
||||||
|
romBankLow = value;
|
||||||
|
else if (address < 0x4000)
|
||||||
|
romBankHigh = value & 0x01;
|
||||||
|
else if (address < 0x6000)
|
||||||
|
ramBank = value & 0x0F;
|
||||||
|
else if (address >= 0xA000 && address < 0xC000)
|
||||||
|
{
|
||||||
|
if (!ramEnabled || ramBank >= ramBankCount)
|
||||||
|
return;
|
||||||
|
cart.Ram[ramBank * 0x2000 + (address - 0xA000)] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveState(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(romBankLow);
|
||||||
|
writer.Write(romBankHigh);
|
||||||
|
writer.Write(ramBank);
|
||||||
|
writer.Write(ramEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadState(BinaryReader reader)
|
||||||
|
{
|
||||||
|
romBankLow = reader.ReadInt32();
|
||||||
|
romBankHigh = reader.ReadInt32();
|
||||||
|
ramBank = reader.ReadInt32();
|
||||||
|
ramEnabled = reader.ReadBoolean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user