566 lines
16 KiB
C#
566 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using BizHawk.Emulation.CPUs.H6280;
|
|
using BizHawk.Emulation.Sound;
|
|
using BizHawk.DiscSystem;
|
|
|
|
namespace BizHawk.Emulation.Consoles.TurboGrafx
|
|
{
|
|
public enum NecSystemType { TurboGrafx, TurboCD, SuperGrafx }
|
|
|
|
[CoreVersion("1.2.1", FriendlyName = "TurboHawk16")]
|
|
public sealed partial class PCEngine : IEmulator
|
|
{
|
|
// ROM
|
|
public byte[] RomData;
|
|
public int RomLength;
|
|
Disc disc;
|
|
|
|
// Machine
|
|
public NecSystemType Type;
|
|
public HuC6280 Cpu;
|
|
public VDC VDC1, VDC2;
|
|
public VCE VCE;
|
|
public VPC VPC;
|
|
public ScsiCDBus SCSI;
|
|
public ADPCM ADPCM;
|
|
|
|
public HuC6280PSG PSG;
|
|
public CDAudio CDAudio;
|
|
public SoundMixer SoundMixer;
|
|
public MetaspuSoundProvider SoundSynchronizer;
|
|
|
|
bool TurboGrafx { get { return Type == NecSystemType.TurboGrafx; } }
|
|
bool SuperGrafx { get { return Type == NecSystemType.SuperGrafx; } }
|
|
bool TurboCD { get { return Type == NecSystemType.TurboCD; } }
|
|
|
|
// BRAM
|
|
bool BramEnabled = false;
|
|
bool BramLocked = true;
|
|
byte[] BRAM;
|
|
|
|
// Memory system
|
|
public byte[] Ram; // PCE= 8K base ram, SGX= 64k base ram
|
|
public byte[] CDRam; // TurboCD extra 64k of ram
|
|
public byte[] SuperRam; // Super System Card 192K of additional RAM
|
|
public byte[] ArcadeRam; // Arcade Card 2048K of additional RAM
|
|
|
|
private string systemid = "PCE";
|
|
|
|
// 21,477,270 Machine clocks / sec
|
|
// 7,159,090 Cpu cycles / sec
|
|
|
|
public PCEngine(GameInfo game, byte[] rom)
|
|
{
|
|
CoreOutputComm = new CoreOutputComm();
|
|
|
|
switch (game.System)
|
|
{
|
|
case "PCE":
|
|
systemid = "PCE";
|
|
Type = NecSystemType.TurboGrafx;
|
|
break;
|
|
case "SGX":
|
|
systemid = "SGX";
|
|
Type = NecSystemType.SuperGrafx;
|
|
break;
|
|
}
|
|
Init(game, rom);
|
|
}
|
|
|
|
public PCEngine(GameInfo game, Disc disc, byte[] rom)
|
|
{
|
|
systemid = "PCECD";
|
|
CoreOutputComm = new CoreOutputComm();
|
|
Type = NecSystemType.TurboCD;
|
|
this.disc = disc;
|
|
Init(game, rom);
|
|
}
|
|
|
|
void Init(GameInfo game, byte[] rom)
|
|
{
|
|
Controller = NullController.GetNullController();
|
|
Cpu = new HuC6280();
|
|
VCE = new VCE();
|
|
VDC1 = new VDC(this, Cpu, VCE);
|
|
PSG = new HuC6280PSG();
|
|
SCSI = new ScsiCDBus(this, disc);
|
|
|
|
if (TurboGrafx)
|
|
{
|
|
Ram = new byte[0x2000];
|
|
Cpu.ReadMemory21 = ReadMemory;
|
|
Cpu.WriteMemory21 = WriteMemory;
|
|
Cpu.WriteVDC = VDC1.WriteVDC;
|
|
soundProvider = PSG;
|
|
CDAudio = new CDAudio(null, 0);
|
|
}
|
|
|
|
else if (SuperGrafx)
|
|
{
|
|
VDC2 = new VDC(this, Cpu, VCE);
|
|
VPC = new VPC(this, VDC1, VDC2, VCE, Cpu);
|
|
Ram = new byte[0x8000];
|
|
Cpu.ReadMemory21 = ReadMemorySGX;
|
|
Cpu.WriteMemory21 = WriteMemorySGX;
|
|
Cpu.WriteVDC = VDC1.WriteVDC;
|
|
soundProvider = PSG;
|
|
CDAudio = new CDAudio(null, 0);
|
|
}
|
|
|
|
else if (TurboCD)
|
|
{
|
|
Ram = new byte[0x2000];
|
|
CDRam = new byte[0x10000];
|
|
ADPCM = new ADPCM(this, SCSI);
|
|
Cpu.ReadMemory21 = ReadMemoryCD;
|
|
Cpu.WriteMemory21 = WriteMemoryCD;
|
|
Cpu.WriteVDC = VDC1.WriteVDC;
|
|
CDAudio = new CDAudio(disc);
|
|
SetCDAudioCallback();
|
|
PSG.MaxVolume = short.MaxValue * 3 / 4;
|
|
SoundMixer = new SoundMixer(PSG, CDAudio, ADPCM);
|
|
SoundSynchronizer = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V);
|
|
soundProvider = SoundSynchronizer;
|
|
Cpu.ThinkAction = (cycles) => { SCSI.Think(); ADPCM.Think(cycles); };
|
|
}
|
|
|
|
if (rom.Length == 0x60000)
|
|
{
|
|
// 384k roms require special loading code. Why ;_;
|
|
// In memory, 384k roms look like [1st 256k][Then full 384k]
|
|
RomData = new byte[0xA0000];
|
|
var origRom = rom;
|
|
for (int i = 0; i < 0x40000; i++)
|
|
RomData[i] = origRom[i];
|
|
for (int i = 0; i < 0x60000; i++)
|
|
RomData[i + 0x40000] = origRom[i];
|
|
RomLength = RomData.Length;
|
|
}
|
|
else if (rom.Length > 1024 * 1024)
|
|
{
|
|
// If the rom is bigger than 1 megabyte, switch to Street Fighter 2 mapper
|
|
Cpu.ReadMemory21 = ReadMemorySF2;
|
|
Cpu.WriteMemory21 = WriteMemorySF2;
|
|
RomData = rom;
|
|
RomLength = RomData.Length;
|
|
}
|
|
else
|
|
{
|
|
// normal rom.
|
|
RomData = rom;
|
|
RomLength = RomData.Length;
|
|
}
|
|
|
|
if (game["BRAM"] || Type == NecSystemType.TurboCD)
|
|
{
|
|
BramEnabled = true;
|
|
BRAM = new byte[2048];
|
|
|
|
// pre-format BRAM. damn are we helpful.
|
|
BRAM[0] = 0x48; BRAM[1] = 0x55; BRAM[2] = 0x42; BRAM[3] = 0x4D;
|
|
BRAM[4] = 0x00; BRAM[5] = 0x88; BRAM[6] = 0x10; BRAM[7] = 0x80;
|
|
}
|
|
|
|
if (game["SuperSysCard"])
|
|
SuperRam = new byte[0x30000];
|
|
|
|
if (game["ArcadeCard"])
|
|
{
|
|
ArcadeRam = new byte[0x200000];
|
|
ArcadeCard = true;
|
|
ArcadeCardRewindHack = game["ArcadeRewindHack"];
|
|
for (int i = 0; i < 4; i++)
|
|
ArcadePage[i] = new ArcadeCardPage();
|
|
}
|
|
|
|
if (game["PopulousSRAM"])
|
|
{
|
|
PopulousRAM = new byte[0x8000];
|
|
Cpu.ReadMemory21 = ReadMemoryPopulous;
|
|
Cpu.WriteMemory21 = WriteMemoryPopulous;
|
|
}
|
|
|
|
if (game["ForceSpriteLimit"] || game.NotInDatabase)
|
|
{
|
|
VDC1.PerformSpriteLimit = true;
|
|
if (VDC2 != null)
|
|
VDC2.PerformSpriteLimit = true;
|
|
}
|
|
|
|
if (game["CdVol"])
|
|
CDAudio.MaxVolume = int.Parse(game.OptionValue("CdVol"));
|
|
if (game["PsgVol"])
|
|
PSG.MaxVolume = int.Parse(game.OptionValue("PsgVol"));
|
|
if (game["AdpcmVol"])
|
|
ADPCM.MaxVolume = int.Parse(game.OptionValue("AdpcmVol"));
|
|
if (game["EqualizeVolumes"] || (game.NotInDatabase && TurboCD))
|
|
SoundMixer.EqualizeVolumes();
|
|
|
|
// Ok, yes, HBlankPeriod's only purpose is game-specific hax.
|
|
// 1) At least they're not coded directly into the emulator, but instead data-driven.
|
|
// 2) The games which have custom HBlankPeriods work without it, the override only
|
|
// serves to clean up minor gfx anomalies.
|
|
// 3) There's no point in haxing the timing with incorrect values in an attempt to avoid this.
|
|
// The proper fix is cycle-accurate/bus-accurate timing. That isn't coming to the C#
|
|
// version of this core. Let's just acknolwedge that the timing is imperfect and fix
|
|
// it in the least intrusive and most honest way we can.
|
|
|
|
if (game["HBlankPeriod"])
|
|
VDC1.HBlankCycles = int.Parse(game.OptionValue("HBlankPeriod"));
|
|
|
|
// This is also a hack. Proper multi-res/TV emulation will be a native-code core feature.
|
|
|
|
if (game["MultiResHack"])
|
|
{
|
|
VDC1.MultiResHack = int.Parse(game.OptionValue("MultiResHack"));
|
|
}
|
|
|
|
Cpu.ResetPC();
|
|
SetupMemoryDomains();
|
|
}
|
|
|
|
int _lagcount = 0;
|
|
bool lagged = true;
|
|
bool islag = false;
|
|
public int Frame { get; set; }
|
|
public int LagCount { get { return _lagcount; } set { _lagcount = value; } }
|
|
public bool IsLagFrame { get { return islag; } }
|
|
|
|
public void ResetFrameCounter()
|
|
{
|
|
// this should just be a public setter instead of a new method.
|
|
Frame = 0;
|
|
}
|
|
|
|
public void FrameAdvance(bool render)
|
|
{
|
|
lagged = true;
|
|
Controller.UpdateControls(Frame++);
|
|
|
|
PSG.BeginFrame(Cpu.TotalExecutedCycles);
|
|
|
|
if (SuperGrafx)
|
|
VPC.ExecFrame(render);
|
|
else
|
|
VDC1.ExecFrame(render);
|
|
|
|
PSG.EndFrame(Cpu.TotalExecutedCycles);
|
|
if (TurboCD)
|
|
SoundSynchronizer.PullSamples(SoundMixer);
|
|
|
|
if (lagged)
|
|
{
|
|
_lagcount++;
|
|
islag = true;
|
|
}
|
|
else
|
|
islag = false;
|
|
}
|
|
|
|
public CoreInputComm CoreInputComm { get; set; }
|
|
public CoreOutputComm CoreOutputComm { get; private set; }
|
|
|
|
public IVideoProvider VideoProvider
|
|
{
|
|
get { return (IVideoProvider)VPC ?? VDC1; }
|
|
}
|
|
|
|
ISoundProvider soundProvider;
|
|
public ISoundProvider SoundProvider
|
|
{
|
|
get { return soundProvider; }
|
|
}
|
|
|
|
public string SystemId { get { return systemid; } }
|
|
public string Region { get; set; }
|
|
public bool DeterministicEmulation { get; set; }
|
|
|
|
public byte[] SaveRam
|
|
{
|
|
get { return BRAM; }
|
|
}
|
|
|
|
public bool SaveRamModified { get; set; }
|
|
|
|
public void SaveStateText(TextWriter writer)
|
|
{
|
|
writer.WriteLine("[PCEngine]");
|
|
writer.Write("RAM ");
|
|
Ram.SaveAsHex(writer);
|
|
if (PopulousRAM != null)
|
|
{
|
|
writer.Write("PopulousRAM ");
|
|
PopulousRAM.SaveAsHex(writer);
|
|
}
|
|
writer.WriteLine("Frame {0}", Frame);
|
|
writer.WriteLine("Lag {0}", _lagcount);
|
|
if (Cpu.ReadMemory21 == ReadMemorySF2)
|
|
writer.WriteLine("SF2MapperLatch " + SF2MapperLatch);
|
|
writer.WriteLine("IOBuffer {0:X2}", IOBuffer);
|
|
writer.Write("CdIoPorts "); CdIoPorts.SaveAsHex(writer);
|
|
writer.WriteLine();
|
|
|
|
if (SuperGrafx)
|
|
{
|
|
Cpu.SaveStateText(writer);
|
|
VPC.SaveStateText(writer);
|
|
VCE.SaveStateText(writer);
|
|
VDC1.SaveStateText(writer, 1);
|
|
VDC2.SaveStateText(writer, 2);
|
|
PSG.SaveStateText(writer);
|
|
}
|
|
else
|
|
{
|
|
Cpu.SaveStateText(writer);
|
|
VCE.SaveStateText(writer);
|
|
VDC1.SaveStateText(writer, 1);
|
|
PSG.SaveStateText(writer);
|
|
}
|
|
if (TurboCD)
|
|
{
|
|
writer.Write("CDRAM "); CDRam.SaveAsHex(writer);
|
|
if (SuperRam != null)
|
|
{ writer.Write("SuperRAM "); SuperRam.SaveAsHex(writer); }
|
|
|
|
writer.WriteLine();
|
|
SCSI.SaveStateText(writer);
|
|
CDAudio.SaveStateText(writer);
|
|
ADPCM.SaveStateText(writer);
|
|
}
|
|
if (ArcadeCard)
|
|
SaveArcadeCardText(writer);
|
|
|
|
writer.WriteLine("[/PCEngine]");
|
|
}
|
|
|
|
public void LoadStateText(TextReader reader)
|
|
{
|
|
while (true)
|
|
{
|
|
string[] args = reader.ReadLine().Split(' ');
|
|
if (args[0].Trim() == "") continue;
|
|
if (args[0] == "[PCEngine]") continue;
|
|
if (args[0] == "[/PCEngine]") break;
|
|
if (args[0] == "Frame")
|
|
Frame = int.Parse(args[1]);
|
|
else if (args[0] == "Lag")
|
|
_lagcount = int.Parse(args[1]);
|
|
else if (args[0] == "SF2MapperLatch")
|
|
SF2MapperLatch = byte.Parse(args[1]);
|
|
else if (args[0] == "IOBuffer")
|
|
IOBuffer = byte.Parse(args[1], NumberStyles.HexNumber);
|
|
else if (args[0] == "CdIoPorts")
|
|
CdIoPorts.ReadFromHex(args[1]);
|
|
else if (args[0] == "RAM")
|
|
Ram.ReadFromHex(args[1]);
|
|
else if (args[0] == "CDRAM")
|
|
CDRam.ReadFromHex(args[1]);
|
|
else if (args[0] == "SuperRAM")
|
|
SuperRam.ReadFromHex(args[1]);
|
|
else if (args[0] == "PopulousRAM" && PopulousRAM != null)
|
|
PopulousRAM.ReadFromHex(args[1]);
|
|
else if (args[0] == "[HuC6280]")
|
|
Cpu.LoadStateText(reader);
|
|
else if (args[0] == "[PSG]")
|
|
PSG.LoadStateText(reader);
|
|
else if (args[0] == "[VCE]")
|
|
VCE.LoadStateText(reader);
|
|
else if (args[0] == "[VPC]")
|
|
VPC.LoadStateText(reader);
|
|
else if (args[0] == "[VDC1]")
|
|
VDC1.LoadStateText(reader, 1);
|
|
else if (args[0] == "[VDC2]")
|
|
VDC2.LoadStateText(reader, 2);
|
|
else if (args[0] == "[SCSI]")
|
|
SCSI.LoadStateText(reader);
|
|
else if (args[0] == "[CDAudio]")
|
|
CDAudio.LoadStateText(reader);
|
|
else if (args[0] == "[ADPCM]")
|
|
ADPCM.LoadStateText(reader);
|
|
else if (args[0] == "[ArcadeCard]")
|
|
LoadArcadeCardText(reader);
|
|
else
|
|
Console.WriteLine("Skipping unrecognized identifier " + args[0]);
|
|
}
|
|
}
|
|
|
|
public void SaveStateBinary(BinaryWriter writer)
|
|
{
|
|
if (SuperGrafx == false)
|
|
{
|
|
writer.Write(Ram);
|
|
writer.Write(CdIoPorts);
|
|
RefreshIRQ2();
|
|
if (BRAM != null)
|
|
writer.Write(BRAM);
|
|
if (PopulousRAM != null)
|
|
writer.Write(PopulousRAM);
|
|
if (SuperRam != null)
|
|
writer.Write(SuperRam);
|
|
if (TurboCD)
|
|
{
|
|
writer.Write(CDRam);
|
|
ADPCM.SaveStateBinary(writer);
|
|
CDAudio.SaveStateBinary(writer);
|
|
SCSI.SaveStateBinary(writer);
|
|
}
|
|
if (ArcadeCard)
|
|
SaveArcadeCardBinary(writer);
|
|
writer.Write(Frame);
|
|
writer.Write(_lagcount);
|
|
writer.Write(SF2MapperLatch);
|
|
writer.Write(IOBuffer);
|
|
Cpu.SaveStateBinary(writer);
|
|
VCE.SaveStateBinary(writer);
|
|
VDC1.SaveStateBinary(writer);
|
|
PSG.SaveStateBinary(writer);
|
|
}
|
|
else
|
|
{
|
|
writer.Write(Ram);
|
|
writer.Write(Frame);
|
|
writer.Write(_lagcount);
|
|
writer.Write(IOBuffer);
|
|
Cpu.SaveStateBinary(writer);
|
|
VCE.SaveStateBinary(writer);
|
|
VPC.SaveStateBinary(writer);
|
|
VDC1.SaveStateBinary(writer);
|
|
VDC2.SaveStateBinary(writer);
|
|
PSG.SaveStateBinary(writer);
|
|
}
|
|
}
|
|
|
|
public void LoadStateBinary(BinaryReader reader)
|
|
{
|
|
if (SuperGrafx == false)
|
|
{
|
|
Ram = reader.ReadBytes(0x2000);
|
|
CdIoPorts = reader.ReadBytes(16);
|
|
if (BRAM != null)
|
|
BRAM = reader.ReadBytes(0x800);
|
|
if (PopulousRAM != null)
|
|
PopulousRAM = reader.ReadBytes(0x8000);
|
|
if (SuperRam != null)
|
|
SuperRam = reader.ReadBytes(0x30000);
|
|
if (TurboCD)
|
|
{
|
|
CDRam = reader.ReadBytes(0x10000);
|
|
ADPCM.LoadStateBinary(reader);
|
|
CDAudio.LoadStateBinary(reader);
|
|
SCSI.LoadStateBinary(reader);
|
|
}
|
|
if (ArcadeCard)
|
|
LoadArcadeCardBinary(reader);
|
|
Frame = reader.ReadInt32();
|
|
_lagcount = reader.ReadInt32();
|
|
SF2MapperLatch = reader.ReadByte();
|
|
IOBuffer = reader.ReadByte();
|
|
Cpu.LoadStateBinary(reader);
|
|
VCE.LoadStateBinary(reader);
|
|
VDC1.LoadStateBinary(reader);
|
|
PSG.LoadStateBinary(reader);
|
|
}
|
|
else
|
|
{
|
|
Ram = reader.ReadBytes(0x8000);
|
|
Frame = reader.ReadInt32();
|
|
_lagcount = reader.ReadInt32();
|
|
IOBuffer = reader.ReadByte();
|
|
Cpu.LoadStateBinary(reader);
|
|
VCE.LoadStateBinary(reader);
|
|
VPC.LoadStateBinary(reader);
|
|
VDC1.LoadStateBinary(reader);
|
|
VDC2.LoadStateBinary(reader);
|
|
PSG.LoadStateBinary(reader);
|
|
}
|
|
}
|
|
|
|
public byte[] SaveStateBinary()
|
|
{
|
|
int buflen = 75887;
|
|
if (SuperGrafx) buflen += 90698;
|
|
if (BramEnabled) buflen += 2048;
|
|
if (PopulousRAM != null) buflen += 0x8000;
|
|
if (SuperRam != null) buflen += 0x30000;
|
|
if (TurboCD) buflen += 0x20000 + 2165;
|
|
if (ArcadeCard) buflen += 42;
|
|
if (ArcadeCard && !ArcadeCardRewindHack) buflen += 0x200000;
|
|
//Console.WriteLine("LENGTH1 " + buflen);
|
|
|
|
var buf = new byte[buflen];
|
|
var stream = new MemoryStream(buf);
|
|
var writer = new BinaryWriter(stream);
|
|
SaveStateBinary(writer);
|
|
//Console.WriteLine("LENGTH2 " + stream.Position);
|
|
writer.Close();
|
|
return buf;
|
|
}
|
|
|
|
void SetupMemoryDomains()
|
|
{
|
|
var domains = new List<MemoryDomain>(10);
|
|
var MainMemoryDomain = new MemoryDomain("Main Memory", Ram.Length, Endian.Little,
|
|
addr => Ram[addr & 0x1FFF],
|
|
(addr, value) => Ram[addr & 0x1FFF] = value);
|
|
domains.Add(MainMemoryDomain);
|
|
|
|
var SystemBusDomain = new MemoryDomain("System Bus", 0x200000, Endian.Little,
|
|
addr => Cpu.ReadMemory21(addr),
|
|
(addr, value) => Cpu.WriteMemory21(addr, value));
|
|
domains.Add(SystemBusDomain);
|
|
|
|
if (BRAM != null)
|
|
{
|
|
var BRAMMemoryDomain = new MemoryDomain("Battery RAM", Ram.Length, Endian.Little,
|
|
addr => BRAM[addr & 0x7FF],
|
|
(addr, value) => BRAM[addr & 0x7FF] = value);
|
|
domains.Add(BRAMMemoryDomain);
|
|
}
|
|
|
|
if (TurboCD)
|
|
{
|
|
var CDRamMemoryDomain = new MemoryDomain("TurboCD RAM", CDRam.Length, Endian.Little,
|
|
addr => CDRam[addr & 0xFFFF],
|
|
(addr, value) => CDRam[addr & 0xFFFF] = value);
|
|
domains.Add(CDRamMemoryDomain);
|
|
|
|
var AdpcmMemoryDomain = new MemoryDomain("ADPCM RAM", ADPCM.RAM.Length, Endian.Little,
|
|
addr => ADPCM.RAM[addr & 0xFFFF],
|
|
(addr, value) => ADPCM.RAM[addr & 0xFFFF] = value);
|
|
domains.Add(AdpcmMemoryDomain);
|
|
|
|
if (SuperRam != null)
|
|
{
|
|
var SuperRamMemoryDomain = new MemoryDomain("Super System Card RAM", SuperRam.Length, Endian.Little,
|
|
addr => SuperRam[addr & 0x3FFFF],
|
|
(addr, value) => SuperRam[addr & 0x3FFFF] = value);
|
|
domains.Add(SuperRamMemoryDomain);
|
|
}
|
|
}
|
|
|
|
if (ArcadeCard)
|
|
{
|
|
var ArcadeRamMemoryDomain = new MemoryDomain("Aracde Card RAM", ArcadeRam.Length, Endian.Little,
|
|
addr => ArcadeRam[addr & 0x1FFFFF],
|
|
(addr, value) => ArcadeRam[addr & 0x1FFFFF] = value);
|
|
domains.Add(ArcadeRamMemoryDomain);
|
|
}
|
|
|
|
memoryDomains = domains.AsReadOnly();
|
|
}
|
|
|
|
IList<MemoryDomain> memoryDomains;
|
|
public IList<MemoryDomain> MemoryDomains { get { return memoryDomains; } }
|
|
public MemoryDomain MainMemory { get { return memoryDomains[0]; } }
|
|
|
|
public void Dispose()
|
|
{
|
|
if (disc != null)
|
|
disc.Dispose();
|
|
}
|
|
}
|
|
}
|