249 lines
6.9 KiB
C#
249 lines
6.9 KiB
C#
using System;
|
|
|
|
using StoicGoose.Common.Utilities;
|
|
using StoicGoose.Core.EEPROMs;
|
|
using StoicGoose.Core.Interfaces;
|
|
|
|
namespace StoicGoose.Core.Cartridges
|
|
{
|
|
public class Cartridge : IComponent
|
|
{
|
|
byte[] rom, sram;
|
|
uint romMask, sramMask;
|
|
|
|
Metadata metadata;
|
|
|
|
/* REG_BANK_xxx */
|
|
byte romBank2, sramBank, romBank0, romBank1;
|
|
|
|
/* REG_EEP_xxx -> EEPROM class */
|
|
EEPROM eeprom = default;
|
|
|
|
/* REG_RTC_xxx -> RTC class */
|
|
RTC rtc = default;
|
|
|
|
public bool IsLoaded => rom?.Length > 0;
|
|
public int SizeInBytes => rom?.Length ?? 0;
|
|
public uint Crc32 { get; private set; } = default;
|
|
|
|
public Metadata Metadata => metadata;
|
|
|
|
public Cartridge()
|
|
{
|
|
rom = Array.Empty<byte>();
|
|
sram = Array.Empty<byte>();
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
romBank2 = 0xFF;
|
|
sramBank = 0xFF;
|
|
romBank0 = 0xFF;
|
|
romBank1 = 0xFF;
|
|
|
|
eeprom?.Reset();
|
|
rtc?.Reset();
|
|
|
|
// HACK: set RTC to current date/time on boot for testing
|
|
rtc?.Program(DateTime.Now);
|
|
}
|
|
|
|
public void Shutdown()
|
|
{
|
|
eeprom?.Shutdown();
|
|
rtc?.Shutdown();
|
|
}
|
|
|
|
public void LoadRom(byte[] data)
|
|
{
|
|
rom = data;
|
|
romMask = (uint)(rom.Length - 1);
|
|
|
|
metadata = new Metadata(rom);
|
|
|
|
if (metadata.SaveSize != 0)
|
|
{
|
|
if (metadata.IsSramSave)
|
|
{
|
|
sram = new byte[metadata.SaveSize];
|
|
sramMask = (uint)(sram.Length - 1);
|
|
}
|
|
else if (metadata.IsEepromSave)
|
|
{
|
|
switch (metadata.SaveType)
|
|
{
|
|
// TODO: verify size/address bits
|
|
case Metadata.SaveTypes.Eeprom1Kbit: eeprom = new EEPROM(metadata.SaveSize, 6); break;
|
|
case Metadata.SaveTypes.Eeprom16Kbit: eeprom = new EEPROM(metadata.SaveSize, 10); break;
|
|
case Metadata.SaveTypes.Eeprom8Kbit: eeprom = new EEPROM(metadata.SaveSize, 9); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (metadata.IsRtcPresent)
|
|
{
|
|
// NOTE: "RTC present" flag is not entirely consistent; ex. Digimon Tamers Battle Spirit has the flag, but does not have an RTC
|
|
rtc = new RTC();
|
|
}
|
|
|
|
Crc32 = Common.Utilities.Crc32.Calculate(rom);
|
|
|
|
Log.WriteEvent(LogSeverity.Information, this, "ROM loaded.");
|
|
Log.WriteLine($"~ {Ansi.Cyan}Cartridge metadata{Ansi.Reset} ~");
|
|
Log.WriteLine($" Publisher ID: {Metadata.PublisherCode}, {Metadata.PublisherName} [0x{Metadata.PublisherId:X2}]");
|
|
Log.WriteLine($" System type: {Metadata.SystemType}");
|
|
Log.WriteLine($" Game ID: 0x{Metadata.GameId:X2}");
|
|
Log.WriteLine($" Calculated ID string: {Metadata.GameIdString}");
|
|
Log.WriteLine($" Game revision: 0x{Metadata.GameRevision:X2}");
|
|
Log.WriteLine($" ROM size: {Metadata.RomSize} [0x{(byte)Metadata.RomSize:X2}]");
|
|
Log.WriteLine($" Save type/size: {Metadata.SaveType}/{Metadata.SaveSize} [0x{(byte)Metadata.SaveType:X2}]");
|
|
Log.WriteLine($" Misc flags: 0x{Metadata.MiscFlags:X2}");
|
|
Log.WriteLine($" Orientation: {Metadata.Orientation}");
|
|
Log.WriteLine($" ROM bus width: {Metadata.RomBusWidth}");
|
|
Log.WriteLine($" ROM access speed: {Metadata.RomAccessSpeed}");
|
|
Log.WriteLine($" RTC present: {Metadata.IsRtcPresent} [0x{Metadata.RtcPresentFlag:X2}]");
|
|
Log.WriteLine($" Checksum (from metadata): 0x{Metadata.Checksum:X4}");
|
|
Log.WriteLine($" Checksum (calculated): 0x{Metadata.CalculatedChecksum:X4}");
|
|
Log.WriteLine($" Checksum is {(metadata.IsChecksumValid ? $"{Ansi.Green}valid" : $"{Ansi.Red}invalid")}{Ansi.Reset}!");
|
|
|
|
if (metadata.PublisherId == 0x01 && metadata.GameId == 0x27)
|
|
{
|
|
// HACK: Meitantei Conan - Nishi no Meitantei Saidai no Kiki, prevent crash on startup (see TODO in V30MZ, prefetching)
|
|
rom[0xFFFE8] = 0xEA;
|
|
rom[0xFFFE9] = 0x00;
|
|
rom[0xFFFEA] = 0x00;
|
|
rom[0xFFFEB] = 0x00;
|
|
rom[0xFFFEC] = 0x20;
|
|
Log.WriteLine($"~ {Ansi.Red}Conan prefetch hack enabled{Ansi.Reset} ~");
|
|
}
|
|
}
|
|
|
|
public void LoadSram(byte[] data)
|
|
{
|
|
if (data.Length != sram.Length) throw new Exception("Sram size mismatch");
|
|
Buffer.BlockCopy(data, 0, sram, 0, data.Length);
|
|
}
|
|
|
|
public void LoadEeprom(byte[] data)
|
|
{
|
|
eeprom?.LoadContents(data);
|
|
}
|
|
|
|
public byte[] GetSram()
|
|
{
|
|
return sram.Clone() as byte[];
|
|
}
|
|
|
|
public byte[] GetEeprom()
|
|
{
|
|
return eeprom?.GetContents().Clone() as byte[];
|
|
}
|
|
|
|
public bool Step(int clockCyclesInStep)
|
|
{
|
|
return rtc != null && rtc.Step(clockCyclesInStep);
|
|
}
|
|
|
|
public byte ReadMemory(uint address)
|
|
{
|
|
return address switch
|
|
{
|
|
/* SRAM */
|
|
var n when n >= 0x010000 && n < 0x020000 && sram.Length != 0 => sram[((uint)(sramBank << 16) | (address & 0x0FFFF)) & sramMask],
|
|
/* ROM bank 0 */
|
|
var n when n >= 0x020000 && n < 0x030000 && rom.Length != 0 => rom[((uint)(romBank0 << 16) | (address & 0x0FFFF)) & romMask],
|
|
/* ROM bank 1 */
|
|
var n when n >= 0x030000 && n < 0x040000 && rom.Length != 0 => rom[((uint)(romBank1 << 16) | (address & 0x0FFFF)) & romMask],
|
|
/* ROM bank 2 */
|
|
var n when n >= 0x040000 && n < 0x100000 && rom.Length != 0 => rom[((uint)(romBank2 << 20) | (address & 0xFFFFF)) & romMask],
|
|
/* Unmapped */
|
|
_ => 0x90,
|
|
};
|
|
}
|
|
|
|
public void WriteMemory(uint address, byte value)
|
|
{
|
|
/* SRAM */
|
|
if (address >= 0x010000 && address < 0x020000 && sram.Length != 0)
|
|
sram[((uint)(sramBank << 16) | (address & 0x0FFFF)) & sramMask] = value;
|
|
}
|
|
|
|
public byte ReadPort(ushort port)
|
|
{
|
|
return port switch
|
|
{
|
|
/* REG_BANK_ROM2 */
|
|
0xC0 => romBank2,
|
|
/* REG_BANK_SRAM */
|
|
0xC1 => sramBank,
|
|
/* REG_BANK_ROM0 */
|
|
0xC2 => romBank0,
|
|
/* REG_BANK_ROM1 */
|
|
0xC3 => romBank1,
|
|
/* REG_EEP_DATA (low) */
|
|
0xC4 => eeprom != null ? eeprom.ReadPort((byte)(port - 0xC4)) : (byte)0x90,
|
|
/* REG_EEP_DATA (high) */
|
|
0xC5 => eeprom != null ? eeprom.ReadPort((byte)(port - 0xC4)) : (byte)0x90,
|
|
/* REG_EEP_ADDR (low) */
|
|
0xC6 => eeprom != null ? eeprom.ReadPort((byte)(port - 0xC4)) : (byte)0x90,
|
|
/* REG_EEP_ADDR (high) */
|
|
0xC7 => eeprom != null ? eeprom.ReadPort((byte)(port - 0xC4)) : (byte)0x90,
|
|
/* REG_EEP_STATUS (read) */
|
|
0xC8 => eeprom != null ? eeprom.ReadPort((byte)(port - 0xC4)) : (byte)0x90,
|
|
/* REG_RTC_STATUS (read) */
|
|
0xCA => rtc != null ? rtc.ReadPort((byte)(port - 0xCA)) : (byte)0x90,
|
|
/* REG_RTC_DATA */
|
|
0xCB => rtc != null ? rtc.ReadPort((byte)(port - 0xCA)) : (byte)0x90,
|
|
/* Unmapped */
|
|
_ => 0x90,
|
|
};
|
|
}
|
|
|
|
public void WritePort(ushort port, byte value)
|
|
{
|
|
switch (port)
|
|
{
|
|
case 0xC0:
|
|
/* REG_BANK_ROM2 */
|
|
romBank2 = value;
|
|
break;
|
|
|
|
case 0xC1:
|
|
/* REG_BANK_SRAM */
|
|
sramBank = value;
|
|
break;
|
|
|
|
case 0xC2:
|
|
/* REG_BANK_ROM0 */
|
|
romBank0 = value;
|
|
break;
|
|
|
|
case 0xC3:
|
|
/* REG_BANK_ROM1 */
|
|
romBank1 = value;
|
|
break;
|
|
|
|
case 0xC4:
|
|
case 0xC5:
|
|
case 0xC6:
|
|
case 0xC7:
|
|
case 0xC8:
|
|
/* REG_EEP_DATA (low) */
|
|
/* REG_EEP_DATA (high) */
|
|
/* REG_EEP_ADDR (low) */
|
|
/* REG_EEP_ADDR (high) */
|
|
/* REG_EEP_CMD (write) */
|
|
eeprom?.WritePort((byte)(port - 0xC4), value);
|
|
break;
|
|
|
|
case 0xCA:
|
|
case 0xCB:
|
|
/* REG_RTC_CMD (write) */
|
|
/* REG_RTC_DATA */
|
|
rtc?.WritePort((byte)(port - 0xCA), value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|