Add SameBoy as a GB/C core (#3009)

* basics for sameboy

* lol

* bleh

* lol

* push this in

* push this in

* settings, and also update sameboy bootroms

* bleh

* build linux

* remove some debugging shiz

* fix the order of this

* debug stuff also do gpu palettes right

* use new key mask API

* push shit in

* bleh

* add in replacement impl for joypad, use until opposing directions are allowed upstream

* update

* finally get this working without needing GB_INTERNAL

* hook up acc controls

* oops

* oops x2

* oh right this doesn't use this

* finish this up

* also mark this as released

* cleanups

* Nitpicks

Co-authored-by: YoshiRulz <OSSYoshiRulz@gmail.com>
This commit is contained in:
CasualPokePlayer 2022-01-27 13:17:52 -08:00 committed by GitHub
parent 69d51aba50
commit 2348e2885d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1707 additions and 7 deletions

3
.gitmodules vendored
View File

@ -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

BIN
Assets/dll/libsameboy.dll Normal file

Binary file not shown.

BIN
Assets/dll/libsameboy.so Executable file

Binary file not shown.

View File

@ -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 },

View File

@ -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";

View File

@ -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();

View File

@ -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 =

View File

@ -60,6 +60,7 @@
<Compile Update="Consoles/Nintendo/NDS/MelonDS.*.cs" DependentUpon="MelonDS.cs" />
<Compile Update="Consoles/Nintendo/SubNESHawk/SubNESHawk.*.cs" DependentUpon="SubNESHawk.cs" />
<Compile Update="Consoles/Nintendo/QuickNES/QuickNES.*.cs" DependentUpon="QuickNES.cs" />
<Compile Update="Consoles/Nintendo/Sameboy/SameBoy.*.cs" DependentUpon="SameBoy.cs" />
<Compile Update="Consoles/Nintendo/SNES/LibsnesCore.*.cs" DependentUpon="LibsnesCore.cs" />
<Compile Update="Consoles/PC Engine/PCEngine.*.cs" DependentUpon="PCEngine.cs" />
<Compile Update="Consoles/Sega/GGHawkLink/GGHawkLink.*.cs" DependentUpon="GGHawkLink.cs" />

View File

@ -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++)

View File

@ -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;

View File

@ -0,0 +1,152 @@
using System;
using System.Runtime.InteropServices;
using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy;
namespace BizHawk.Emulation.Cores.Nintendo.Sameboy
{
/// <summary>
/// static bindings into libsameboy.dll
/// </summary>
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);
}
}

View File

@ -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<string, RegisterValue> GetCpuFlagsAndRegisters()
{
int[] data = new int[10];
LibSameboy.sameboy_getregs(SameboyState, data);
return new Dictionary<string, RegisterValue>
{
["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<bool> 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);
}
}
}

View File

@ -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<string> 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;
}
}
}
}

View File

@ -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<MemoryDomain> _memoryDomains = new List<MemoryDomain>();
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);
}
}
}

View File

@ -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<byte>();
}
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);
}
}
}

View File

@ -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<Sameboy.SameboySettings, Sameboy.SameboySyncSettings>
{
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);
}
}
}

View File

@ -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++;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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
)));
}
}
}

View File

@ -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;
}
}

View File

@ -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
{
/// <summary>
/// a gameboy/gameboy color emulator wrapped around native C libsameboy
/// </summary>
[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<IDisassemblable>(_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);
}
}
}

View File

@ -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";

5
submodules/sameboy/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.sconsign.dblite
BizInterface.os
config.log
libsameboy.dll.a
.sconf_temp

View File

@ -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);
}

View File

@ -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)

View File

@ -0,0 +1,3 @@
scons
mv ./libsameboy.dll ../../Assets/dll/
mv ./libsameboy.so ../../Assets/dll/

@ -0,0 +1 @@
Subproject commit b7f03dea8dd98fa90ce5d7c7e8e05ff4cee81362