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