496 lines
15 KiB
C#
496 lines
15 KiB
C#
#define MUSASHI
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
|
|
using BizHawk.Common.BufferExtensions;
|
|
|
|
using BizHawk.Emulation.Common;
|
|
using BizHawk.Emulation.Common.Components;
|
|
using BizHawk.Emulation.Cores.Components;
|
|
using BizHawk.Emulation.Cores.Components.M68000;
|
|
using BizHawk.Emulation.Cores.Components.Z80;
|
|
using Native68000;
|
|
|
|
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
|
{
|
|
[CoreAttributes(
|
|
"GenesisHawk",
|
|
"Vecna",
|
|
isPorted: false,
|
|
isReleased: false
|
|
)]
|
|
public sealed partial class Genesis : IEmulator, ISaveRam, IStatable, IInputPollable
|
|
{
|
|
private int _lagcount = 0;
|
|
private bool lagged = true;
|
|
private bool islag = false;
|
|
|
|
// ROM
|
|
public byte[] RomData;
|
|
|
|
// Machine stuff
|
|
public MC68000 MainCPU;
|
|
public Z80A SoundCPU;
|
|
public GenVDP VDP;
|
|
public SN76489 PSG;
|
|
public YM2612 YM2612;
|
|
public byte[] Ram = new byte[0x10000];
|
|
public byte[] Z80Ram = new byte[0x2000];
|
|
|
|
private bool M68000HasZ80Bus = false;
|
|
private bool Z80Reset = false;
|
|
private bool Z80Runnable { get { return (Z80Reset == false && M68000HasZ80Bus == false); } }
|
|
|
|
private SoundMixer SoundMixer;
|
|
|
|
[FeatureNotImplemented]
|
|
public IInputCallbackSystem InputCallbacks { get { throw new NotImplementedException(); } }
|
|
|
|
public void ResetCounters()
|
|
{
|
|
Frame = 0;
|
|
_lagcount = 0;
|
|
islag = false;
|
|
}
|
|
|
|
// Genesis timings:
|
|
// 53,693,175 Machine clocks / sec
|
|
// 7,670,454 Main 68000 cycles / sec (7 mclk divisor)
|
|
// 3,579,545 Z80 cycles / sec (15 mclk divisor)
|
|
|
|
// At 59.92 FPS:
|
|
// 896,081 mclks / frame
|
|
// 128,011 Main 68000 cycles / frame
|
|
// 59,738 Z80 cycles / frame
|
|
|
|
// At 262 lines/frame:
|
|
// 3420 mclks / line
|
|
// ~ 488.5 Main 68000 cycles / line
|
|
// 228 Z80 cycles / line
|
|
|
|
// Video characteristics:
|
|
// 224 lines are active display. The remaining 38 lines are vertical blanking.
|
|
// In H40 mode, the dot clock is 480 pixels per line.
|
|
// 320 are active display, the remaining 160 are horizontal blanking.
|
|
// A total of 3420 mclks per line, but 2560 mclks are active display and 860 mclks are blanking.
|
|
|
|
#if MUSASHI
|
|
VdpCallback _vdp;
|
|
ReadCallback read8;
|
|
ReadCallback read16;
|
|
ReadCallback read32;
|
|
WriteCallback write8;
|
|
WriteCallback write16;
|
|
WriteCallback write32;
|
|
#endif
|
|
|
|
public Genesis(CoreComm comm, GameInfo game, byte[] rom)
|
|
{
|
|
ServiceProvider = new BasicServiceProvider(this);
|
|
CoreComm = comm;
|
|
MainCPU = new MC68000();
|
|
SoundCPU = new Z80A();
|
|
YM2612 = new YM2612() { MaxVolume = 23405 };
|
|
PSG = new SN76489() { MaxVolume = 4681 };
|
|
VDP = new GenVDP();
|
|
VDP.DmaReadFrom68000 = ReadWord;
|
|
(ServiceProvider as BasicServiceProvider).Register<IVideoProvider>(VDP);
|
|
SoundMixer = new SoundMixer(YM2612, PSG);
|
|
|
|
MainCPU.ReadByte = ReadByte;
|
|
MainCPU.ReadWord = ReadWord;
|
|
MainCPU.ReadLong = ReadLong;
|
|
MainCPU.WriteByte = WriteByte;
|
|
MainCPU.WriteWord = WriteWord;
|
|
MainCPU.WriteLong = WriteLong;
|
|
MainCPU.IrqCallback = InterruptCallback;
|
|
|
|
// ---------------------- musashi -----------------------
|
|
#if MUSASHI
|
|
_vdp = vdpcallback;
|
|
read8 = Read8;
|
|
read16 = Read16;
|
|
read32 = Read32;
|
|
write8 = Write8;
|
|
write16 = Write16;
|
|
write32 = Write32;
|
|
|
|
Musashi.RegisterVdpCallback(Marshal.GetFunctionPointerForDelegate(_vdp));
|
|
Musashi.RegisterRead8(Marshal.GetFunctionPointerForDelegate(read8));
|
|
Musashi.RegisterRead16(Marshal.GetFunctionPointerForDelegate(read16));
|
|
Musashi.RegisterRead32(Marshal.GetFunctionPointerForDelegate(read32));
|
|
Musashi.RegisterWrite8(Marshal.GetFunctionPointerForDelegate(write8));
|
|
Musashi.RegisterWrite16(Marshal.GetFunctionPointerForDelegate(write16));
|
|
Musashi.RegisterWrite32(Marshal.GetFunctionPointerForDelegate(write32));
|
|
#endif
|
|
// ---------------------- musashi -----------------------
|
|
|
|
SoundCPU.ReadMemory = ReadMemoryZ80;
|
|
SoundCPU.WriteMemory = WriteMemoryZ80;
|
|
SoundCPU.WriteHardware = (a, v) => { Console.WriteLine("Z80: Attempt I/O Write {0:X2}:{1:X2}", a, v); };
|
|
SoundCPU.ReadHardware = x => 0xFF;
|
|
SoundCPU.IRQCallback = () => SoundCPU.Interrupt = false;
|
|
Z80Reset = true;
|
|
RomData = new byte[0x400000];
|
|
for (int i = 0; i < rom.Length; i++)
|
|
RomData[i] = rom[i];
|
|
|
|
SetupMemoryDomains();
|
|
#if MUSASHI
|
|
Musashi.Init();
|
|
Musashi.Reset();
|
|
VDP.GetPC = () => Musashi.PC;
|
|
#else
|
|
MainCPU.Reset();
|
|
VDP.GetPC = () => MainCPU.PC;
|
|
#endif
|
|
InitializeCartHardware(game);
|
|
}
|
|
|
|
public IEmulatorServiceProvider ServiceProvider { get; private set; }
|
|
|
|
void InitializeCartHardware(GameInfo game)
|
|
{
|
|
LogCartInfo();
|
|
InitializeEeprom(game);
|
|
InitializeSaveRam(game);
|
|
}
|
|
|
|
public void FrameAdvance(bool render, bool rendersound)
|
|
{
|
|
lagged = true;
|
|
Frame++;
|
|
PSG.BeginFrame(SoundCPU.TotalExecutedCycles);
|
|
YM2612.BeginFrame(SoundCPU.TotalExecutedCycles);
|
|
|
|
// Do start-of-frame events
|
|
VDP.HIntLineCounter = VDP.Registers[10];
|
|
//VDP.VdpStatusWord &=
|
|
unchecked { VDP.VdpStatusWord &= (ushort)~GenVDP.StatusVerticalBlanking; }
|
|
|
|
for (VDP.ScanLine = 0; VDP.ScanLine < 262; VDP.ScanLine++)
|
|
{
|
|
//Log.Error("VDP","FRAME {0}, SCANLINE {1}", Frame, VDP.ScanLine);
|
|
|
|
if (VDP.ScanLine < VDP.FrameHeight)
|
|
VDP.RenderLine();
|
|
|
|
Exec68k(365);
|
|
RunZ80(171);
|
|
|
|
// H-Int now?
|
|
|
|
VDP.HIntLineCounter--;
|
|
if (VDP.HIntLineCounter < 0 && VDP.ScanLine < 224) // FIXME
|
|
{
|
|
VDP.HIntLineCounter = VDP.Registers[10];
|
|
VDP.VdpStatusWord |= GenVDP.StatusHorizBlanking;
|
|
|
|
if (VDP.HInterruptsEnabled)
|
|
{
|
|
Set68kIrq(4);
|
|
//Console.WriteLine("Fire hint!");
|
|
}
|
|
|
|
}
|
|
|
|
Exec68k(488 - 365);
|
|
RunZ80(228 - 171);
|
|
|
|
if (VDP.ScanLine == 224)
|
|
{
|
|
VDP.VdpStatusWord |= GenVDP.StatusVerticalInterruptPending;
|
|
VDP.VdpStatusWord |= GenVDP.StatusVerticalBlanking;
|
|
Exec68k(16); // this is stupidly wrong.
|
|
// End-frame stuff
|
|
if (VDP.VInterruptEnabled)
|
|
Set68kIrq(6);
|
|
|
|
SoundCPU.Interrupt = true;
|
|
//The INT output is asserted every frame for exactly one scanline, and it can't be disabled. A very short Z80 interrupt routine would be triggered multiple times if it finishes within 228 Z80 clock cycles. I think (but cannot recall the specifics) that some games have delay loops in the interrupt handler for this very reason.
|
|
}
|
|
}
|
|
PSG.EndFrame(SoundCPU.TotalExecutedCycles);
|
|
YM2612.EndFrame(SoundCPU.TotalExecutedCycles);
|
|
|
|
|
|
|
|
if (lagged)
|
|
{
|
|
_lagcount++;
|
|
islag = true;
|
|
}
|
|
else
|
|
islag = false;
|
|
}
|
|
|
|
void Exec68k(int cycles)
|
|
{
|
|
#if MUSASHI
|
|
Musashi.Execute(cycles);
|
|
#else
|
|
MainCPU.ExecuteCycles(cycles);
|
|
#endif
|
|
}
|
|
|
|
void RunZ80(int cycles)
|
|
{
|
|
// I emulate the YM2612 synced to Z80 clock, for better or worse.
|
|
// So we still need to keep the Z80 cycle count accurate even if the Z80 isn't running.
|
|
|
|
if (Z80Runnable)
|
|
SoundCPU.ExecuteCycles(cycles);
|
|
else
|
|
SoundCPU.TotalExecutedCycles += cycles;
|
|
}
|
|
|
|
void Set68kIrq(int irq)
|
|
{
|
|
#if MUSASHI
|
|
Musashi.SetIRQ(irq);
|
|
#else
|
|
MainCPU.Interrupt = irq;
|
|
#endif
|
|
}
|
|
|
|
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
|
|
{
|
|
return new Dictionary<string, RegisterValue>
|
|
{
|
|
{ "A-0", MainCPU.A[0].s32 },
|
|
{ "A-1", MainCPU.A[1].s32 },
|
|
{ "A-2", MainCPU.A[2].s32 },
|
|
{ "A-3", MainCPU.A[3].s32 },
|
|
{ "A-4", MainCPU.A[4].s32 },
|
|
{ "A-5", MainCPU.A[5].s32 },
|
|
{ "A-6", MainCPU.A[6].s32 },
|
|
{ "A-7", MainCPU.A[7].s32 },
|
|
|
|
{ "D-0", MainCPU.D[0].s32 },
|
|
{ "D-1", MainCPU.D[1].s32 },
|
|
{ "D-2", MainCPU.D[2].s32 },
|
|
{ "D-3", MainCPU.D[3].s32 },
|
|
{ "D-4", MainCPU.D[4].s32 },
|
|
{ "D-5", MainCPU.D[5].s32 },
|
|
{ "D-6", MainCPU.D[6].s32 },
|
|
{ "D-7", MainCPU.D[7].s32 },
|
|
|
|
{ "SR", MainCPU.SR },
|
|
|
|
{ "Flag X", MainCPU.X },
|
|
{ "Flag N", MainCPU.N },
|
|
{ "Flag Z", MainCPU.Z },
|
|
{ "Flag V", MainCPU.V },
|
|
{ "Flag C", MainCPU.C }
|
|
};
|
|
}
|
|
|
|
int vdpcallback(int level) // Musashi handler
|
|
{
|
|
InterruptCallback(level);
|
|
return -1;
|
|
}
|
|
|
|
void InterruptCallback(int level)
|
|
{
|
|
unchecked { VDP.VdpStatusWord &= (ushort)~GenVDP.StatusVerticalInterruptPending; }
|
|
}
|
|
|
|
public CoreComm CoreComm { get; private set; }
|
|
|
|
public ISoundProvider SoundProvider
|
|
{
|
|
get { return SoundMixer; }
|
|
}
|
|
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(SoundMixer, 735); } }
|
|
public bool StartAsyncSound() { return true; }
|
|
public void EndAsyncSound() { }
|
|
|
|
public int Frame { get; set; }
|
|
public int LagCount { get { return _lagcount; } set { _lagcount = value; } }
|
|
public bool IsLagFrame { get { return islag; } set { islag = value; } }
|
|
public bool DeterministicEmulation { get { return true; } }
|
|
public string SystemId { get { return "GEN"; } }
|
|
|
|
public string BoardName { get { return null; } }
|
|
|
|
public void SaveStateText(TextWriter writer)
|
|
{
|
|
var buf = new byte[141501 + SaveRAM.Length];
|
|
var stream = new MemoryStream(buf);
|
|
var bwriter = new BinaryWriter(stream);
|
|
SaveStateBinary(bwriter);
|
|
|
|
writer.WriteLine("Version 1");
|
|
writer.Write("BigFatBlob ");
|
|
buf.SaveAsHex(writer);
|
|
|
|
/*writer.WriteLine("[MegaDrive]");
|
|
MainCPU.SaveStateText(writer, "Main68K");
|
|
SoundCPU.SaveStateText(writer);
|
|
PSG.SaveStateText(writer);
|
|
VDP.SaveStateText(writer);
|
|
writer.WriteLine("Frame {0}", Frame);
|
|
writer.WriteLine("Lag {0}", _lagcount);
|
|
writer.WriteLine("IsLag {0}", islag);
|
|
writer.Write("MainRAM ");
|
|
Ram.SaveAsHex(writer);
|
|
writer.Write("Z80RAM ");
|
|
Z80Ram.SaveAsHex(writer);
|
|
writer.WriteLine("[/MegaDrive]");*/
|
|
}
|
|
|
|
public void LoadStateText(TextReader reader)
|
|
{
|
|
var buf = new byte[141501 + SaveRAM.Length];
|
|
var version = reader.ReadLine();
|
|
if (version != "Version 1")
|
|
throw new Exception("Not a valid state vesrion! sorry! your state is bad! Robust states will be added later!");
|
|
var omgstate = reader.ReadLine().Split(' ')[1];
|
|
buf.ReadFromHex(omgstate);
|
|
LoadStateBinary(new BinaryReader(new MemoryStream(buf)));
|
|
|
|
/*while (true)
|
|
{
|
|
string[] args = reader.ReadLine().Split(' ');
|
|
if (args[0].Trim() == "") continue;
|
|
if (args[0] == "[MegaDrive]") continue;
|
|
if (args[0] == "[/MegaDrive]") break;
|
|
if (args[0] == "MainRAM")
|
|
Ram.ReadFromHex(args[1]);
|
|
else if (args[0] == "Z80RAM")
|
|
Z80Ram.ReadFromHex(args[1]);
|
|
else if (args[0] == "[Main68K]")
|
|
MainCPU.LoadStateText(reader, "Main68K");
|
|
else if (args[0] == "[Z80]")
|
|
SoundCPU.LoadStateText(reader);
|
|
else if (args[0] == "Frame")
|
|
Frame = int.Parse(args[1]);
|
|
else if (args[0] == "Lag")
|
|
_lagcount = int.Parse(args[1]);
|
|
else if (args[0] == "IsLag")
|
|
islag = bool.Parse(args[1]);
|
|
else if (args[0] == "[PSG]")
|
|
PSG.LoadStateText(reader);
|
|
else if (args[0] == "[VDP]")
|
|
VDP.LoadStateText(reader);
|
|
else
|
|
Console.WriteLine("Skipping unrecognized identifier " + args[0]);
|
|
}*/
|
|
}
|
|
|
|
public void SaveStateBinary(BinaryWriter writer)
|
|
{
|
|
Musashi.SaveStateBinary(writer); // 124
|
|
//SoundCPU.SaveStateBinary(writer); // 46 TODO fix this, there is no way to invoke this core from the UI for testing anyway.
|
|
//PSG.SaveStateBinary(writer); // 15
|
|
VDP.SaveStateBinary(writer); // 65781
|
|
YM2612.SaveStateBinary(writer); // 1785
|
|
|
|
writer.Write(Ram); // 65535
|
|
writer.Write(Z80Ram); // 8192
|
|
|
|
writer.Write(Frame); // 4
|
|
writer.Write(M68000HasZ80Bus); // 1
|
|
writer.Write(Z80Reset); // 1
|
|
writer.Write(BankRegion); // 4
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
writer.Write(IOPorts[i].Data);
|
|
writer.Write(IOPorts[i].TxData);
|
|
writer.Write(IOPorts[i].RxData);
|
|
writer.Write(IOPorts[i].SCtrl);
|
|
}
|
|
|
|
if (SaveRAM.Length > 0)
|
|
writer.Write(SaveRAM);
|
|
|
|
// TODO: EEPROM/cart HW state
|
|
// TODO: lag counter crap
|
|
}
|
|
|
|
public void LoadStateBinary(BinaryReader reader)
|
|
{
|
|
Musashi.LoadStateBinary(reader);
|
|
//SoundCPU.LoadStateBinary(reader);
|
|
//PSG.LoadStateBinary(reader);
|
|
VDP.LoadStateBinary(reader);
|
|
YM2612.LoadStateBinary(reader);
|
|
|
|
Ram = reader.ReadBytes(Ram.Length);
|
|
Z80Ram = reader.ReadBytes(Z80Ram.Length);
|
|
|
|
Frame = reader.ReadInt32();
|
|
M68000HasZ80Bus = reader.ReadBoolean();
|
|
Z80Reset = reader.ReadBoolean();
|
|
BankRegion = reader.ReadInt32();
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
IOPorts[i].Data = reader.ReadByte();
|
|
IOPorts[i].TxData = reader.ReadByte();
|
|
IOPorts[i].RxData = reader.ReadByte();
|
|
IOPorts[i].SCtrl = reader.ReadByte();
|
|
}
|
|
|
|
if (SaveRAM.Length > 0)
|
|
SaveRAM = reader.ReadBytes(SaveRAM.Length);
|
|
}
|
|
|
|
public byte[] SaveStateBinary()
|
|
{
|
|
var buf = new byte[141501 + SaveRAM.Length];
|
|
var stream = new MemoryStream(buf);
|
|
var writer = new BinaryWriter(stream);
|
|
SaveStateBinary(writer);
|
|
//Console.WriteLine("buf len = {0}", stream.Position);
|
|
writer.Close();
|
|
return buf;
|
|
}
|
|
|
|
public bool BinarySaveStatesPreferred { get { return false; } }
|
|
|
|
MemoryDomainList memoryDomains;
|
|
|
|
void SetupMemoryDomains()
|
|
{
|
|
/*
|
|
var domains = new List<MemoryDomain>(3);
|
|
var MainMemoryDomain = new MemoryDomain("Main RAM", Ram.Length, MemoryDomain.Endian.Big,
|
|
addr => Ram[addr & 0xFFFF],
|
|
(addr, value) => Ram[addr & 0xFFFF] = value);
|
|
var Z80Domain = new MemoryDomain("Z80 RAM", Z80Ram.Length, MemoryDomain.Endian.Little,
|
|
addr => Z80Ram[addr & 0x1FFF],
|
|
(addr, value) => { Z80Ram[addr & 0x1FFF] = value; });
|
|
|
|
var VRamDomain = new MemoryDomain("Video RAM", VDP.VRAM.Length, MemoryDomain.Endian.Big,
|
|
addr => VDP.VRAM[addr & 0xFFFF],
|
|
(addr, value) => VDP.VRAM[addr & 0xFFFF] = value);
|
|
|
|
var RomDomain = new MemoryDomain("MD CART", RomData.Length, MemoryDomain.Endian.Big,
|
|
addr => RomData[addr], //adelikat: For speed considerations, I didn't mask this, every tool that uses memory domains is smart enough not to overflow, if I'm wrong let me know!
|
|
(addr, value) => RomData[addr & (RomData.Length - 1)] = value);
|
|
|
|
var SystemBusDomain = new MemoryDomain("System Bus", 0x1000000, MemoryDomain.Endian.Big,
|
|
addr => (byte)ReadByte((int)addr),
|
|
(addr, value) => Write8((uint)addr, (uint)value));
|
|
|
|
domains.Add(MainMemoryDomain);
|
|
domains.Add(Z80Domain);
|
|
domains.Add(VRamDomain);
|
|
domains.Add(RomDomain);
|
|
domains.Add(SystemBusDomain);
|
|
memoryDomains = new MemoryDomainList(domains);
|
|
(ServiceProvider as BasicServiceProvider).Register<IMemoryDomains>(memoryDomains);
|
|
*/
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Dispose() { }
|
|
}
|
|
}
|