Essgee.Unity/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs
2025-01-02 17:55:16 +08:00

268 lines
6.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Essgee.Exceptions;
using Essgee.Utilities;
namespace Essgee.Emulation.Cartridges.Nintendo
{
public class MBC3Cartridge : IGameBoyCartridge
{
// https://thomas.spurden.name/gameboy/#mbc3-real-time-clock-rtc
public class RTC
{
public const int NumRegisters = 0x05;
public byte[] BaseRegisters { get; private set; }
public byte[] LatchedRegisters { get; private set; }
public DateTime BaseTime { get; set; }
public bool IsSelected { get; set; }
public byte SelectedRegister { get; set; }
public bool IsLatched { get; set; }
public RTC()
{
BaseRegisters = new byte[NumRegisters];
LatchedRegisters = new byte[NumRegisters];
BaseTime = DateTime.Now;
IsSelected = false;
SelectedRegister = 0;
IsLatched = false;
}
public void FromSaveData(byte[] ramData)
{
var rtcOffset = ramData.Length - 0x30;
// Time
BaseRegisters[0x00] = ramData[rtcOffset + 0];
BaseRegisters[0x01] = ramData[rtcOffset + 4];
BaseRegisters[0x02] = ramData[rtcOffset + 8];
BaseRegisters[0x03] = ramData[rtcOffset + 12];
BaseRegisters[0x04] = ramData[rtcOffset + 16];
// Latched time
LatchedRegisters[0x00] = ramData[rtcOffset + 20];
LatchedRegisters[0x01] = ramData[rtcOffset + 24];
LatchedRegisters[0x02] = ramData[rtcOffset + 28];
LatchedRegisters[0x03] = ramData[rtcOffset + 32];
LatchedRegisters[0x04] = ramData[rtcOffset + 36];
// Timestamp
BaseTime = DateTimeOffset.FromUnixTimeSeconds((long)BitConverter.ToUInt64(ramData, rtcOffset + 40)).UtcDateTime;
}
public byte[] ToSaveData()
{
var appendData = new byte[0x30];
// Time
appendData[0] = BaseRegisters[0x00];
appendData[4] = BaseRegisters[0x01];
appendData[8] = BaseRegisters[0x02];
appendData[12] = BaseRegisters[0x03];
appendData[16] = BaseRegisters[0x04];
// Latched time
appendData[20] = LatchedRegisters[0x00];
appendData[24] = LatchedRegisters[0x01];
appendData[28] = LatchedRegisters[0x02];
appendData[32] = LatchedRegisters[0x03];
appendData[36] = LatchedRegisters[0x04];
// Timestamp
var timestamp = BitConverter.GetBytes(((DateTimeOffset)BaseTime).ToUnixTimeSeconds());
for (var i = 0; i < timestamp.Length; i++) appendData[40 + i] = timestamp[i];
return appendData;
}
public void Update()
{
// GOLD,38695,3000 == 00931
var currentTime = DateTime.Now;
var newTime = currentTime;
if (((BaseRegisters[0x04] >> 6) & 0b1) == 0 && currentTime > BaseTime)
newTime.Add(currentTime - BaseTime);
newTime.AddSeconds(BaseRegisters[0x00]);
newTime.AddMinutes(BaseRegisters[0x01]);
newTime.AddHours(BaseRegisters[0x02]);
newTime.AddDays(BaseRegisters[0x03]);
newTime.AddDays((BaseRegisters[0x04] & 0b1) << 8);
BaseRegisters[0x00] = (byte)newTime.Second;
BaseRegisters[0x01] = (byte)newTime.Minute;
BaseRegisters[0x02] = (byte)newTime.Hour;
BaseRegisters[0x03] = (byte)(newTime.Day & 0xFF);
BaseRegisters[0x04] = (byte)((BaseRegisters[0x04] & 0xFE) | ((newTime.Day >> 8) & 0b1) | ((newTime.Day >> 8) & 0b1) << 7);
BaseTime = currentTime;
}
}
byte[] romData, ramData;
bool hasBattery, hasRTC;
byte romBank, ramBank;
bool ramEnable;
RTC rtc;
public MBC3Cartridge(int romSize, int ramSize)
{
romData = new byte[romSize];
ramData = new byte[ramSize];
hasBattery = false;
hasRTC = false;
romBank = 1;
ramBank = 0;
ramEnable = false;
rtc = new RTC();
}
public void LoadRom(byte[] data)
{
Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length));
}
public void LoadRam(byte[] data)
{
/* Has appended RTC state data? */
if ((data.Length & 0x30) == 0x30) rtc.FromSaveData(data);
Buffer.BlockCopy(data, 0, ramData, 0, Math.Min(data.Length, ramData.Length));
}
public byte[] GetRomData()
{
return romData;
}
public byte[] GetRamData()
{
if (hasRTC)
return ramData.Concat(rtc.ToSaveData()).ToArray();
else
return ramData;
}
public bool IsRamSaveNeeded()
{
return hasBattery;
}
public ushort GetLowerBound()
{
return 0x0000;
}
public ushort GetUpperBound()
{
return 0x7FFF;
}
public void SetCartridgeConfig(bool battery, bool rtc, bool rumble)
{
hasBattery = battery;
hasRTC = rtc;
}
public void Step(int clockCyclesInStep)
{
/* Nothing to do */
}
public byte Read(ushort address)
{
if (address >= 0x0000 && address <= 0x3FFF)
{
return romData[address & 0x3FFF];
}
else if (address >= 0x4000 && address <= 0x7FFF)
{
return romData[(romBank << 14) | (address & 0x3FFF)];
}
else if (address >= 0xA000 && address <= 0xBFFF)
{
if (rtc.IsSelected)
{
if (rtc.IsLatched)
return rtc.LatchedRegisters[rtc.SelectedRegister];
else
return rtc.BaseRegisters[rtc.SelectedRegister];
}
else if (ramEnable)
return ramData[(ramBank << 13) | (address & 0x1FFF)];
else
return 0xFF;
}
else
return 0xFF;
}
public void Write(ushort address, byte value)
{
if (address >= 0x0000 && address <= 0x1FFF)
{
ramEnable = (value & 0x0F) == 0x0A;
}
else if (address >= 0x2000 && address <= 0x3FFF)
{
romBank = (byte)((romBank & 0x80) | (value & 0x7F));
romBank &= (byte)((romData.Length >> 14) - 1);
if (romBank == 0x00) romBank = 0x01;
}
else if (address >= 0x4000 && address <= 0x5FFF)
{
if (value >= 0x00 && value <= 0x07)
{
rtc.IsSelected = false;
ramBank = (byte)(value & 0x07);
}
else if (value >= 0x08 && value <= 0x0C)
{
rtc.IsSelected = true;
rtc.SelectedRegister = (byte)(value - 0x08);
}
}
else if (address >= 0x6000 && address <= 0x7FFF)
{
if (value == 0x00 && rtc.IsLatched)
rtc.IsLatched = false;
else if (value == 0x01 && !rtc.IsLatched)
{
rtc.Update();
for (var i = 0; i < RTC.NumRegisters; i++)
rtc.LatchedRegisters[i] = rtc.BaseRegisters[i];
rtc.IsLatched = true;
}
}
else if (address >= 0xA000 && address <= 0xBFFF)
{
if (rtc.IsSelected)
{
rtc.Update();
rtc.BaseRegisters[rtc.SelectedRegister] = value;
}
else if (ramEnable)
ramData[(ramBank << 13) | (address & 0x1FFF)] = value;
}
}
}
}