diff --git a/.gitmodules b/.gitmodules index a82b03f8c9..1a5285b035 100644 --- a/.gitmodules +++ b/.gitmodules @@ -40,3 +40,6 @@ [submodule "submodules/libfwunpack"] path = submodules/libfwunpack url = https://github.com/TASEmulators/fwunpack +[submodule "submodules/sameboy/libsameboy"] + path = submodules/sameboy/libsameboy + url = https://github.com/LIJI32/SameBoy diff --git a/Assets/dll/libsameboy.dll b/Assets/dll/libsameboy.dll new file mode 100644 index 0000000000..f453747b4c Binary files /dev/null and b/Assets/dll/libsameboy.dll differ diff --git a/Assets/dll/libsameboy.so b/Assets/dll/libsameboy.so new file mode 100755 index 0000000000..8157e20135 Binary files /dev/null and b/Assets/dll/libsameboy.so differ diff --git a/src/BizHawk.Client.Common/config/Config.cs b/src/BizHawk.Client.Common/config/Config.cs index 9aa3947a40..840fddfae3 100644 --- a/src/BizHawk.Client.Common/config/Config.cs +++ b/src/BizHawk.Client.Common/config/Config.cs @@ -29,7 +29,7 @@ namespace BizHawk.Client.Common (new[] { VSystemID.Raw.SGB }, new[] { CoreNames.Gambatte, CoreNames.Bsnes, CoreNames.Bsnes115}), (new[] { VSystemID.Raw.GB, VSystemID.Raw.GBC }, - new[] { CoreNames.Gambatte, CoreNames.GbHawk, CoreNames.SubGbHawk }), + new[] { CoreNames.Gambatte, CoreNames.Sameboy, CoreNames.GbHawk, CoreNames.SubGbHawk }), (new[] { VSystemID.Raw.GBL }, new[] { CoreNames.GambatteLink, CoreNames.GBHawkLink, CoreNames.GBHawkLink3x, CoreNames.GBHawkLink4x }), (new[] { VSystemID.Raw.PCE, VSystemID.Raw.PCECD, VSystemID.Raw.SGX }, diff --git a/src/BizHawk.Client.Common/movie/MovieSession.cs b/src/BizHawk.Client.Common/movie/MovieSession.cs index a2f4471f6f..02babb4b17 100644 --- a/src/BizHawk.Client.Common/movie/MovieSession.cs +++ b/src/BizHawk.Client.Common/movie/MovieSession.cs @@ -5,6 +5,7 @@ using System.Linq; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores; using BizHawk.Emulation.Cores.Nintendo.Gameboy; +using BizHawk.Emulation.Cores.Nintendo.Sameboy; using BizHawk.Emulation.Cores.Nintendo.SubNESHawk; using BizHawk.Emulation.Cores.Nintendo.SubGBHawk; @@ -363,6 +364,12 @@ namespace BizHawk.Client.Common coreValue = ((Gameboy)Movie.Emulator).CycleCount; movieHasValue = Movie.HeaderEntries.TryGetValue(HeaderKeys.CycleCount, out movieValueStr); } + else if (Movie.Core == CoreNames.Sameboy) + { + valueName = "cycle"; + coreValue = ((Sameboy)Movie.Emulator).CycleCount; + movieHasValue = Movie.HeaderEntries.TryGetValue(HeaderKeys.CycleCount, out movieValueStr); + } else if (Movie.Core == CoreNames.SubGbHawk) { valueName = "cycle"; diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs index d77c751241..7f58ca38b0 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs @@ -83,6 +83,10 @@ namespace BizHawk.Client.Common { Header[HeaderKeys.CycleCount] = gameboy.CycleCount.ToString(); } + else if (Emulator is Emulation.Cores.Nintendo.Sameboy.Sameboy sameboy) + { + Header[HeaderKeys.CycleCount] = sameboy.CycleCount.ToString(); + } else if (Emulator is Emulation.Cores.Nintendo.SubGBHawk.SubGBHawk subGb) { Header[HeaderKeys.CycleCount] = subGb.CycleCount.ToString(); diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Debug.cs b/src/BizHawk.Client.EmuHawk/MainForm.Debug.cs index 47bb9231c2..cdcc04ebf7 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Debug.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Debug.cs @@ -14,6 +14,7 @@ using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores; using BizHawk.Emulation.Cores.Nintendo.GBA; using BizHawk.Emulation.Cores.Nintendo.N64; +using BizHawk.Emulation.Cores.Nintendo.Sameboy; using BizHawk.WinForms.Controls; namespace BizHawk.Client.EmuHawk @@ -186,6 +187,18 @@ namespace BizHawk.Client.EmuHawk Text = "Firmware", }, new ToolStripSeparatorEx(), + new DebugVSystemMenuItem("GB") + { + DropDownItems = + { + new DebugVSystemChildItem( + "Debug SameBoy States", + () => ((Sameboy) Emulator).DebugSameBoyState()) + { + RequiresCore = CoreNames.Sameboy, + }, + }, + }, new DebugVSystemMenuItem("GBA") { DropDownItems = diff --git a/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index e695c56298..7d2e3ed670 100644 --- a/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/src/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -60,6 +60,7 @@ + diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index c7a149b30c..97d16d8f1b 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -198,7 +198,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy _cdCallback = new LibGambatte.CDCallback(CDCallbackProc); - ControllerDefinition = CreateControllerDefinition(IsSgb, _syncSettings.FrameLength is GambatteSyncSettings.FrameLengthType.UserDefinedFrames); + ControllerDefinition = CreateControllerDefinition(sgb: IsSgb, sub: _syncSettings.FrameLength is GambatteSyncSettings.FrameLengthType.UserDefinedFrames, tilt: false); NewSaveCoreSetBuff(); } @@ -263,11 +263,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public long CycleCount => (long)_cycleCount; public double ClockRate => TICKSPERSECOND; - public static ControllerDefinition CreateControllerDefinition(bool sgb, bool sub) + public static ControllerDefinition CreateControllerDefinition(bool sgb, bool sub, bool tilt) { - var ret = sub - ? new ControllerDefinition("Subframe Gameboy Controller").AddAxis("Input Length", 0.RangeTo(35112), 35112) - : new ControllerDefinition("Gameboy Controller"); + var ret = new ControllerDefinition((sub ? "Subframe " : "") + "Gameboy Controller" + (tilt ? " + Tilt" : "")); + if (sub) + { + ret.AddAxis("Input Length", 0.RangeTo(35112), 35112); + } + if (tilt) + { + ret.AddXYPair($"Tilt {{0}}", AxisPairOrientation.RightAndUp, (-90).RangeTo(90), 0); + } if (sgb) { for (int i = 0; i < 4; i++) diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs index f7972ae734..59778a9176 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs @@ -49,7 +49,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy _linkedCores[i] = new Gameboy(lp.Comm, lp.Roms[i].Game, lp.Roms[i].RomData, _settings._linkedSettings[i], _syncSettings._linkedSyncSettings[i], lp.DeterministicEmulationRequested); _linkedCores[i].ConnectInputCallbackSystem(_inputCallbacks); _linkedCores[i].ConnectMemoryCallbackSystem(_memoryCallbacks, i); - _linkedConts[i] = new SaveController(Gameboy.CreateControllerDefinition(false, false)); + _linkedConts[i] = new SaveController(Gameboy.CreateControllerDefinition(sgb: false, sub: false, tilt: false)); _linkedBlips[i] = new BlipBuffer(1024); _linkedBlips[i].SetRates(2097152 * 2, 44100); _linkedOverflow[i] = 0; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/LibSameBoy.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/LibSameBoy.cs new file mode 100644 index 0000000000..9c53a6dd16 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/LibSameBoy.cs @@ -0,0 +1,152 @@ +using System; +using System.Runtime.InteropServices; + +using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy; + +namespace BizHawk.Emulation.Cores.Nintendo.Sameboy +{ + /// + /// static bindings into libsameboy.dll + /// + public static class LibSameboy + { + [Flags] + public enum Buttons : uint + { + RIGHT = 0x01, + LEFT = 0x02, + UP = 0x04, + DOWN = 0x08, + A = 0x10, + B = 0x20, + SELECT = 0x40, + START = 0x80, + } + + // mirror of GB_direct_access_t + public enum MemoryAreas : uint + { + ROM, + RAM, + CART_RAM, + VRAM, + HRAM, + IO, + BOOTROM, + OAM, + BGP, + OBP, + IE, + BGPRGB, + OBPRGB, + } + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr sameboy_create(byte[] romdata, int romlength, byte[] biosdata, int bioslength, Sameboy.SameboySyncSettings.GBModel model, bool realtime); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_destroy(IntPtr core); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void SampleCallback(IntPtr core, IntPtr sample); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setsamplecallback(IntPtr core, SampleCallback callback); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void InputCallback(); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setinputcallback(IntPtr core, InputCallback callback); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_frameadvance(IntPtr core, Buttons buttons, ushort x, ushort y, int[] videobuf, bool render, bool border); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_reset(IntPtr core); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern bool sameboy_iscgbdmg(IntPtr core); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_savesram(IntPtr core, byte[] dest); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_loadsram(IntPtr core, byte[] data, int len); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern int sameboy_sramlen(IntPtr core); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_savestate(IntPtr core, byte[] data); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern bool sameboy_loadstate(IntPtr core, byte[] data, int len); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern int sameboy_statelen(IntPtr core); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern bool sameboy_getmemoryarea(IntPtr core, MemoryAreas which, ref IntPtr data, ref int length); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern byte sameboy_cpuread(IntPtr core, ushort addr); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_cpuwrite(IntPtr core, ushort addr, byte val); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern long sameboy_getcyclecount(IntPtr core); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setcyclecount(IntPtr core, long newcc); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void TraceCallback(ushort pc); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_settracecallback(IntPtr core, TraceCallback callback); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_getregs(IntPtr core, int[] buf); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setreg(IntPtr core, int which, int value); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void MemoryCallback(ushort address); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setmemorycallback(IntPtr core, int which, MemoryCallback callback); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setprintercallback(IntPtr core, PrinterCallback callback); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setscanlinecallback(IntPtr core, ScanlineCallback callback, int sl); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setpalette(IntPtr core, Sameboy.SameboySettings.GBPaletteType which); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setcolorcorrection(IntPtr core, Sameboy.SameboySettings.ColorCorrectionMode which); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setlighttemperature(IntPtr core, int temperature); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_sethighpassfilter(IntPtr core, Sameboy.SameboySettings.HighPassFilterMode which); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setinterferencevolume(IntPtr core, int volume); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setrtcdivisoroffset(IntPtr core, int offset); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setbgwinenabled(IntPtr core, bool enabled); + + [DllImport("libsameboy", CallingConvention = CallingConvention.Cdecl)] + public static extern void sameboy_setobjenabled(IntPtr core, bool enabled); + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IDebuggable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IDebuggable.cs new file mode 100644 index 0000000000..f242e71761 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IDebuggable.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.Sameboy +{ + public partial class Sameboy : IDebuggable + { + public IDictionary GetCpuFlagsAndRegisters() + { + int[] data = new int[10]; + LibSameboy.sameboy_getregs(SameboyState, data); + + return new Dictionary + { + ["PC"] = (ushort)(data[0] & 0xFFFF), + ["A"] = (byte)(data[1] & 0xFF), + ["F"] = (byte)(data[2] & 0xFF), + ["B"] = (byte)(data[3] & 0xFF), + ["C"] = (byte)(data[4] & 0xFF), + ["D"] = (byte)(data[5] & 0xFF), + ["E"] = (byte)(data[6] & 0xFF), + ["H"] = (byte)(data[7] & 0xFF), + ["L"] = (byte)(data[8] & 0xFF), + ["SP"] = (ushort)(data[9] & 0xFFFF), + }; + } + + public void SetCpuRegister(string register, int value) + { + LibSameboy.sameboy_setreg(SameboyState, register.ToUpper() switch + { + "PC" => 0, + "A" => 1, + "F" => 2, + "B" => 3, + "C" => 4, + "D" => 5, + "E" => 6, + "H" => 7, + "L" => 8, + "SP" => 9, + _ => throw new InvalidOperationException("Invalid Register?"), + }, + value); + } + + public bool CanStep(StepType type) => false; + + [FeatureNotImplemented] + public void Step(StepType type) => throw new NotImplementedException(); + + public long TotalExecutedCycles => CycleCount; + + private const string systemBusScope = "System Bus"; + + private readonly MemoryCallbackSystem _memorycallbacks = new MemoryCallbackSystem(new[] { systemBusScope }); + + public IMemoryCallbackSystem MemoryCallbacks => _memorycallbacks; + + private LibSameboy.MemoryCallback _readcb; + private LibSameboy.MemoryCallback _writecb; + private LibSameboy.MemoryCallback _execcb; + + private void InitMemoryCallbacks() + { + LibSameboy.MemoryCallback CreateCallback(MemoryCallbackFlags flags, Func getHasCBOfType) + { + var rawFlags = (uint)flags; + return (address) => + { + if (getHasCBOfType()) + { + MemoryCallbacks.CallMemoryCallbacks(address, 0, rawFlags, systemBusScope); + } + }; + } + + _readcb = CreateCallback(MemoryCallbackFlags.AccessRead, () => MemoryCallbacks.HasReads); + _writecb = CreateCallback(MemoryCallbackFlags.AccessWrite, () => MemoryCallbacks.HasWrites); + _execcb = CreateCallback(MemoryCallbackFlags.AccessExecute, () => MemoryCallbacks.HasExecutes); + + _memorycallbacks.ActiveChanged += SetMemoryCallbacks; + } + + private void SetMemoryCallbacks() + { + LibSameboy.sameboy_setmemorycallback(SameboyState, 0, MemoryCallbacks.HasReads ? _readcb : null); + LibSameboy.sameboy_setmemorycallback(SameboyState, 1, MemoryCallbacks.HasWrites ? _writecb : null); + LibSameboy.sameboy_setmemorycallback(SameboyState, 2, MemoryCallbacks.HasExecutes ? _execcb : null); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IEmulator.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IEmulator.cs new file mode 100644 index 0000000000..5d388d1ccd --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IEmulator.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.Sameboy +{ + public partial class Sameboy : IEmulator + { + public IEmulatorServiceProvider ServiceProvider => _serviceProvider; + + public ControllerDefinition ControllerDefinition { get; } + + private static readonly IReadOnlyList GB_BUTTON_ORDER_IN_BITMASK = new[] { "Start", "Select", "B", "A", "Down", "Up", "Left", "Right", }; + + private LibSameboy.Buttons FrameAdvancePrep(IController controller) + { + uint b = 0; + for (var i = 0; i < 8; i++) + { + b <<= 1; + if (controller.IsPressed(GB_BUTTON_ORDER_IN_BITMASK[i])) b |= 1; + } + + if (controller.IsPressed("Power")) + { + LibSameboy.sameboy_reset(SameboyState); + } + + IsLagFrame = true; + + LibSameboy.sameboy_settracecallback(SameboyState, Tracer.IsEnabled() ? _tracecb : null); + + return (LibSameboy.Buttons)b; + } + + // copy pasting GBHawk here... + + private readonly bool _hasAcc; + private double theta, phi, theta_prev, phi_prev, phi_prev_2; + + private ushort GetAccX(IController c) + { + if (!_hasAcc) + { + return 0; + } + + theta_prev = theta; + phi_prev_2 = phi_prev; + phi_prev = phi; + + theta = c.AxisValue("Tilt Y") * Math.PI / 180.0; + phi = c.AxisValue("Tilt X") * Math.PI / 180.0; + + double temp = (double)(Math.Cos(theta) * Math.Sin(phi)); + + double temp2 = (double)((phi - 2 * phi_prev + phi_prev_2) * 59.7275 * 59.7275 * 0.1); + + return (ushort)(0x8370 - Math.Floor(temp * 216) - temp2); + } + + private ushort GetAccY() + { + if (!_hasAcc) + { + return 0; + } + + double temp = (double)Math.Sin(theta); + + double temp2 = (double)(Math.Pow((theta - theta_prev) * 59.7275, 2) * 0.15); + + return (ushort)(0x8370 - Math.Floor(temp * 216) + temp2); + } + + public bool FrameAdvance(IController controller, bool render, bool rendersound) + { + var buttons = FrameAdvancePrep(controller); + + LibSameboy.sameboy_frameadvance(SameboyState, buttons, GetAccX(controller), GetAccY(), VideoBuffer, render, _settings.ShowBorder); + + if (!rendersound) + { + DiscardSamples(); + } + + FrameAdvancePost(); + + return true; + } + + private void FrameAdvancePost() + { + if (IsLagFrame) + { + LagCount++; + } + + Frame++; + + if (_scanlinecbline == -1) + { + _scanlinecb?.Invoke(LibSameboy.sameboy_cpuread(SameboyState, 0xFF40)); + } + } + + public int Frame { get; private set; } = 0; + + public string SystemId => VSystemID.Raw.GB; + + public bool DeterministicEmulation { get; } + + public void ResetCounters() + { + Frame = 0; + LagCount = 0; + IsLagFrame = false; + CycleCount = 0; + } + + public void Dispose() + { + if (SameboyState != IntPtr.Zero) + { + LibSameboy.sameboy_destroy(SameboyState); + SameboyState = IntPtr.Zero; + } + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IMemoryDomains.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IMemoryDomains.cs new file mode 100644 index 0000000000..1b6139b5f6 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IMemoryDomains.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.Sameboy +{ + public partial class Sameboy + { + private readonly List _memoryDomains = new List(); + + private IMemoryDomains MemoryDomains { get; set; } + + private void CreateMemoryDomain(LibSameboy.MemoryAreas which, string name) + { + IntPtr data = IntPtr.Zero; + int length = 0; + + if (!LibSameboy.sameboy_getmemoryarea(SameboyState, which, ref data, ref length)) + { + throw new Exception($"{nameof(LibSameboy.sameboy_getmemoryarea)}() failed!"); + } + + // if length == 0, it's an empty block; (usually rambank on some carts); that's ok + if (data != IntPtr.Zero && length > 0) + { + _memoryDomains.Add(new MemoryDomainIntPtr(name, MemoryDomain.Endian.Little, data, length, true, 1)); + } + } + + private void InitMemoryDomains() + { + CreateMemoryDomain(LibSameboy.MemoryAreas.ROM, "ROM"); + CreateMemoryDomain(LibSameboy.MemoryAreas.RAM, "WRAM"); + CreateMemoryDomain(LibSameboy.MemoryAreas.CART_RAM, "CartRAM"); + CreateMemoryDomain(LibSameboy.MemoryAreas.VRAM, "VRAM"); + CreateMemoryDomain(LibSameboy.MemoryAreas.HRAM, "HRAM"); + CreateMemoryDomain(LibSameboy.MemoryAreas.IO, "MMIO"); + CreateMemoryDomain(LibSameboy.MemoryAreas.BOOTROM, "BIOS"); + CreateMemoryDomain(LibSameboy.MemoryAreas.OAM, "OAM"); + CreateMemoryDomain(LibSameboy.MemoryAreas.BGP, "BGP"); + CreateMemoryDomain(LibSameboy.MemoryAreas.OBP, "OBP"); + CreateMemoryDomain(LibSameboy.MemoryAreas.IE, "IE"); + + // also add a special memory domain for the system bus, where calls get sent directly to the core each time + _memoryDomains.Add(new MemoryDomainDelegate("System Bus", 65536, MemoryDomain.Endian.Little, + delegate(long addr) + { + if (addr < 0 || addr >= 65536) + { + throw new ArgumentOutOfRangeException(); + } + + return LibSameboy.sameboy_cpuread(SameboyState, (ushort)addr); + }, + delegate(long addr, byte val) + { + if (addr < 0 || addr >= 65536) + { + throw new ArgumentOutOfRangeException(); + } + + LibSameboy.sameboy_cpuwrite(SameboyState, (ushort)addr, val); + }, 1)); + + MemoryDomains = new MemoryDomainList(_memoryDomains); + _serviceProvider.Register(MemoryDomains); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ISaveRam.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ISaveRam.cs new file mode 100644 index 0000000000..c258cc2045 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ISaveRam.cs @@ -0,0 +1,36 @@ +using System; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.Sameboy +{ + public partial class Sameboy : ISaveRam + { + public bool SaveRamModified => LibSameboy.sameboy_sramlen(SameboyState) != 0; + + public byte[] CloneSaveRam() + { + int length = LibSameboy.sameboy_sramlen(SameboyState); + + if (length > 0) + { + byte[] ret = new byte[length]; + LibSameboy.sameboy_savesram(SameboyState, ret); + return ret; + } + + return Array.Empty(); + } + + public void StoreSaveRam(byte[] data) + { + int expected = LibSameboy.sameboy_sramlen(SameboyState); + if (data.Length - expected != 0) + { + throw new ArgumentException("Size of saveram data does not match expected!"); + } + + LibSameboy.sameboy_loadsram(SameboyState, data, data.Length); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ISettable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ISettable.cs new file mode 100644 index 0000000000..46d01f8564 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ISettable.cs @@ -0,0 +1,205 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +using Newtonsoft.Json; + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.Sameboy +{ + public partial class Sameboy : ISettable + { + private SameboySettings _settings; + private SameboySyncSettings _syncSettings; + + public SameboySettings GetSettings() => _settings.Clone(); + + public PutSettingsDirtyBits PutSettings(SameboySettings o) + { + LibSameboy.sameboy_setpalette(SameboyState, o.GBPalette); + LibSameboy.sameboy_setcolorcorrection(SameboyState, o.ColorCorrection); + LibSameboy.sameboy_setlighttemperature(SameboyState, o.LightTemperature); + LibSameboy.sameboy_sethighpassfilter(SameboyState, o.HighPassFilter); + LibSameboy.sameboy_setinterferencevolume(SameboyState, o.InterferenceVolume); + LibSameboy.sameboy_setbgwinenabled(SameboyState, o.EnableBGWIN); + LibSameboy.sameboy_setobjenabled(SameboyState, o.EnableOBJ); + _disassembler.UseRGBDSSyntax = o.UseRGBDSSyntax; + _settings = o; + return PutSettingsDirtyBits.None; + } + + public SameboySyncSettings GetSyncSettings() => _syncSettings.Clone(); + + public PutSettingsDirtyBits PutSyncSettings(SameboySyncSettings o) + { + bool ret = SameboySyncSettings.NeedsReboot(_syncSettings, o); + _syncSettings = o; + return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None; + } + + public class SameboySettings + { + public enum GBPaletteType : uint + { + [Display(Name = "Greyscale")] + GREY, + [Display(Name = "Lime (Game Boy)")] + DMG, + [Display(Name = "Olive (Game Boy Pocket)")] + MGB, + [Display(Name = "Teal (Game Boy Light)")] + GBL, + } + + [DisplayName("GB Mono Palette")] + [Description("Selects which palette to use in GB mode. Does nothing in GBC mode.")] + [DefaultValue(GBPaletteType.GREY)] + [TypeConverter(typeof(DescribableEnumConverter))] + public GBPaletteType GBPalette { get; set; } + + public enum ColorCorrectionMode : uint + { + [Display(Name = "Disabled")] + DISABLED, + [Display(Name = "Correct Color Curves")] + CORRECT_CURVES, + [Display(Name = "Emulate Hardware")] + EMULATE_HARDWARE, + [Display(Name = "Preserve Brightness")] + PRESERVE_BRIGHTNESS, + [Display(Name = "Reduce Contrast")] + REDUCE_CONTRAST, + [Display(Name = "Harsh Reality")] + LOW_CONTRAST, + } + + [DisplayName("GBC Color Correction")] + [Description("Selects which color correction method to use in GBC mode. Does nothing in GB mode.")] + [DefaultValue(ColorCorrectionMode.EMULATE_HARDWARE)] + [TypeConverter(typeof(DescribableEnumConverter))] + public ColorCorrectionMode ColorCorrection { get; set; } + + [JsonIgnore] + private int _lighttemperature; + + [DisplayName("Ambient Light Temperature")] + [Description("Simulates an ambient light's effect on non-backlit screens. Does nothing in GB mode.")] + [DefaultValue(0)] + public int LightTemperature + { + get => _lighttemperature; + set => _lighttemperature = Math.Max(-10, Math.Min(10, value)); + } + + [DisplayName("Show Border")] + [Description("")] + [DefaultValue(false)] + public bool ShowBorder { get; set; } + + public enum HighPassFilterMode : uint + { + [Display(Name = "None (Keep DC Offset)")] + HIGHPASS_OFF, + [Display(Name = "Accurate")] + HIGHPASS_ACCURATE, + [Display(Name = "Preserve Waveform")] + HIGHPASS_REMOVE_DC_OFFSET, + } + + [DisplayName("High Pass Filter")] + [Description("Selects which high pass filter to use for audio.")] + [DefaultValue(HighPassFilterMode.HIGHPASS_ACCURATE)] + [TypeConverter(typeof(DescribableEnumConverter))] + public HighPassFilterMode HighPassFilter { get; set; } + + [JsonIgnore] + private int _interferencevolume; + + [DisplayName("Audio Interference Volume")] + [Description("Sets the volume of audio interference.")] + [DefaultValue(0)] + public int InterferenceVolume + { + get => _interferencevolume; + set => _interferencevolume = Math.Max(0, Math.Min(100, value)); + } + + [DisplayName("Enable Background/Window")] + [Description("")] + [DefaultValue(true)] + public bool EnableBGWIN { get; set; } + + [DisplayName("Enable Objects")] + [Description("")] + [DefaultValue(true)] + public bool EnableOBJ { get; set; } + + [DisplayName("Use RGBDS Syntax")] + [Description("Uses RGBDS syntax for disassembling.")] + [DefaultValue(true)] + public bool UseRGBDSSyntax { get; set; } + + public SameboySettings() => SettingsUtil.SetDefaultValues(this); + + public SameboySettings Clone() => MemberwiseClone() as SameboySettings; + } + + public class SameboySyncSettings + { + [DisplayName("Use official BIOS")] + [Description("When false, SameBoy's internal bios is used. The official bios should be used for TASing.")] + [DefaultValue(false)] + public bool EnableBIOS { get; set; } + + public enum GBModel : short + { + Auto = -1, + // GB_MODEL_DMG_0 = 0x000, + // GB_MODEL_DMG_A = 0x001, + [Display(Name = "DMG-B")] + GB_MODEL_DMG_B = 0x002, + // GB_MODEL_DMG_C = 0x003, + [Display(Name = "MGB")] + GB_MODEL_MGB = 0x100, + [Display(Name = "CGB-0 (Experimental)")] + GB_MODEL_CGB_0 = 0x200, + [Display(Name = "CGB-A (Experimental)")] + GB_MODEL_CGB_A = 0x201, + [Display(Name = "CGB-B (Experimental)")] + GB_MODEL_CGB_B = 0x202, + [Display(Name = "CGB-C (Experimental)")] + GB_MODEL_CGB_C = 0x203, + [Display(Name = "CGB-D")] + GB_MODEL_CGB_D = 0x204, + [Display(Name = "CGB-E")] + GB_MODEL_CGB_E = 0x205, + [Display(Name = "AGB")] + GB_MODEL_AGB = 0x206, + } + + [DisplayName("Console Mode")] + [Description("Pick which console to run, 'Auto' chooses from ROM header. DMG-B, CGB-E, and AGB are the best options for GB, GBC, and GBA, respectively.")] + [DefaultValue(GBModel.Auto)] + [TypeConverter(typeof(DescribableEnumConverter))] + public GBModel ConsoleMode { get; set; } + + [DisplayName("Use Real Time")] + [Description("If true, RTC clock will be based off of real time instead of emulated time. Ignored (set to false) when recording a movie.")] + [DefaultValue(false)] + public bool UseRealTime { get; set; } + + [DisplayName("RTC Divisor Offset")] + [Description("CPU clock frequency relative to real time clock. Base value is 2^22 Hz. Used in cycle-based RTC to sync on real hardware to account for RTC imperfections.")] + [DefaultValue(0)] + public int RTCDivisorOffset { get; set; } + + public SameboySyncSettings() => SettingsUtil.SetDefaultValues(this); + + public SameboySyncSettings Clone() => MemberwiseClone() as SameboySyncSettings; + + public static bool NeedsReboot(SameboySyncSettings x, SameboySyncSettings y) => !DeepEquality.DeepEquals(x, y); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ISoundProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ISoundProvider.cs new file mode 100644 index 0000000000..11e7410de4 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ISoundProvider.cs @@ -0,0 +1,50 @@ +using System; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.Sameboy +{ + public partial class Sameboy : ISoundProvider + { + public bool CanProvideAsync => false; + + public void DiscardSamples() + { + _soundoutbuffcontains = 0; + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + samples = _soundoutbuff; + nsamp = _soundoutbuffcontains; + DiscardSamples(); + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode => SyncSoundMode.Sync; + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + + private int _soundoutbuffcontains = 0; + + private readonly short[] _soundoutbuff = new short[2048]; + + private unsafe void QueueSample(IntPtr core, IntPtr sample) + { + short* s = (short*)sample; + _soundoutbuff[_soundoutbuffcontains * 2] = s[0]; + _soundoutbuff[_soundoutbuffcontains * 2 + 1] = s[1]; + _soundoutbuffcontains++; + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IStatable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IStatable.cs new file mode 100644 index 0000000000..3cedb37ad0 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IStatable.cs @@ -0,0 +1,67 @@ +using System; +using System.IO; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.Sameboy +{ + public partial class Sameboy : IStatable + { + private readonly byte[] _stateBuf; + + public void SaveStateBinary(BinaryWriter writer) + { + LibSameboy.sameboy_savestate(SameboyState, _stateBuf); + + writer.Write(_stateBuf.Length); + writer.Write(_stateBuf); + + // other variables + writer.Write(IsLagFrame); + writer.Write(LagCount); + writer.Write(Frame); + writer.Write(IsCgb); + writer.Write(CycleCount); + writer.Write(theta); + writer.Write(phi); + writer.Write(theta_prev); + writer.Write(phi_prev); + writer.Write(phi_prev_2); + } + + public void LoadStateBinary(BinaryReader reader) + { + int length = reader.ReadInt32(); + if (length != _stateBuf.Length) + { + throw new InvalidOperationException("Savestate buffer size mismatch!"); + } + + reader.Read(_stateBuf, 0, _stateBuf.Length); + + if (LibSameboy.sameboy_loadstate(SameboyState, _stateBuf, _stateBuf.Length)) + { + throw new Exception($"{nameof(LibSameboy.sameboy_loadstate)}() returned true"); + } + + // other variables + IsLagFrame = reader.ReadBoolean(); + LagCount = reader.ReadInt32(); + Frame = reader.ReadInt32(); + IsCgb = reader.ReadBoolean(); + CycleCount = reader.ReadInt64(); + theta = reader.ReadDouble(); + phi = reader.ReadDouble(); + theta_prev = reader.ReadDouble(); + phi_prev = reader.ReadDouble(); + phi_prev_2 = reader.ReadDouble(); + } + + public void DebugSameBoyState() + { + LibSameboy.sameboy_savestate(SameboyState, _stateBuf); + Directory.CreateDirectory("sameboy_debug"); + File.WriteAllBytes($"sameboy_debug/debug_state{Frame}.bin", _stateBuf); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ITraceable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ITraceable.cs new file mode 100644 index 0000000000..0666417604 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.ITraceable.cs @@ -0,0 +1,38 @@ +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Components.LR35902; + +namespace BizHawk.Emulation.Cores.Nintendo.Sameboy +{ + public partial class Sameboy + { + private ITraceable Tracer { get; } + private readonly LibSameboy.TraceCallback _tracecb; + + private void MakeTrace(ushort pc) + { + int[] s = new int[10]; + LibSameboy.sameboy_getregs(SameboyState, s); + + Tracer.Put(new( + disassembly: LR35902.Disassemble( + pc, + addr => LibSameboy.sameboy_cpuread(SameboyState, addr), + _settings.UseRGBDSSyntax, + out _).PadRight(36), + registerInfo: string.Format( + "A:{0:x2} F:{1:x2} B:{2:x2} C:{3:x2} D:{4:x2} E:{5:x2} H:{6:x2} L:{7:x2} SP:{8:x4} LY:{9:x2} Cy:{10}", + s[1] & 0xFF, + s[2] & 0xFF, + s[3] & 0xFF, + s[4] & 0xFF, + s[5] & 0xFF, + s[6] & 0xFF, + s[7] & 0xFF, + s[8] & 0xFF, + s[9] & 0xFFFF, + LibSameboy.sameboy_cpuread(SameboyState, 0xFF44), + CycleCount + 485808 + ))); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IVideoProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IVideoProvider.cs new file mode 100644 index 0000000000..7b11766f21 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.IVideoProvider.cs @@ -0,0 +1,38 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.Sameboy +{ + public partial class Sameboy : IVideoProvider + { + private readonly int[] VideoBuffer = CreateVideoBuffer(); + + private static int[] CreateVideoBuffer() + { + var b = new int[256 * 224]; + for (int i = 0; i < (256 * 224); i++) + { + b[i] = -1; + } + return b; + } + + public int[] GetVideoBuffer() + { + return VideoBuffer; + } + + public int VirtualWidth => _settings.ShowBorder ? 256 : 160; + + public int VirtualHeight => _settings.ShowBorder ? 224 : 144; + + public int BufferWidth => _settings.ShowBorder ? 256 : 160; + + public int BufferHeight => _settings.ShowBorder ? 224 : 144; + + public int BackgroundColor => 0; + + public int VsyncNumerator => 262144; + + public int VsyncDenominator => 4389; + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.cs new file mode 100644 index 0000000000..f9634d7cdc --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SameBoy/SameBoy.cs @@ -0,0 +1,238 @@ +using System; +using System.IO; + +using BizHawk.Common; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy; +using BizHawk.Emulation.Cores.Properties; + +namespace BizHawk.Emulation.Cores.Nintendo.Sameboy +{ + /// + /// a gameboy/gameboy color emulator wrapped around native C libsameboy + /// + [PortedCore(CoreNames.Sameboy, "LIJI32", "0.14.7", "https://github.com/LIJI32/SameBoy")] + [ServiceNotApplicable(new[] { typeof(IDriveLight) })] + public partial class Sameboy : ICycleTiming, IInputPollable, ILinkable, IRomInfo, IBoardInfo, IGameboyCommon + { + private readonly BasicServiceProvider _serviceProvider; + + private readonly Gameboy.GBDisassembler _disassembler; + + private IntPtr SameboyState { get; set; } = IntPtr.Zero; + + public bool IsCgb { get; set; } + + public bool IsCGBMode() => IsCgb; + + public bool IsCGBDMGMode() => LibSameboy.sameboy_iscgbdmg(SameboyState); + + private readonly LibSameboy.SampleCallback _samplecb; + private readonly LibSameboy.InputCallback _inputcb; + + [CoreConstructor(VSystemID.Raw.GB)] + [CoreConstructor(VSystemID.Raw.GBC)] + public Sameboy(CoreComm comm, GameInfo game, byte[] file, SameboySettings settings, SameboySyncSettings syncSettings, bool deterministic) + { + _serviceProvider = new BasicServiceProvider(this); + + _settings = settings ?? new SameboySettings(); + _syncSettings = syncSettings ?? new SameboySyncSettings(); + + var model = _syncSettings.ConsoleMode; + if (model is SameboySyncSettings.GBModel.Auto) + { + model = game.System == VSystemID.Raw.GBC + ? SameboySyncSettings.GBModel.GB_MODEL_CGB_E + : SameboySyncSettings.GBModel.GB_MODEL_DMG_B; + } + + IsCgb = model >= SameboySyncSettings.GBModel.GB_MODEL_CGB_0; + + byte[] bios; + if (_syncSettings.EnableBIOS) + { + FirmwareID fwid = new( + IsCgb ? "GBC" : "GB", + _syncSettings.ConsoleMode is SameboySyncSettings.GBModel.GB_MODEL_AGB + ? "AGB" + : "World"); + bios = comm.CoreFileProvider.GetFirmwareOrThrow(fwid, "BIOS Not Found, Cannot Load. Change SyncSettings to run without BIOS."); + } + else + { + bios = Util.DecompressGzipFile(new MemoryStream(IsCgb + ? _syncSettings.ConsoleMode is SameboySyncSettings.GBModel.GB_MODEL_AGB ? Resources.SameboyAgbBoot.Value : Resources.SameboyCgbBoot.Value + : Resources.SameboyDmgBoot.Value)); + } + + DeterministicEmulation = false; + + bool realtime = true; + if (!_syncSettings.UseRealTime || deterministic) + { + realtime = false; + DeterministicEmulation = true; + } + + SameboyState = LibSameboy.sameboy_create(file, file.Length, bios, bios.Length, model, realtime); + + InitMemoryDomains(); + InitMemoryCallbacks(); + + _samplecb = QueueSample; + LibSameboy.sameboy_setsamplecallback(SameboyState, _samplecb); + _inputcb = InputCallback; + LibSameboy.sameboy_setinputcallback(SameboyState, _inputcb); + _tracecb = MakeTrace; + LibSameboy.sameboy_settracecallback(SameboyState, null); + + LibSameboy.sameboy_setscanlinecallback(SameboyState, null, 0); + LibSameboy.sameboy_setprintercallback(SameboyState, null); + + const string TRACE_HEADER = "SM83: PC, opcode, registers (A, F, B, C, D, E, H, L, SP, LY, CY)"; + Tracer = new TraceBuffer(TRACE_HEADER); + _serviceProvider.Register(Tracer); + + _disassembler = new Gameboy.GBDisassembler(); + _serviceProvider.Register(_disassembler); + + PutSettings(_settings); + + _stateBuf = new byte[LibSameboy.sameboy_statelen(SameboyState)]; + + RomDetails = $"{game.Name}\r\n{SHA1Checksum.ComputePrefixedHex(file)}\r\n{MD5Checksum.ComputePrefixedHex(file)}\r\n"; + BoardName = MapperName(file); + + _hasAcc = BoardName is "MBC7 ROM+ACCEL+EEPROM"; + ControllerDefinition = Gameboy.Gameboy.CreateControllerDefinition(sgb: false, sub: false, tilt: _hasAcc); + + LibSameboy.sameboy_setrtcdivisoroffset(SameboyState, _syncSettings.RTCDivisorOffset); + CycleCount = 0; + } + + public double ClockRate => 2097152; + + public long CycleCount + { + get => LibSameboy.sameboy_getcyclecount(SameboyState); + private set => LibSameboy.sameboy_setcyclecount(SameboyState, value); + } + + public int LagCount { get; set; } = 0; + + public bool IsLagFrame { get; set; } = false; + + public IInputCallbackSystem InputCallbacks => _inputCallbacks; + + private readonly InputCallbackSystem _inputCallbacks = new(); + + private void InputCallback() + { + IsLagFrame = false; + _inputCallbacks.Call(); + } + + public bool LinkConnected + { + get => _printercb != null; + set {} + } + + public string RomDetails { get; } + + private static string MapperName(byte[] romdata) + { + return (romdata[0x147]) switch + { + 0x00 => "Plain ROM", + 0x01 => "MBC1 ROM", + 0x02 => "MBC1 ROM+RAM", + 0x03 => "MBC1 ROM+RAM+BATTERY", + 0x05 => "MBC2 ROM", + 0x06 => "MBC2 ROM+BATTERY", + 0x08 => "Plain ROM+RAM", + 0x09 => "Plain ROM+RAM+BATTERY", + 0x0F => "MBC3 ROM+TIMER+BATTERY", + 0x10 => "MBC3 ROM+TIMER+RAM+BATTERY", + 0x11 => "MBC3 ROM", + 0x12 => "MBC3 ROM+RAM", + 0x13 => "MBC3 ROM+RAM+BATTERY", + 0x19 => "MBC5 ROM", + 0x1A => "MBC5 ROM+RAM", + 0x1B => "MBC5 ROM+RAM+BATTERY", + 0x1C => "MBC5 ROM+RUMBLE", + 0x1D => "MBC5 ROM+RUMBLE+RAM", + 0x1E => "MBC5 ROM+RUMBLE+RAM+BATTERY", + 0x22 => "MBC7 ROM+ACCEL+EEPROM", + 0xFC => "Pocket Camera ROM+RAM+BATTERY", + 0xFE => "HuC3 ROM+RAM+BATTERY", + 0xFF => "HuC1 ROM+RAM+BATTERY", + _ => "UNKNOWN", + }; + } + + public string BoardName { get; } + + public IGPUMemoryAreas LockGPU() + { + var _vram = IntPtr.Zero; + var _bgpal = IntPtr.Zero; + var _sppal = IntPtr.Zero; + var _oam = IntPtr.Zero; + int unused = 0; + if (!LibSameboy.sameboy_getmemoryarea(SameboyState, LibSameboy.MemoryAreas.VRAM, ref _vram, ref unused) + || !LibSameboy.sameboy_getmemoryarea(SameboyState, LibSameboy.MemoryAreas.BGPRGB, ref _bgpal, ref unused) + || !LibSameboy.sameboy_getmemoryarea(SameboyState, LibSameboy.MemoryAreas.OBPRGB, ref _sppal, ref unused) + || !LibSameboy.sameboy_getmemoryarea(SameboyState, LibSameboy.MemoryAreas.OAM, ref _oam, ref unused)) + { + throw new InvalidOperationException("Unexpected error in sameboy_getmemoryarea"); + } + + return new GPUMemoryAreas() + { + Vram = _vram, + Oam = _oam, + Sppal = _sppal, + Bgpal = _bgpal + }; + } + + private class GPUMemoryAreas : IGPUMemoryAreas + { + public IntPtr Vram { get; init; } + + public IntPtr Oam { get; init; } + + public IntPtr Sppal { get; init; } + + public IntPtr Bgpal { get; init; } + + public void Dispose() {} + } + + private ScanlineCallback _scanlinecb; + private int _scanlinecbline; + + public void SetScanlineCallback(ScanlineCallback callback, int line) + { + _scanlinecb = callback; + _scanlinecbline = line; + + LibSameboy.sameboy_setscanlinecallback(SameboyState, _scanlinecbline >= 0 ? callback : null, line); + + if (_scanlinecbline == -2) + { + _scanlinecb(LibSameboy.sameboy_cpuread(SameboyState, 0xFF40)); + } + } + + private PrinterCallback _printercb; + + public void SetPrinterCallback(PrinterCallback callback) + { + _printercb = callback; + LibSameboy.sameboy_setprintercallback(SameboyState, _printercb); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/CoreNames.cs b/src/BizHawk.Emulation.Cores/CoreNames.cs index c7fd2aac33..49cbfa8fbf 100644 --- a/src/BizHawk.Emulation.Cores/CoreNames.cs +++ b/src/BizHawk.Emulation.Cores/CoreNames.cs @@ -44,6 +44,7 @@ namespace BizHawk.Emulation.Cores public const string PceHawk = "PCEHawk"; public const string PicoDrive = "PicoDrive"; public const string QuickNes = "QuickNes"; + public const string Sameboy = "SameBoy"; public const string Saturnus = "Saturnus"; public const string SMSHawk = "SMSHawk"; public const string Snes9X = "Snes9x"; diff --git a/src/BizHawk.Emulation.Cores/Resources/sameboy_agb_boot.rom.gz b/src/BizHawk.Emulation.Cores/Resources/sameboy_agb_boot.rom.gz index b6a6e0c1d4..3de76267b9 100644 Binary files a/src/BizHawk.Emulation.Cores/Resources/sameboy_agb_boot.rom.gz and b/src/BizHawk.Emulation.Cores/Resources/sameboy_agb_boot.rom.gz differ diff --git a/src/BizHawk.Emulation.Cores/Resources/sameboy_cgb_boot.rom.gz b/src/BizHawk.Emulation.Cores/Resources/sameboy_cgb_boot.rom.gz index 774f2800fc..f76482100a 100644 Binary files a/src/BizHawk.Emulation.Cores/Resources/sameboy_cgb_boot.rom.gz and b/src/BizHawk.Emulation.Cores/Resources/sameboy_cgb_boot.rom.gz differ diff --git a/src/BizHawk.Emulation.Cores/Resources/sameboy_dmg_boot.rom.gz b/src/BizHawk.Emulation.Cores/Resources/sameboy_dmg_boot.rom.gz index 831acd61f7..494d868826 100644 Binary files a/src/BizHawk.Emulation.Cores/Resources/sameboy_dmg_boot.rom.gz and b/src/BizHawk.Emulation.Cores/Resources/sameboy_dmg_boot.rom.gz differ diff --git a/submodules/sameboy/.gitignore b/submodules/sameboy/.gitignore new file mode 100644 index 0000000000..c0f5fd20ae --- /dev/null +++ b/submodules/sameboy/.gitignore @@ -0,0 +1,5 @@ +.sconsign.dblite +BizInterface.os +config.log +libsameboy.dll.a +.sconf_temp diff --git a/submodules/sameboy/BizInterface.c b/submodules/sameboy/BizInterface.c new file mode 100644 index 0000000000..2c62f12eca --- /dev/null +++ b/submodules/sameboy/BizInterface.c @@ -0,0 +1,499 @@ +#include "gb.h" +#include "stdio.h" + +#ifdef _WIN32 + #define EXPORT __declspec(dllexport) +#else + #define EXPORT __attribute__((visibility("default"))) +#endif + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef enum +{ + IS_DMG = 0, + IS_CGB = 1, + IS_AGB = 2, + RTC_ACCURATE = 4, +} LoadFlags; + +typedef void (*input_callback_t)(void); +typedef void (*trace_callback_t)(u16); +typedef void (*memory_callback_t)(u16); +typedef void (*printer_callback_t)(u32*, u8, u8, u8, u8); +typedef void (*scanline_callback_t)(u32); + +typedef struct +{ + GB_gameboy_t gb; + u32 vbuf[256 * 224]; + u32 bg_pal[0x20]; + u32 obj_pal[0x20]; + input_callback_t input_cb; + trace_callback_t trace_cb; + memory_callback_t read_cb; + memory_callback_t write_cb; + memory_callback_t exec_cb; + printer_callback_t printer_cb; + scanline_callback_t scanline_cb; + u32 scanline_sl; + bool vblank_occured; + u64 cc; +} biz_t; + +static u8 PeekIO(biz_t* biz, u8 addr) +{ + u8* io = GB_get_direct_access(&biz->gb, GB_DIRECT_ACCESS_IO, NULL, NULL); + return io[addr]; +} + +static u32 rgb_cb(GB_gameboy_t *gb, u8 r, u8 g, u8 b) +{ + return (0xFF << 24) | (r << 16) | (g << 8) | b; +} + +static void vblank_cb(GB_gameboy_t *gb) +{ + ((biz_t*)gb)->vblank_occured = true; +} + +static u8 ReadCallbackRelay(GB_gameboy_t* gb, u16 addr, u8 data) +{ + ((biz_t*)gb)->read_cb(addr); + return data; +} + +static bool WriteCallbackRelay(GB_gameboy_t* gb, u16 addr, u8 data) +{ + ((biz_t*)gb)->write_cb(addr); + return true; +} + +static void ExecCallbackRelay(GB_gameboy_t* gb, u16 addr, u8 opcode) +{ + biz_t* biz = (biz_t*)gb; + if (biz->trace_cb) + { + biz->trace_cb(addr); + } + if (biz->exec_cb) + { + biz->exec_cb(addr); + } +} + +static void PrinterCallbackRelay(GB_gameboy_t* gb, u32* image, u8 height, u8 top_margin, u8 bottom_margin, u8 exposure) +{ + ((biz_t*)gb)->printer_cb(image, height, top_margin, bottom_margin, exposure); +} + +static void ScanlineCallbackRelay(GB_gameboy_t* gb, u8 line) +{ + biz_t* biz = (biz_t*)gb; + if (line == biz->scanline_sl) + { + biz->scanline_cb(PeekIO(biz, GB_IO_LCDC)); + } +} + +EXPORT biz_t* sameboy_create(u8* romdata, u32 romlen, u8* biosdata, u32 bioslen, GB_model_t model, bool realtime) +{ + biz_t* biz = calloc(1, sizeof (biz_t)); + GB_random_seed(0); + GB_init(&biz->gb, model); + GB_load_rom_from_buffer(&biz->gb, romdata, romlen); + GB_load_boot_rom_from_buffer(&biz->gb, biosdata, bioslen); + GB_set_sample_rate(&biz->gb, 44100); + GB_set_rgb_encode_callback(&biz->gb, rgb_cb); + GB_set_vblank_callback(&biz->gb, vblank_cb); + GB_set_rtc_mode(&biz->gb, realtime ? GB_RTC_MODE_SYNC_TO_HOST : GB_RTC_MODE_ACCURATE); + GB_set_allow_illegal_inputs(&biz->gb, true); + return biz; +} + +EXPORT void sameboy_destroy(biz_t* biz) +{ + GB_free(&biz->gb); + free(biz); +} + +EXPORT void sameboy_setsamplecallback(biz_t* biz, GB_sample_callback_t callback) +{ + GB_apu_set_sample_callback(&biz->gb, callback); +} + +EXPORT void sameboy_setinputcallback(biz_t* biz, input_callback_t callback) +{ + biz->input_cb = callback; +} + +static double FromRawToG(u16 raw) +{ + return (raw - 0x81D0) / (0x70 * 1.0); +} + +EXPORT void sameboy_frameadvance(biz_t* biz, GB_key_mask_t keys, u16 x, u16 y, u32* vbuf, bool render, bool border) +{ + GB_set_key_mask(&biz->gb, keys); + if (GB_has_accelerometer(&biz->gb)) + { + GB_set_accelerometer_values(&biz->gb, FromRawToG(x), FromRawToG(y)); + } + GB_set_pixels_output(&biz->gb, biz->vbuf); + GB_set_border_mode(&biz->gb, border ? GB_BORDER_ALWAYS : GB_BORDER_NEVER); + GB_set_rendering_disabled(&biz->gb, !render); + + // todo: switch this hack over to joyp_accessed when upstream fixes problems with it + if ((PeekIO(biz, GB_IO_JOYP) & 0x30) != 0x30) + { + biz->input_cb(); + } + + u32 cycles = 0; + biz->vblank_occured = false; + do + { + u8 oldjoyp = PeekIO(biz, GB_IO_JOYP) & 0x30; + u32 ret = GB_run(&biz->gb) >> 2; + cycles += ret; + biz->cc += ret; + u8 newjoyp = PeekIO(biz, GB_IO_JOYP) & 0x30; + if (oldjoyp != newjoyp && newjoyp != 0x30) + { + biz->input_cb(); + } + } + while (!biz->vblank_occured && cycles < 35112); + + if (biz->vblank_occured && render) + { + memcpy(vbuf, biz->vbuf, sizeof biz->vbuf); + } +} + +EXPORT void sameboy_reset(biz_t* biz) +{ + GB_random_seed(0); + GB_reset(&biz->gb); +} + +EXPORT bool sameboy_iscgbdmg(biz_t* biz) +{ + return !GB_is_cgb_in_cgb_mode(&biz->gb); +} + +EXPORT void sameboy_savesram(biz_t* biz, u8* dest) +{ + GB_save_battery_to_buffer(&biz->gb, dest, GB_save_battery_size(&biz->gb)); +} + +EXPORT void sameboy_loadsram(biz_t* biz, u8* data, u32 len) +{ + GB_load_battery_from_buffer(&biz->gb, data, len); +} + +EXPORT u32 sameboy_sramlen(biz_t* biz) +{ + return GB_save_battery_size(&biz->gb); +} + +EXPORT void sameboy_savestate(biz_t* biz, u8* data) +{ + GB_save_state_to_buffer(&biz->gb, data); +} + +EXPORT u32 sameboy_loadstate(biz_t* biz, u8* data, u32 len) +{ + return GB_load_state_from_buffer(&biz->gb, data, len); +} + +EXPORT u32 sameboy_statelen(biz_t* biz) +{ + return GB_get_save_state_size(&biz->gb); +} + +static void UpdatePal(biz_t* biz, bool bg) +{ + u32* pal = bg ? biz->bg_pal : biz->obj_pal; + if (GB_is_cgb_in_cgb_mode(&biz->gb)) + { + u16* rawPal = GB_get_direct_access(&biz->gb, bg ? GB_DIRECT_ACCESS_BGP : GB_DIRECT_ACCESS_OBP, NULL, NULL); + for (u32 i = 0; i < 0x20; i++) + { + pal[i] = GB_convert_rgb15(&biz->gb, rawPal[i] & 0x7FFF, false); + } + } + else + { + if (bg) + { + u32 bgPal[4]; + if (GB_is_cgb(&biz->gb)) + { + u16* rawPal = GB_get_direct_access(&biz->gb, GB_DIRECT_ACCESS_BGP, NULL, NULL); + for (u32 i = 0; i < 4; i++) + { + bgPal[i] = GB_convert_rgb15(&biz->gb, rawPal[i] & 0x7FFF, false); + } + } + else + { + const GB_palette_t* rawPal = GB_get_palette(&biz->gb); + for (u32 i = 0; i < 4; i++) + { + bgPal[i] = rgb_cb(&biz->gb, rawPal->colors[i].r, rawPal->colors[i].g, rawPal->colors[i].b); + } + } + u8 bgp = PeekIO(biz, GB_IO_BGP); + for (u32 i = 0; i < 4; i++) + { + pal[i] = bgPal[(bgp >> (i * 2)) & 3]; + } + for (u32 i = 4; i < 0x20; i++) + { + pal[i] = GB_convert_rgb15(&biz->gb, 0x7FFF, false); + } + } + else + { + u32 obj0Pal[4]; + u32 obj1Pal[4]; + if (GB_is_cgb(&biz->gb)) + { + u16* rawPal = GB_get_direct_access(&biz->gb, GB_DIRECT_ACCESS_OBP, NULL, NULL); + for (u32 i = 0; i < 4; i++) + { + obj0Pal[i] = GB_convert_rgb15(&biz->gb, rawPal[i + 0] & 0x7FFF, false); + obj1Pal[i] = GB_convert_rgb15(&biz->gb, rawPal[i + 4] & 0x7FFF, false); + } + } + else + { + const GB_palette_t* rawPal = GB_get_palette(&biz->gb); + for (u32 i = 0; i < 4; i++) + { + obj0Pal[i] = rgb_cb(&biz->gb, rawPal->colors[i].r, rawPal->colors[i].g, rawPal->colors[i].b); + obj1Pal[i] = rgb_cb(&biz->gb, rawPal->colors[i].r, rawPal->colors[i].g, rawPal->colors[i].b); + } + } + u8 obp0 = PeekIO(biz, GB_IO_OBP0); + u8 obp1 = PeekIO(biz, GB_IO_OBP1); + for (u32 i = 0; i < 4; i++) + { + pal[i + 0] = obj0Pal[(obp0 >> (i * 2)) & 3]; + pal[i + 4] = obj1Pal[(obp1 >> (i * 2)) & 3]; + } + for (u32 i = 8; i < 0x20; i++) + { + pal[i] = GB_convert_rgb15(&biz->gb, 0x7FFF, false); + } + } + } +} + +EXPORT bool sameboy_getmemoryarea(biz_t* biz, GB_direct_access_t which, void** data, size_t* len) +{ + if (which == GB_DIRECT_ACCESS_IE + 1) + { + UpdatePal(biz, true); + *data = biz->bg_pal; + *len = sizeof biz->bg_pal; + return true; + } + else if (which == GB_DIRECT_ACCESS_IE + 2) + { + UpdatePal(biz, false); + *data = biz->obj_pal; + *len = sizeof biz->obj_pal; + return true; + } + + if (which > GB_DIRECT_ACCESS_IE || which < GB_DIRECT_ACCESS_ROM) + { + return false; + } + + *data = GB_get_direct_access(&biz->gb, which, len, NULL); + return true; +} + +EXPORT u8 sameboy_cpuread(biz_t* biz, u16 addr) +{ + GB_set_read_memory_callback(&biz->gb, NULL); + u8 ret = GB_safe_read_memory(&biz->gb, addr); + GB_set_read_memory_callback(&biz->gb, biz->read_cb ? ReadCallbackRelay : NULL); + return ret; +} + +EXPORT void sameboy_cpuwrite(biz_t* biz, u16 addr, u8 value) +{ + GB_set_write_memory_callback(&biz->gb, NULL); + GB_write_memory(&biz->gb, addr, value); + GB_set_write_memory_callback(&biz->gb, biz->write_cb ? WriteCallbackRelay : NULL); +} + +EXPORT u64 sameboy_getcyclecount(biz_t* biz) +{ + return biz->cc; +} + +EXPORT void sameboy_setcyclecount(biz_t* biz, u64 newCc) +{ + biz->cc = newCc; +} + +EXPORT void sameboy_settracecallback(biz_t* biz, trace_callback_t callback) +{ + biz->trace_cb = callback; + GB_set_execution_callback(&biz->gb, (callback || biz->exec_cb) ? ExecCallbackRelay : NULL); +} + +EXPORT void sameboy_getregs(biz_t* biz, u32* buf) +{ + GB_registers_t* regs = GB_get_registers(&biz->gb); + buf[0] = regs->pc & 0xFFFF; + buf[1] = regs->a & 0xFF; + buf[2] = regs->f & 0xFF; + buf[3] = regs->b & 0xFF; + buf[4] = regs->c & 0xFF; + buf[5] = regs->d & 0xFF; + buf[6] = regs->e & 0xFF; + buf[7] = regs->h & 0xFF; + buf[8] = regs->l & 0xFF; + buf[9] = regs->sp & 0xFFFF; +} + +EXPORT void sameboy_setreg(biz_t* biz, u32 which, u32 value) +{ + GB_registers_t* regs = GB_get_registers(&biz->gb); + switch (which) + { + case 0: + regs->pc = value & 0xFFFF; + break; + case 1: + regs->a = value & 0xFF; + break; + case 2: + regs->f = value & 0xFF; + break; + case 3: + regs->b = value & 0xFF; + break; + case 4: + regs->c = value & 0xFF; + break; + case 5: + regs->d = value & 0xFF; + break; + case 6: + regs->e = value & 0xFF; + break; + case 7: + regs->h = value & 0xFF; + break; + case 8: + regs->l = value & 0xFF; + break; + case 9: + regs->sp = value & 0xFFFF; + break; + } +} + +EXPORT void sameboy_setmemorycallback(biz_t* biz, u32 which, memory_callback_t callback) +{ + switch (which) + { + case 0: + biz->read_cb = callback; + GB_set_read_memory_callback(&biz->gb, callback ? ReadCallbackRelay : NULL); + break; + case 1: + biz->write_cb = callback; + GB_set_write_memory_callback(&biz->gb, callback ? WriteCallbackRelay : NULL); + break; + case 2: + biz->exec_cb = callback; + GB_set_execution_callback(&biz->gb, (callback || biz->trace_cb) ? ExecCallbackRelay : NULL); + break; + } +} + +EXPORT void sameboy_setprintercallback(biz_t* biz, printer_callback_t callback) +{ + biz->printer_cb = callback; + if (callback) + { + GB_connect_printer(&biz->gb, PrinterCallbackRelay); + } + else + { + GB_disconnect_serial(&biz->gb); + } +} + +EXPORT void sameboy_setscanlinecallback(biz_t* biz, scanline_callback_t callback, u32 sl) +{ + biz->scanline_cb = callback; + biz->scanline_sl = sl; + GB_set_lcd_line_callback(&biz->gb, callback ? ScanlineCallbackRelay : NULL); +} + +EXPORT void sameboy_setpalette(biz_t* biz, u32 which) +{ + switch (which) + { + case 0: + GB_set_palette(&biz->gb, &GB_PALETTE_GREY); + break; + case 1: + GB_set_palette(&biz->gb, &GB_PALETTE_DMG); + break; + case 2: + GB_set_palette(&biz->gb, &GB_PALETTE_MGB); + break; + case 3: + GB_set_palette(&biz->gb, &GB_PALETTE_GBL); + break; + } +} + +EXPORT void sameboy_setcolorcorrection(biz_t* biz, GB_color_correction_mode_t which) +{ + GB_set_color_correction_mode(&biz->gb, which); +} + +EXPORT void sameboy_setlighttemperature(biz_t* biz, int temperature) +{ + GB_set_light_temperature(&biz->gb, temperature / 10.0); +} + +EXPORT void sameboy_sethighpassfilter(biz_t* biz, GB_highpass_mode_t which) +{ + GB_set_highpass_filter_mode(&biz->gb, which); +} + +EXPORT void sameboy_setinterferencevolume(biz_t* biz, int volume) +{ + GB_set_interference_volume(&biz->gb, volume / 100.0); +} + +EXPORT void sameboy_setrtcdivisoroffset(biz_t* biz, int offset) +{ + double base = GB_get_unmultiplied_clock_rate(&biz->gb) * 2.0; + GB_set_rtc_multiplier(&biz->gb, (base + offset) / base); +} + +EXPORT void sameboy_setbgwinenabled(biz_t* biz, bool enabled) +{ + GB_set_background_rendering_disabled(&biz->gb, !enabled); +} + +EXPORT void sameboy_setobjenabled(biz_t* biz, bool enabled) +{ + GB_set_object_rendering_disabled(&biz->gb, !enabled); +} diff --git a/submodules/sameboy/SConstruct b/submodules/sameboy/SConstruct new file mode 100644 index 0000000000..c50a8385b2 --- /dev/null +++ b/submodules/sameboy/SConstruct @@ -0,0 +1,38 @@ +global_cflags = ARGUMENTS.get('CFLAGS', '-I./libsameboy/Core -Wall -Wextra -O3 -std=gnu11 -fomit-frame-pointer -flto') +global_cflags += ARGUMENTS.get('CFLAGS', ' -Wno-strict-aliasing -Wno-multichar -Wno-implicit-fallthrough -Wno-sign-compare') +global_cflags += ARGUMENTS.get('CFLAGS', ' -Wno-unused-parameter -Wno-int-in-bool-context -Wno-missing-field-initializers -Wno-overflow') +global_defines = ' -D_GNU_SOURCE -D_USE_MATH_DEFINES -DNDEBUG -DGB_INTERNAL -DGB_DISABLE_DEBUGGER -DGB_DISABLE_CHEATS -DGB_DISABLE_TIMEKEEPING -DGB_DISABLE_REWIND -DGB_VERSION=' +vars = Variables() +vars.Add('CC') + +import os +env = Environment(ENV = os.environ, + CFLAGS = global_cflags + global_defines, + variables = vars) + +sourceFiles = Split(''' + libsameboy/Core/apu.c + libsameboy/Core/random.c + libsameboy/Core/camera.c + libsameboy/Core/rumble.c + libsameboy/Core/save_state.c + libsameboy/Core/display.c + libsameboy/Core/sgb.c + libsameboy/Core/gb.c + libsameboy/Core/sm83_cpu.c + libsameboy/Core/mbc.c + libsameboy/Core/memory.c + libsameboy/Core/timing.c + libsameboy/Core/printer.c + libsameboy/Core/joypad.c +''') + +conf = env.Configure() + +conf.Finish() + +shlib = env.SharedLibrary('sameboy', sourceFiles + ['BizInterface.c'], + LINKFLAGS = env['LINKFLAGS'] + ' -s -Wno-attributes', + SHLIBPREFIX = "lib") + +env.Default(shlib) diff --git a/submodules/sameboy/build.sh b/submodules/sameboy/build.sh new file mode 100644 index 0000000000..bf53f1a4e1 --- /dev/null +++ b/submodules/sameboy/build.sh @@ -0,0 +1,3 @@ +scons +mv ./libsameboy.dll ../../Assets/dll/ +mv ./libsameboy.so ../../Assets/dll/ \ No newline at end of file diff --git a/submodules/sameboy/libsameboy b/submodules/sameboy/libsameboy new file mode 160000 index 0000000000..b7f03dea8d --- /dev/null +++ b/submodules/sameboy/libsameboy @@ -0,0 +1 @@ +Subproject commit b7f03dea8dd98fa90ce5d7c7e8e05ff4cee81362