diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index f06cf75020..e59465c069 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -17,6 +17,7 @@ using BizHawk.Emulation.Cores.Nintendo.GBHawk; using BizHawk.Emulation.Cores.Nintendo.GBHawkLink; using BizHawk.Emulation.Cores.Nintendo.SNES; using BizHawk.Emulation.Cores.PCEngine; +using BizHawk.Emulation.Cores.Sega.GGHawkLink; using BizHawk.Emulation.Cores.Sega.Saturn; using BizHawk.Emulation.Cores.Sony.PSP; using BizHawk.Emulation.Cores.Sony.PSX; @@ -805,6 +806,22 @@ namespace BizHawk.Client.Common } nextEmulator = new GPGX(nextComm, null, genDiscs, GetCoreSettings(), GetCoreSyncSettings()); break; + case "Game Gear": + var leftBytesGG = xmlGame.Assets.First().Value; + var rightBytesGG = xmlGame.Assets.Skip(1).First().Value; + + var leftGG = Database.GetGameInfo(leftBytesGG, "left.gg"); + var rightGG = Database.GetGameInfo(rightBytesGG, "right.gg"); + + nextEmulator = new GGHawkLink( + nextComm, + leftGG, + leftBytesGG, + rightGG, + rightBytesGG, + GetCoreSettings(), + GetCoreSyncSettings()); + break; default: return false; } diff --git a/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs b/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs index f6b2a00d07..8b3762e715 100644 --- a/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs +++ b/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs @@ -145,7 +145,8 @@ "PSX", "SAT", "ZXSpectrum", - "AmstradCPC"}); + "AmstradCPC", + "Game Gear"}); this.SystemDropDown.Location = new System.Drawing.Point(425, 75); this.SystemDropDown.Name = "SystemDropDown"; this.SystemDropDown.Size = new System.Drawing.Size(69, 21); diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index f6bf415a2a..67a4037449 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -1336,6 +1336,31 @@ + + + + GGHawkLink.cs + + + GGHawkLink.cs + + + GGHawkLink.cs + + + GGHawkLink.cs + + + GGHawkLink.cs + + + GGHawkLink.cs + + + GGHawkLink.cs + + + GPGX.cs diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawkLink/GBHawkLink.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawkLink/GBHawkLink.cs index 329fdd55dd..094270ce05 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawkLink/GBHawkLink.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawkLink/GBHawkLink.cs @@ -39,7 +39,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawkLink linkSettings = (GBLinkSettings)settings ?? new GBLinkSettings(); linkSyncSettings = (GBLinkSyncSettings)syncSettings ?? new GBLinkSyncSettings(); - _controllerDeck = new GBHawkLinkControllerDeck(GBHawkControllerDeck.DefaultControllerName, GBHawkControllerDeck.DefaultControllerName); + _controllerDeck = new GBHawkLinkControllerDeck(GBHawkLinkControllerDeck.DefaultControllerName, GBHawkLinkControllerDeck.DefaultControllerName); CoreComm = comm; diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.ICodeDataLog.cs b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.ICodeDataLog.cs new file mode 100644 index 0000000000..2371961b33 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.ICodeDataLog.cs @@ -0,0 +1,115 @@ +using System; +using System.IO; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Components.Z80A; + +namespace BizHawk.Emulation.Cores.Sega.GGHawkLink +{ + public sealed partial class GGHawkLink : ICodeDataLogger + { + public void SetCDL(ICodeDataLog cdl) + { + CDL = cdl; + if (cdl == null) + { + L.Cpu.ReadMemory = L.ReadMemory; + L.Cpu.WriteMemory = L.WriteMemory; + L.Cpu.FetchMemory = L.FetchMemory; + } + else + { + L.Cpu.ReadMemory = ReadMemory_CDL; + L.Cpu.WriteMemory = L.WriteMemory; + L.Cpu.FetchMemory = FetchMemory_CDL; + } + } + + public void NewCDL(ICodeDataLog cdl) + { + cdl["ROM"] = new byte[MemoryDomains["ROM"].Size]; + cdl["Main RAM"] = new byte[MemoryDomains["Main RAM"].Size]; + + if (MemoryDomains.Has("Save RAM")) + { + cdl["Save RAM"] = new byte[MemoryDomains["Save RAM"].Size]; + } + + if (MemoryDomains.Has("Cart (Volatile) RAM")) + { + cdl["Cart (Volatile) RAM"] = new byte[MemoryDomains["Cart (Volatile) RAM"].Size]; + } + + cdl.SubType = "SMS"; + cdl.SubVer = 0; + } + + [FeatureNotImplemented] + public void DisassembleCDL(Stream s, ICodeDataLog cdl) + { + + } + + private enum CDLog_AddrType + { + None, + ROM, + MainRAM, + SaveRAM, + CartRAM, //"Cart (Volatile) RAM" aka ExtRam + } + + [Flags] + private enum CDLog_Flags + { + ExecFirst = 0x01, + ExecOperand = 0x02, + Data = 0x04 + }; + + private struct CDLog_MapResults + { + public CDLog_AddrType Type; + public int Address; + } + + private delegate CDLog_MapResults MapMemoryDelegate(ushort addr, bool write); + private MapMemoryDelegate MapMemory; + private ICodeDataLog CDL; + + private void RunCDL(ushort address, CDLog_Flags flags) + { + if (MapMemory != null) + { + CDLog_MapResults results = MapMemory(address, false); + switch (results.Type) + { + case CDLog_AddrType.None: break; + case CDLog_AddrType.ROM: CDL["ROM"][results.Address] |= (byte)flags; break; + case CDLog_AddrType.MainRAM: CDL["Main RAM"][results.Address] |= (byte)flags; break; + case CDLog_AddrType.SaveRAM: CDL["Save RAM"][results.Address] |= (byte)flags; break; + case CDLog_AddrType.CartRAM: CDL["Cart (Volatile) RAM"][results.Address] |= (byte)flags; break; + } + } + } + + /// + /// A wrapper for FetchMemory which inserts CDL logic + /// + private byte FetchMemory_CDL(ushort address) + { + RunCDL(address, CDLog_Flags.ExecFirst); + return L.ReadMemory(address); + } + + /// + /// A wrapper for ReadMemory which inserts CDL logic + /// + private byte ReadMemory_CDL(ushort address) + { + RunCDL(address, CDLog_Flags.Data); + return L.ReadMemory(address); + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IDebuggable.cs new file mode 100644 index 0000000000..559381bead --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IDebuggable.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Sega.GGHawkLink +{ + public partial class GGHawkLink : IDebuggable + { + public IDictionary GetCpuFlagsAndRegisters() + { + return new Dictionary + { + ["A"] = L.Cpu.Regs[L.Cpu.A], + ["AF"] = L.Cpu.Regs[L.Cpu.F] + (L.Cpu.Regs[L.Cpu.A] << 8), + ["B"] = L.Cpu.Regs[L.Cpu.B], + ["BC"] = L.Cpu.Regs[L.Cpu.C] + (L.Cpu.Regs[L.Cpu.B] << 8), + ["C"] = L.Cpu.Regs[L.Cpu.C], + ["D"] = L.Cpu.Regs[L.Cpu.D], + ["DE"] = L.Cpu.Regs[L.Cpu.E] + (L.Cpu.Regs[L.Cpu.D] << 8), + ["E"] = L.Cpu.Regs[L.Cpu.E], + ["F"] = L.Cpu.Regs[L.Cpu.F], + ["H"] = L.Cpu.Regs[L.Cpu.H], + ["HL"] = L.Cpu.Regs[L.Cpu.L] + (L.Cpu.Regs[L.Cpu.H] << 8), + ["I"] = L.Cpu.Regs[L.Cpu.I], + ["IX"] = L.Cpu.Regs[L.Cpu.Ixl] + (L.Cpu.Regs[L.Cpu.Ixh] << 8), + ["IY"] = L.Cpu.Regs[L.Cpu.Iyl] + (L.Cpu.Regs[L.Cpu.Iyh] << 8), + ["L"] = L.Cpu.Regs[L.Cpu.L], + ["PC"] = L.Cpu.Regs[L.Cpu.PCl] + (L.Cpu.Regs[L.Cpu.PCh] << 8), + ["R"] = L.Cpu.Regs[L.Cpu.R], + ["Shadow AF"] = L.Cpu.Regs[L.Cpu.F_s] + (L.Cpu.Regs[L.Cpu.A_s] << 8), + ["Shadow BC"] = L.Cpu.Regs[L.Cpu.C_s] + (L.Cpu.Regs[L.Cpu.B_s] << 8), + ["Shadow DE"] = L.Cpu.Regs[L.Cpu.E_s] + (L.Cpu.Regs[L.Cpu.D_s] << 8), + ["Shadow HL"] = L.Cpu.Regs[L.Cpu.L_s] + (L.Cpu.Regs[L.Cpu.H_s] << 8), + ["SP"] = L.Cpu.Regs[L.Cpu.SPl] + (L.Cpu.Regs[L.Cpu.SPh] << 8), + ["Flag C"] = L.Cpu.FlagC, + ["Flag N"] = L.Cpu.FlagN, + ["Flag P/V"] = L.Cpu.FlagP, + ["Flag 3rd"] = L.Cpu.Flag3, + ["Flag H"] = L.Cpu.FlagH, + ["Flag 5th"] = L.Cpu.Flag5, + ["Flag Z"] = L.Cpu.FlagZ, + ["Flag S"] = L.Cpu.FlagS + }; + } + + public void SetCpuRegister(string register, int value) + { + switch (register) + { + default: + throw new InvalidOperationException(); + case "A": + L.Cpu.Regs[L.Cpu.A] = (ushort)value; + break; + case "AF": + L.Cpu.Regs[L.Cpu.F] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.A] = (ushort)(value & 0xFF00); + break; + case "B": + L.Cpu.Regs[L.Cpu.B] = (ushort)value; + break; + case "BC": + L.Cpu.Regs[L.Cpu.C] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.B] = (ushort)(value & 0xFF00); + break; + case "C": + L.Cpu.Regs[L.Cpu.C] = (ushort)value; + break; + case "D": + L.Cpu.Regs[L.Cpu.D] = (ushort)value; + break; + case "DE": + L.Cpu.Regs[L.Cpu.E] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.D] = (ushort)(value & 0xFF00); + break; + case "E": + L.Cpu.Regs[L.Cpu.E] = (ushort)value; + break; + case "F": + L.Cpu.Regs[L.Cpu.F] = (ushort)value; + break; + case "H": + L.Cpu.Regs[L.Cpu.H] = (ushort)value; + break; + case "HL": + L.Cpu.Regs[L.Cpu.L] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.H] = (ushort)(value & 0xFF00); + break; + case "I": + L.Cpu.Regs[L.Cpu.I] = (ushort)value; + break; + case "IX": + L.Cpu.Regs[L.Cpu.Ixl] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.Ixh] = (ushort)(value & 0xFF00); + break; + case "IY": + L.Cpu.Regs[L.Cpu.Iyl] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.Iyh] = (ushort)(value & 0xFF00); + break; + case "L": + L.Cpu.Regs[L.Cpu.L] = (ushort)value; + break; + case "PC": + L.Cpu.Regs[L.Cpu.PCl] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.PCh] = (ushort)(value & 0xFF00); + break; + case "R": + L.Cpu.Regs[L.Cpu.R] = (ushort)value; + break; + case "Shadow AF": + L.Cpu.Regs[L.Cpu.F_s] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.A_s] = (ushort)(value & 0xFF00); + break; + case "Shadow BC": + L.Cpu.Regs[L.Cpu.C_s] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.B_s] = (ushort)(value & 0xFF00); + break; + case "Shadow DE": + L.Cpu.Regs[L.Cpu.E_s] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.D_s] = (ushort)(value & 0xFF00); + break; + case "Shadow HL": + L.Cpu.Regs[L.Cpu.L_s] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.H_s] = (ushort)(value & 0xFF00); + break; + case "SP": + L.Cpu.Regs[L.Cpu.SPl] = (ushort)(value & 0xFF); + L.Cpu.Regs[L.Cpu.SPh] = (ushort)(value & 0xFF00); + break; + } + } + + public IMemoryCallbackSystem MemoryCallbacks { get; } = new MemoryCallbackSystem(new[] { "System Bus" }); + + public bool CanStep(StepType type) + { + return false; + } + + [FeatureNotImplemented] + public void Step(StepType type) + { + throw new NotImplementedException(); + } + + public long TotalExecutedCycles + { + get { return (long)L.Cpu.TotalExecutedCycles; } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IEmulator.cs new file mode 100644 index 0000000000..ea168bd2b0 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IEmulator.cs @@ -0,0 +1,275 @@ +using BizHawk.Common.NumberExtensions; +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using BizHawk.Emulation.Cores.Sega.MasterSystem; + +namespace BizHawk.Emulation.Cores.Sega.GGHawkLink +{ + public partial class GGHawkLink : IEmulator, IVideoProvider, ISoundProvider + { + public IEmulatorServiceProvider ServiceProvider { get; } + + public ControllerDefinition ControllerDefinition => _controllerDeck.Definition; + + public bool FrameAdvance(IController controller, bool render, bool rendersound) + { + //Console.WriteLine("-----------------------FRAME-----------------------"); + if (_tracer.Enabled) + { + L.Cpu.TraceCallback = s => _tracer.Put(s); + } + else + { + L.Cpu.TraceCallback = null; + } + + _frame++; + + if (controller.IsPressed("Power")) + { + HardReset(); + } + + bool cablediscosignalNew = controller.IsPressed("Toggle Cable"); + if (cablediscosignalNew && !_cablediscosignal) + { + _cableconnected ^= true; + Console.WriteLine("Cable connect status to {0}", _cableconnected); + LinkConnected = _cableconnected; + } + + _cablediscosignal = cablediscosignalNew; + + _islag = true; + + GetControllerState(controller); + + do_frame(controller, render, rendersound); + + _islag = L._isLag; + + if (_islag) + { + _lagcount++; + } + + return true; + } + + public void do_frame(IController controller, bool render, bool rendersound) + { + /* + L.do_controller_check(); + R.do_controller_check(); + + // advance one full frame + for (int i = 0; i < 70224; i++) + { + L.do_single_step(); + R.do_single_step(); + + // the signal to shift out a bit is when serial_clock = 1 + if (((L.serialport.serial_clock == 1) || (L.serialport.serial_clock == 2)) && !do_r_next) + { + if (LinkConnected) + { + L.serialport.send_external_bit((byte)(L.serialport.serial_data & 0x80)); + + if ((R.serialport.clk_rate == -1) && R.serialport.serial_start) + { + R.serialport.serial_clock = L.serialport.serial_clock; + R.serialport.send_external_bit((byte)(R.serialport.serial_data & 0x80)); + R.serialport.coming_in = L.serialport.going_out; + } + + L.serialport.coming_in = R.serialport.going_out; + } + } + else if ((R.serialport.serial_clock == 1) || (R.serialport.serial_clock == 2)) + { + do_r_next = false; + + if (LinkConnected) + { + R.serialport.send_external_bit((byte)(R.serialport.serial_data & 0x80)); + + if ((L.serialport.clk_rate == -1) && L.serialport.serial_start) + { + L.serialport.serial_clock = R.serialport.serial_clock; + L.serialport.send_external_bit((byte)(L.serialport.serial_data & 0x80)); + L.serialport.coming_in = R.serialport.going_out; + } + + R.serialport.coming_in = L.serialport.going_out; + } + + if (R.serialport.serial_clock == 2) { do_r_next = true; } + } + else + { + do_r_next = false; + } + + // if we hit a frame boundary, update video + if (L.vblank_rise) + { + buff_L = L.GetVideoBuffer(); + L.vblank_rise = false; + FillVideoBuffer(); + } + if (R.vblank_rise) + { + buff_R = R.GetVideoBuffer(); + R.vblank_rise = false; + FillVideoBuffer(); + } + } + */ + L.FrameAdvance(controller, render, rendersound); + R.FrameAdvance(controller, render, rendersound); + + buff_L = L.Vdp.GetVideoBuffer(); + buff_R = R.Vdp.GetVideoBuffer(); + + FillVideoBuffer(); + + } + + public void GetControllerState(IController controller) + { + InputCallbacks.Call(); + //L.controller_state = _controllerDeck.ReadPort1(controller); + //R.controller_state = _controllerDeck.ReadPort2(controller); + } + + public int Frame => _frame; + + public string SystemId => "DGB"; + + public bool DeterministicEmulation { get; set; } + + public void ResetCounters() + { + _frame = 0; + _lagcount = 0; + _islag = false; + } + + public CoreComm CoreComm { get; } + + public void Dispose() + { + L.Dispose(); + R.Dispose(); + } + + #region Video provider + + public int _frameHz = 60; + + public int[] _vidbuffer = new int[160 * 2 * 144]; + public int[] buff_L = new int[160 * 144]; + public int[] buff_R = new int[160 * 144]; + + public int[] GetVideoBuffer() + { + return _vidbuffer; + } + + public void FillVideoBuffer() + { + // combine the 2 video buffers from the instances + for (int i = 0; i < 144; i++) + { + for (int j = 0; j < 160; j++) + { + _vidbuffer[i * 320 + j] = buff_L[i * 160 + j]; + _vidbuffer[i * 320 + j + 160] = buff_R[i * 160 + j]; + } + } + } + + public int VirtualWidth => 160 * 2; + public int VirtualHeight => 144; + public int BufferWidth => 160 * 2; + public int BufferHeight => 144; + public int BackgroundColor => unchecked((int)0xFF000000); + public int VsyncNumerator => _frameHz; + public int VsyncDenominator => 1; + + public static readonly uint[] color_palette_BW = { 0xFFFFFFFF , 0xFFAAAAAA, 0xFF555555, 0xFF000000 }; + public static readonly uint[] color_palette_Gr = { 0xFFA4C505, 0xFF88A905, 0xFF1D551D, 0xFF052505 }; + + public uint[] color_palette = new uint[4]; + + #endregion + + #region audio + + public bool CanProvideAsync => false; + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode != SyncSoundMode.Sync) + { + throw new InvalidOperationException("Only Sync mode is supported_"); + } + } + + public SyncSoundMode SyncMode => SyncSoundMode.Sync; + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + short[] temp_samp_L = new short[735 * 2]; + short[] temp_samp_R = new short[735 * 2]; + + int nsamp_L = 735; + int nsamp_R = 735; + + L.PSG.GetSamples(temp_samp_L); + R.PSG.GetSamples(temp_samp_R); + + if (linkSettings.AudioSet == GGLinkSettings.AudioSrc.Left) + { + samples = temp_samp_L; + nsamp = nsamp_L; + } + else if (linkSettings.AudioSet == GGLinkSettings.AudioSrc.Right) + { + samples = temp_samp_R; + nsamp = nsamp_R; + } + else + { + samples = new short[0]; + nsamp = 0; + } + } + + public void GetSamplesAsync(short[] samples) + { + throw new NotSupportedException("Async is not available"); + } + + public void DiscardSamples() + { + L.PSG.DiscardSamples(); + R.PSG.DiscardSamples(); + } + + private void GetSamples(short[] samples) + { + + } + + public void DisposeSound() + { + + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IInputPollable.cs b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IInputPollable.cs new file mode 100644 index 0000000000..3a180561c8 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IInputPollable.cs @@ -0,0 +1,24 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Sega.GGHawkLink +{ + public partial class GGHawkLink : IInputPollable + { + public int LagCount + { + get { return _lagcount; } + set { _lagcount = value; } + } + + public bool IsLagFrame + { + get { return _islag; } + set { _islag = value; } + } + + public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem(); + + public bool _islag = true; + private int _lagcount; + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IMemoryDomains.cs new file mode 100644 index 0000000000..c4914e0baa --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IMemoryDomains.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Sega.GGHawkLink +{ + public partial class GGHawkLink + { + private IMemoryDomains MemoryDomains; + + public void SetupMemoryDomains() + { + var domains = new List + { + new MemoryDomainDelegate( + "Main RAM L", + L.SystemRam.Length, + MemoryDomain.Endian.Little, + addr => L.SystemRam[addr], + (addr, value) => L.SystemRam[addr] = value, + 1), + new MemoryDomainDelegate( + "Main RAM R", + R.SystemRam.Length, + MemoryDomain.Endian.Little, + addr => R.SystemRam[addr], + (addr, value) => R.SystemRam[addr] = value, + 1), + new MemoryDomainDelegate( + "System Bus L", + 0X10000, + MemoryDomain.Endian.Little, + addr => PeekSystemBusL(addr), + (addr, value) => PokeSystemBusL(addr, value), + 1), + new MemoryDomainDelegate( + "System Bus R", + 0X10000, + MemoryDomain.Endian.Little, + addr => PeekSystemBusR(addr), + (addr, value) => PokeSystemBusR(addr, value), + 1), + new MemoryDomainDelegate( + "ROM L", + L.RomData.Length, + MemoryDomain.Endian.Little, + addr => L.RomData[addr], + (addr, value) => L.RomData[addr] = value, + 1), + new MemoryDomainDelegate( + "ROM R", + R.RomData.Length, + MemoryDomain.Endian.Little, + addr => R.RomData[addr], + (addr, value) => R.RomData[addr] = value, + 1), + new MemoryDomainDelegate( + "VRAM L", + L.Vdp.VRAM.Length, + MemoryDomain.Endian.Little, + addr => L.Vdp.VRAM[addr], + (addr, value) => L.Vdp.VRAM[addr] = value, + 1), + new MemoryDomainDelegate( + "VRAM R", + R.Vdp.VRAM.Length, + MemoryDomain.Endian.Little, + addr => R.Vdp.VRAM[addr], + (addr, value) => R.Vdp.VRAM[addr] = value, + 1) + }; + + if (L.SaveRAM != null) + { + var CartRamL = new MemoryDomainByteArray("Cart RAM L", MemoryDomain.Endian.Little, L.SaveRAM, true, 1); + domains.Add(CartRamL); + } + + if (R.SaveRAM != null) + { + var CartRamR = new MemoryDomainByteArray("Cart RAM R", MemoryDomain.Endian.Little, R.SaveRAM, true, 1); + domains.Add(CartRamR); + } + + MemoryDomains = new MemoryDomainList(domains); + (ServiceProvider as BasicServiceProvider).Register(MemoryDomains); + } + + private byte PeekSystemBusL(long addr) + { + ushort addr2 = (ushort)(addr & 0xFFFF); + return L.ReadMemory(addr2); + } + + private byte PeekSystemBusR(long addr) + { + ushort addr2 = (ushort)(addr & 0xFFFF); + return R.ReadMemory(addr2); + } + + private void PokeSystemBusL(long addr, byte value) + { + ushort addr2 = (ushort)(addr & 0xFFFF); + L.WriteMemory(addr2, value); + } + + private void PokeSystemBusR(long addr, byte value) + { + ushort addr2 = (ushort)(addr & 0xFFFF); + R.WriteMemory(addr2, value); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.ISaveRam.cs b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.ISaveRam.cs new file mode 100644 index 0000000000..335b273e86 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.ISaveRam.cs @@ -0,0 +1,81 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Sega.GGHawkLink +{ + public partial class GGHawkLink : ISaveRam + { + public byte[] CloneSaveRam() + { + if ((L.SaveRAM != null) || (R.SaveRAM != null)) + { + int Len1 = 0; + int Len2 = 0; + int index = 0; + + if (L.SaveRAM != null) + { + Len1 = L.SaveRAM.Length; + } + + if (R.SaveRAM != null) + { + Len2 = R.SaveRAM.Length; + } + + byte[] temp = new byte[Len1 + Len2]; + + if (L.SaveRAM != null) + { + for (int i = 0; i < L.SaveRAM.Length; i++) + { + temp[index] = L.SaveRAM[i]; + index++; + } + } + + if (R.SaveRAM != null) + { + for (int i = 0; i < L.SaveRAM.Length; i++) + { + temp[index] = R.SaveRAM[i]; + index++; + } + } + + return temp; + } + else + { + return null; + } + } + + public void StoreSaveRam(byte[] data) + { + if ((L.SaveRAM != null) && (R.SaveRAM == null)) + { + Buffer.BlockCopy(data, 0, L.SaveRAM, 0, L.SaveRAM.Length); + } + else if ((R.SaveRAM != null) && (L.SaveRAM == null)) + { + Buffer.BlockCopy(data, 0, R.SaveRAM, 0, R.SaveRAM.Length); + } + else if ((R.SaveRAM != null) && (L.SaveRAM != null)) + { + Buffer.BlockCopy(data, 0, L.SaveRAM, 0, L.SaveRAM.Length); + Buffer.BlockCopy(data, L.SaveRAM.Length, R.SaveRAM, 0, R.SaveRAM.Length); + } + + Console.WriteLine("loading SRAM here"); + } + + public bool SaveRamModified + { + get + { + return linkSyncSettings.Use_SRAM; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.ISettable.cs new file mode 100644 index 0000000000..2c5f5d8612 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.ISettable.cs @@ -0,0 +1,78 @@ +using System; +using System.ComponentModel; + +using Newtonsoft.Json; + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Sega.GGHawkLink +{ + public partial class GGHawkLink : IEmulator, IStatable, ISettable + { + public GGLinkSettings GetSettings() + { + return linkSettings.Clone(); + } + + public GGLinkSyncSettings GetSyncSettings() + { + return linkSyncSettings.Clone(); + } + + public bool PutSettings(GGLinkSettings o) + { + linkSettings = o; + return false; + } + + public bool PutSyncSettings(GGLinkSyncSettings o) + { + bool ret = GGLinkSyncSettings.NeedsReboot(linkSyncSettings, o); + linkSyncSettings = o; + return ret; + } + + private GGLinkSettings linkSettings = new GGLinkSettings(); + public GGLinkSyncSettings linkSyncSettings = new GGLinkSyncSettings(); + + public class GGLinkSettings + { + public enum AudioSrc + { + Left, + Right, + Both + } + + [DisplayName("Audio Selection")] + [Description("Choose Audio Source. Both will produce Stereo sound.")] + [DefaultValue(AudioSrc.Left)] + public AudioSrc AudioSet { get; set; } + + public GGLinkSettings Clone() + { + return (GGLinkSettings)MemberwiseClone(); + } + } + + public class GGLinkSyncSettings + { + + [DisplayName("Use Existing SaveRAM")] + [Description("When true, existing SaveRAM will be loaded at boot up")] + [DefaultValue(false)] + public bool Use_SRAM { get; set; } + + public GGLinkSyncSettings Clone() + { + return (GGLinkSyncSettings)MemberwiseClone(); + } + + public static bool NeedsReboot(GGLinkSyncSettings x, GGLinkSyncSettings y) + { + return !DeepEquality.DeepEquals(x, y); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IStatable.cs new file mode 100644 index 0000000000..eb87c70ae6 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.IStatable.cs @@ -0,0 +1,68 @@ +using System.IO; +using Newtonsoft.Json; + +using BizHawk.Common; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Sega.MasterSystem; + +namespace BizHawk.Emulation.Cores.Sega.GGHawkLink +{ + public partial class GGHawkLink : IStatable + { + public bool BinarySaveStatesPreferred => true; + + public void SaveStateText(TextWriter writer) + { + L.SaveStateText(writer); + R.SaveStateText(writer); + SyncState(new Serializer(writer)); + } + + public void LoadStateText(TextReader reader) + { + L.LoadStateText(reader); + R.LoadStateText(reader); + SyncState(new Serializer(reader)); + } + + public void SaveStateBinary(BinaryWriter bw) + { + L.SaveStateBinary(bw); + R.SaveStateBinary(bw); + // other variables + SyncState(new Serializer(bw)); + } + + public void LoadStateBinary(BinaryReader br) + { + L.LoadStateBinary(br); + R.LoadStateBinary(br); + // other variables + SyncState(new Serializer(br)); + } + + public byte[] SaveStateBinary() + { + MemoryStream ms = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(ms); + SaveStateBinary(bw); + bw.Flush(); + return ms.ToArray(); + } + + //private JsonSerializer ser = new JsonSerializer { Formatting = Formatting.Indented }; + + private void SyncState(Serializer ser) + { + ser.Sync("Lag", ref _lagcount); + ser.Sync("Frame", ref _frame); + ser.Sync("IsLag", ref _islag); + ser.Sync("_cableconnected", ref _cableconnected); + ser.Sync("_cablediscosignal", ref _cablediscosignal); + ser.Sync("do_r_next", ref do_r_next); + _controllerDeck.SyncState(ser); + + LinkConnected = _cableconnected; + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.cs b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.cs new file mode 100644 index 0000000000..394e4882e9 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLink.cs @@ -0,0 +1,91 @@ +using System; + +using BizHawk.Common.BufferExtensions; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Components.Z80A; + +using BizHawk.Emulation.Cores.Sega.MasterSystem; +using System.Runtime.InteropServices; + +namespace BizHawk.Emulation.Cores.Sega.GGHawkLink +{ + [Core( + "GGHawkLink", + "", + isPorted: false, + isReleased: false)] + [ServiceNotApplicable(typeof(IDriveLight))] + public partial class GGHawkLink : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPollable, IRegionable, ILinkable, + ISettable + { + // we want to create two GG instances that we will run concurrently + public SMS L; + public SMS R; + + // if true, the link cable is currently connected + private bool _cableconnected = true; + + // if true, the link cable toggle signal is currently asserted + private bool _cablediscosignal = false; + + private bool do_r_next = false; + + public GGHawkLink(CoreComm comm, GameInfo game_L, byte[] rom_L, GameInfo game_R, byte[] rom_R, /*string gameDbFn,*/ object settings, object syncSettings) + { + var ser = new BasicServiceProvider(this); + + linkSettings = (GGLinkSettings)settings ?? new GGLinkSettings(); + linkSyncSettings = (GGLinkSyncSettings)syncSettings ?? new GGLinkSyncSettings(); + _controllerDeck = new GGHawkLinkControllerDeck(GGHawkLinkControllerDeck.DefaultControllerName, GGHawkLinkControllerDeck.DefaultControllerName); + + CoreComm = comm; + + var temp_set_L = new SMS.SMSSettings(); + var temp_set_R = new SMS.SMSSettings(); + + var temp_sync_L = new SMS.SMSSyncSettings(); + var temp_sync_R = new SMS.SMSSyncSettings(); + + L = new SMS(new CoreComm(comm.ShowMessage, comm.Notify) { CoreFileProvider = comm.CoreFileProvider }, + game_L, rom_L, temp_set_L, temp_sync_L); + + R = new SMS(new CoreComm(comm.ShowMessage, comm.Notify) { CoreFileProvider = comm.CoreFileProvider }, + game_R, rom_R, temp_set_R, temp_sync_R); + + ser.Register(this); + ser.Register(this); + + _tracer = new TraceBuffer { Header = L.Cpu.TraceHeader }; + ser.Register(_tracer); + + ServiceProvider = ser; + + SetupMemoryDomains(); + + HardReset(); + + LinkConnected = _cableconnected; + } + + public void HardReset() + { + L.HardReset(); + R.HardReset(); + } + + public DisplayType Region => DisplayType.NTSC; + + public int _frame = 0; + + private readonly GGHawkLinkControllerDeck _controllerDeck; + + private readonly ITraceable _tracer; + + public bool LinkConnected { get; private set; } + + private void ExecFetch(ushort addr) + { + MemoryCallbacks.CallExecutes(addr, "System Bus"); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLinkControllerDeck.cs b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLinkControllerDeck.cs new file mode 100644 index 0000000000..bba074f0be --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLinkControllerDeck.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using BizHawk.Common; +using BizHawk.Common.ReflectionExtensions; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Sega.GGHawkLink +{ + public class GGHawkLinkControllerDeck + { + public GGHawkLinkControllerDeck(string controller1Name, string controller2Name) + { + if (!ValidControllerTypes.ContainsKey(controller1Name)) + { + throw new InvalidOperationException("Invalid controller type: " + controller1Name); + } + + if (!ValidControllerTypes.ContainsKey(controller2Name)) + { + throw new InvalidOperationException("Invalid controller type: " + controller2Name); + } + + Port1 = (IPort)Activator.CreateInstance(ValidControllerTypes[controller1Name], 1); + Port2 = (IPort)Activator.CreateInstance(ValidControllerTypes[controller2Name], 2); + + Definition = new ControllerDefinition + { + Name = Port1.Definition.Name, + BoolButtons = Port1.Definition.BoolButtons + .Concat(Port2.Definition.BoolButtons) + .Concat(new[] { "Toggle Cable" } ) + .ToList() + }; + } + + public byte ReadPort1(IController c) + { + return Port1.Read(c); + } + + public byte ReadPort2(IController c) + { + return Port2.Read(c); + } + + public ControllerDefinition Definition { get; } + + public void SyncState(Serializer ser) + { + ser.BeginSection("Port1"); + Port1.SyncState(ser); + ser.EndSection(); + + ser.BeginSection("Port2"); + Port2.SyncState(ser); + ser.EndSection(); + } + + private readonly IPort Port1; + private readonly IPort Port2; + + private static Dictionary _controllerTypes; + + public static Dictionary ValidControllerTypes + { + get + { + if (_controllerTypes == null) + { + _controllerTypes = typeof(GGHawkLinkControllerDeck).Assembly + .GetTypes() + .Where(t => typeof(IPort).IsAssignableFrom(t)) + .Where(t => !t.IsAbstract && !t.IsInterface) + .ToDictionary(tkey => tkey.DisplayName()); + } + + return _controllerTypes; + } + } + + public static string DefaultControllerName => typeof(StandardControls).DisplayName(); + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLinkControllers.cs b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLinkControllers.cs new file mode 100644 index 0000000000..995adeee60 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/GGHawkLinkControllers.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Sega.GGHawkLink +{ + /// + /// Represents a GG add on + /// + public interface IPort + { + byte Read(IController c); + + ControllerDefinition Definition { get; } + + void SyncState(Serializer ser); + + int PortNum { get; } + } + + [DisplayName("Game Gear Controller")] + public class StandardControls : IPort + { + public StandardControls(int portNum) + { + PortNum = portNum; + Definition = new ControllerDefinition + { + Name = "GG Controller", + BoolButtons = BaseDefinition + .Select(b => "P" + PortNum + " " + b) + .ToList() + }; + } + + public int PortNum { get; } + + public ControllerDefinition Definition { get; } + + public byte Read(IController c) + { + byte result = 0xFF; + + if (c.IsPressed(Definition.BoolButtons[0])) + { + result -= 4; + } + if (c.IsPressed(Definition.BoolButtons[1])) + { + result -= 8; + } + if (c.IsPressed(Definition.BoolButtons[2])) + { + result -= 2; + } + if (c.IsPressed(Definition.BoolButtons[3])) + { + result -= 1; + } + if (c.IsPressed(Definition.BoolButtons[4])) + { + result -= 128; + } + if (c.IsPressed(Definition.BoolButtons[5])) + { + result -= 64; + } + if (c.IsPressed(Definition.BoolButtons[6])) + { + result -= 32; + } + if (c.IsPressed(Definition.BoolButtons[7])) + { + result -= 16; + } + + return result; + } + + private static readonly string[] BaseDefinition = + { + "Up", "Down", "Left", "Right", "B1", "B2", "Start" + }; + + public void SyncState(Serializer ser) + { + //nothing + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/ReadMe.txt b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/ReadMe.txt new file mode 100644 index 0000000000..bc60bf4b01 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/GGHawkLink/ReadMe.txt @@ -0,0 +1 @@ +TODO: diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IInputPollable.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IInputPollable.cs index 82a3e637d8..ea20c139f0 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IInputPollable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IInputPollable.cs @@ -18,8 +18,8 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem public IInputCallbackSystem InputCallbacks { get; private set; } - private int _lagCount = 0; - private bool _lagged = true; - private bool _isLag = false; + public int _lagCount = 0; + public bool _lagged = true; + public bool _isLag = false; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISaveRam.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISaveRam.cs index 9e88f186c7..1b48b8ec52 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISaveRam.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISaveRam.cs @@ -28,7 +28,7 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem public bool SaveRamModified { get; private set; } - private byte[] SaveRAM; + public byte[] SaveRAM; private byte SaveRamBank; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs index bba177d817..1b67c666bc 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs @@ -213,21 +213,26 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem Cpu.Regs[Cpu.SPh] = 0xDF; } + public void HardReset() + { + + } + // Constants private const int BankSize = 16384; // ROM - private byte[] RomData; + public byte[] RomData; private byte RomBank0, RomBank1, RomBank2, RomBank3; private byte Bios_bank; private byte RomBanks; private byte[] BiosRom; // Machine resources - private Z80A Cpu; - private byte[] SystemRam; + public Z80A Cpu; + public byte[] SystemRam; public VDP Vdp; - private SN76489 PSG; + public SN76489 PSG; private YM2413 YM2413; public bool IsGameGear { get; set; } public bool IsGameGear_C { get; set; } @@ -281,21 +286,21 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem return DisplayType.NTSC; } - private byte ReadMemory(ushort addr) + public byte ReadMemory(ushort addr) { MemoryCallbacks.CallReads(addr, "System Bus"); return ReadMemoryMapper(addr); } - private void WriteMemory(ushort addr, byte value) + public void WriteMemory(ushort addr, byte value) { WriteMemoryMapper(addr, value); MemoryCallbacks.CallWrites(addr, "System Bus"); } - private byte FetchMemory(ushort addr) + public byte FetchMemory(ushort addr) { return ReadMemoryMapper(addr); }