268 lines
6.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|