diff --git a/Assets/dll/libgambatte.dll b/Assets/dll/libgambatte.dll index 1e85ef2872..f1a29c47da 100644 Binary files a/Assets/dll/libgambatte.dll and b/Assets/dll/libgambatte.dll differ diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IDebuggable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IDebuggable.cs index 9dd9bec100..d4c827303f 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IDebuggable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IDebuggable.cs @@ -14,21 +14,27 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy return new Dictionary { - ["PC"] = (ushort)(data[(int)LibGambatte.RegIndicies.PC] & 0xffff), - ["SP"] = (ushort)(data[(int)LibGambatte.RegIndicies.SP] & 0xffff), - ["A"] = (byte)(data[(int)LibGambatte.RegIndicies.A] & 0xff), - ["B"] = (byte)(data[(int)LibGambatte.RegIndicies.B] & 0xff), - ["C"] = (byte)(data[(int)LibGambatte.RegIndicies.C] & 0xff), - ["D"] = (byte)(data[(int)LibGambatte.RegIndicies.D] & 0xff), - ["E"] = (byte)(data[(int)LibGambatte.RegIndicies.E] & 0xff), - ["F"] = (byte)(data[(int)LibGambatte.RegIndicies.F] & 0xff), - ["H"] = (byte)(data[(int)LibGambatte.RegIndicies.H] & 0xff), - ["L"] = (byte)(data[(int)LibGambatte.RegIndicies.L] & 0xff) + ["PC"] = (ushort)(data[(int)LibGambatte.RegIndices.PC] & 0xffff), + ["SP"] = (ushort)(data[(int)LibGambatte.RegIndices.SP] & 0xffff), + ["A"] = (byte)(data[(int)LibGambatte.RegIndices.A] & 0xff), + ["B"] = (byte)(data[(int)LibGambatte.RegIndices.B] & 0xff), + ["C"] = (byte)(data[(int)LibGambatte.RegIndices.C] & 0xff), + ["D"] = (byte)(data[(int)LibGambatte.RegIndices.D] & 0xff), + ["E"] = (byte)(data[(int)LibGambatte.RegIndices.E] & 0xff), + ["F"] = (byte)(data[(int)LibGambatte.RegIndices.F] & 0xff), + ["H"] = (byte)(data[(int)LibGambatte.RegIndices.H] & 0xff), + ["L"] = (byte)(data[(int)LibGambatte.RegIndices.L] & 0xff) }; } - [FeatureNotImplemented] - public void SetCpuRegister(string register, int value) => throw new NotImplementedException(); + public void SetCpuRegister(string register, int value) + { + int[] data = new int[10]; + LibGambatte.gambatte_getregs(GambatteState, data); + LibGambatte.RegIndices index = (LibGambatte.RegIndices)Enum.Parse(typeof(LibGambatte.RegIndices), register); + data[(int)index] = value & (index <= LibGambatte.RegIndices.SP ? 0xffff : 0xff); + LibGambatte.gambatte_setregs(GambatteState, data); + } public bool CanStep(StepType type) => false; @@ -37,46 +43,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public long TotalExecutedCycles => Math.Max((long)_cycleCount, (long)callbackCycleCount); - private MemoryCallbackSystem _memorycallbacks = new MemoryCallbackSystem(new[] { "System Bus" }); + private const string systemBusScope = "System Bus"; + + private MemoryCallbackSystem _memorycallbacks = new MemoryCallbackSystem(new[] { systemBusScope }); public IMemoryCallbackSystem MemoryCallbacks => _memorycallbacks; private LibGambatte.MemoryCallback _readcb; private LibGambatte.MemoryCallback _writecb; private LibGambatte.MemoryCallback _execcb; - private void ReadCallback(uint address, ulong cycleOffset) - { - callbackCycleCount = _cycleCount + cycleOffset; - - if (MemoryCallbacks.HasReads) - { - uint flags = (uint)MemoryCallbackFlags.AccessRead; - MemoryCallbacks.CallMemoryCallbacks(address, 0, flags, "System Bus"); - } - } - - private void WriteCallback(uint address, ulong cycleOffset) - { - callbackCycleCount = _cycleCount + cycleOffset; - - if (MemoryCallbacks.HasWrites) - { - uint flags = (uint)MemoryCallbackFlags.AccessWrite; - MemoryCallbacks.CallMemoryCallbacks(address, 0, flags,"System Bus"); - } - } - - private void ExecCallback(uint address, ulong cycleOffset) - { - callbackCycleCount = _cycleCount + cycleOffset; - - if (MemoryCallbacks.HasExecutes) - { - uint flags = (uint)MemoryCallbackFlags.AccessExecute; - MemoryCallbacks.CallMemoryCallbacks(address, 0, flags, "System Bus"); - } - } - /// /// for use in dual core /// @@ -87,19 +62,26 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy private void InitMemoryCallbacks() { - _readcb = new LibGambatte.MemoryCallback(ReadCallback); - _writecb = new LibGambatte.MemoryCallback(WriteCallback); - _execcb = new LibGambatte.MemoryCallback(ExecCallback); - _memorycallbacks.ActiveChanged += RefreshMemoryCallbacks; - } + LibGambatte.MemoryCallback CreateCallback(MemoryCallbackFlags flags, Func getHasCBOfType) + { + var rawFlags = (uint)flags; + return (address, cycleOffset) => + { + callbackCycleCount = _cycleCount + cycleOffset; + if (getHasCBOfType()) MemoryCallbacks.CallMemoryCallbacks(address, 0, rawFlags, systemBusScope); + }; + } - private void RefreshMemoryCallbacks() - { - var mcs = MemoryCallbacks; + _readcb = CreateCallback(MemoryCallbackFlags.AccessRead, () => MemoryCallbacks.HasReads); + _writecb = CreateCallback(MemoryCallbackFlags.AccessWrite, () => MemoryCallbacks.HasWrites); + _execcb = CreateCallback(MemoryCallbackFlags.AccessExecute, () => MemoryCallbacks.HasExecutes); - LibGambatte.gambatte_setreadcallback(GambatteState, mcs.HasReads ? _readcb : null); - LibGambatte.gambatte_setwritecallback(GambatteState, mcs.HasWrites ? _writecb : null); - LibGambatte.gambatte_setexeccallback(GambatteState, mcs.HasExecutes ? _execcb : null); + _memorycallbacks.ActiveChanged += () => + { + LibGambatte.gambatte_setreadcallback(GambatteState, MemoryCallbacks.HasReads ? _readcb : null); + LibGambatte.gambatte_setwritecallback(GambatteState, MemoryCallbacks.HasWrites ? _writecb : null); + LibGambatte.gambatte_setexeccallback(GambatteState, MemoryCallbacks.HasExecutes ? _execcb : null); + }; } } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs index d142bd0423..d27c14011d 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs @@ -64,7 +64,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy case GambatteSyncSettings.FrameLengthType.UserDefinedFrames: while (true) { - // target number of samples to emit: input length minus whatever overflow + // target number of samples to emit: input length float inputFrameLength = controller.AxisValue("Input Length"); uint inputFrameLengthInt = (uint)Math.Floor(inputFrameLength); if (inputFrameLengthInt == 0) @@ -89,7 +89,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy if (frameOverflow >= inputFrameLengthInt) { - frameOverflow -= inputFrameLengthInt; + frameOverflow = 0; break; } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs index 24479aba0f..5fe12e2b51 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs @@ -100,6 +100,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy [DefaultValue(false)] public bool EnableBIOS { get; set; } + [DisplayName("Patch Similar BootROMs")] + [Description("If true, BootROMs (or \"BIOSes\") are patched a similar other. GBC is patched to GBA. DMG is patched to MGB (and vice versa). Should not be used for unofficial BootROMs. Ignored (treated as false) when booting without a BIOS.")] + [DefaultValue(false)] + public bool PatchBIOS { get; set; } + public enum ConsoleModeType { Auto, diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ITraceable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ITraceable.cs index b0f33766d0..7d2313fb47 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ITraceable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ITraceable.cs @@ -14,6 +14,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { int[] s = new int[14]; System.Runtime.InteropServices.Marshal.Copy(_s, s, 0, 14); + ushort PC = (ushort)s[1]; Tracer.Put(new( disassembly: LR35902.Disassemble( @@ -34,7 +35,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy s[8] & 0xff, s[9] & 0xff, s[10] & 0xff, - s[11] != 0 ? "skip" : "", + s[11] != 0 ? "prefetched" : "", s[12] & 0xffffff, s[13] & 0xff))); } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IVideoProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IVideoProvider.cs index 93e24191d5..a314e1452d 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IVideoProvider.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IVideoProvider.cs @@ -5,7 +5,17 @@ /// /// stored image of most recent frame /// - private readonly int[] VideoBuffer = new int[160 * 144]; + private readonly int[] VideoBuffer = CreateVideoBuffer(); + + private static int[] CreateVideoBuffer() + { + var b = new int[160 * 144]; + for (int i = 0; i < (160 * 144); i++) + { + b[i] = -1; // GB/C screen is disabled on bootup, so it always starts as white, not black + } + return b; + } public int[] GetVideoBuffer() { diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index 852baae8d3..19513e5b03 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using BizHawk.Common; using BizHawk.Common.BufferExtensions; @@ -45,7 +46,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy try { - _syncSettings = (GambatteSyncSettings)syncSettings ?? new GambatteSyncSettings(); + _syncSettings = syncSettings ?? new GambatteSyncSettings(); LibGambatte.LoadFlags flags = 0; @@ -73,22 +74,31 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy byte[] bios; string biosSystemId; string biosId; - if ((flags & LibGambatte.LoadFlags.CGB_MODE) == LibGambatte.LoadFlags.CGB_MODE) - { - biosSystemId = "GBC"; - biosId = _syncSettings.ConsoleMode == GambatteSyncSettings.ConsoleModeType.GBA ? "AGB" : "World"; - IsCgb = true; - } - else - { - biosSystemId = "GB"; - biosId = "World"; - IsCgb = false; - } + + IsCgb = (flags & LibGambatte.LoadFlags.CGB_MODE) == LibGambatte.LoadFlags.CGB_MODE; + biosSystemId = IsCgb ? "GBC" : "GB"; + biosId = ((_syncSettings.ConsoleMode == GambatteSyncSettings.ConsoleModeType.GBA) && !_syncSettings.PatchBIOS) ? "AGB" : "World"; if (_syncSettings.EnableBIOS) { bios = comm.CoreFileProvider.GetFirmwareOrThrow(new(biosSystemId, biosId), "BIOS Not Found, Cannot Load. Change SyncSettings to run without BIOS."); + if (_syncSettings.PatchBIOS) + { + if (!IsCgb) + { + bios[0xFD] ^= 0xFE; // patch from dmg<->mgb + } + else if (_syncSettings.ConsoleMode == GambatteSyncSettings.ConsoleModeType.GBA) + { + // patch from cgb->agb re + bios[0xF3] ^= 0x03; + for (var i = 0xF5; i < 0xFB;) + { + bios[i] = bios[++i]; + } + bios[0xFB] ^= 0x74; + } + } if (LibGambatte.gambatte_loadbios(GambatteState, bios, (uint)bios.Length) != 0) { throw new InvalidOperationException($"{nameof(LibGambatte.gambatte_loadbios)}() returned non-zero (bios error)"); @@ -96,6 +106,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy } else { + if (DeterministicEmulation) // throw a warning if a movie is being recorded with the bios disabled + { + comm.ShowMessage("Detected disabled BIOS during movie recording. It is recommended to use a BIOS for movie recording. Change Sync Settings to run with a BIOS."); + } flags |= LibGambatte.LoadFlags.NO_BIOS; } @@ -133,6 +147,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { cgbDmgColors = ColorsFromTitleHash(file); } + _settings.GBPalette = cgbDmgColors; ChangeDMGColors(cgbDmgColors); } @@ -295,27 +310,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy _inputCallbacks = ics; } + // needs to match the reverse order of Libgambatte's button enum + static readonly IReadOnlyList BUTTON_ORDER_IN_BITMASK = new string[] { "Down", "Up", "Left", "Right", "Start", "Select", "B", "A" }; + internal void FrameAdvancePrep(IController controller) { // update our local copy of the controller data - CurrentButtons = 0; - - if (controller.IsPressed("Up")) - CurrentButtons |= LibGambatte.Buttons.UP; - if (controller.IsPressed("Down")) - CurrentButtons |= LibGambatte.Buttons.DOWN; - if (controller.IsPressed("Left")) - CurrentButtons |= LibGambatte.Buttons.LEFT; - if (controller.IsPressed("Right")) - CurrentButtons |= LibGambatte.Buttons.RIGHT; - if (controller.IsPressed("A")) - CurrentButtons |= LibGambatte.Buttons.A; - if (controller.IsPressed("B")) - CurrentButtons |= LibGambatte.Buttons.B; - if (controller.IsPressed("Select")) - CurrentButtons |= LibGambatte.Buttons.SELECT; - if (controller.IsPressed("Start")) - CurrentButtons |= LibGambatte.Buttons.START; + byte b = 0; + for (var i = 0; i < 8; i++) + { + b <<= 1; + if (controller.IsPressed(BUTTON_ORDER_IN_BITMASK[i])) b |= 1; + } + CurrentButtons = (LibGambatte.Buttons)b; // the controller callback will set this to false if it actually gets called during the frame IsLagFrame = true; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IVideoProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IVideoProvider.cs index 40721eaf40..af12f10ffd 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IVideoProvider.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IVideoProvider.cs @@ -17,6 +17,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public int[] GetVideoBuffer() => VideoBuffer; - private readonly int[] VideoBuffer = new int[160 * 2 * 144]; + private readonly int[] VideoBuffer = CreateVideoBuffer(); + + private static int[] CreateVideoBuffer() + { + var b = new int[160 * 2 * 144]; + for (int i = 0; i < (160 * 2 * 144); i++) + { + b[i] = -1; // GB/C screen is disabled on bootup, so it always starts as white, not black + } + return b; + } + } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambattePrinter.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambattePrinter.cs index 91f305dba5..232fd2b3c6 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambattePrinter.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambattePrinter.cs @@ -56,7 +56,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy private readonly byte[] image = new byte[160 * 200]; private ushort image_offset; - private byte compression_run_lenth; + private byte compression_run_length; private bool compression_run_is_compressed; public GambattePrinter(Gameboy gb, PrinterCallback callback) @@ -136,27 +136,27 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { if (compression) { - if (compression_run_lenth == 0) + if (compression_run_length == 0) { compression_run_is_compressed = (byte_received & 0x80) != 0; - compression_run_lenth = (byte)((byte_received & 0x7F) + 1 + (compression_run_is_compressed ? 1 : 0)); + compression_run_length = (byte)((byte_received & 0x7F) + 1 + (compression_run_is_compressed ? 1 : 0)); } else if (compression_run_is_compressed) { - while (compression_run_lenth > 0) + while (compression_run_length > 0) { command_data[command_length++] = byte_received; - compression_run_lenth--; + compression_run_length--; if (command_length == GB_PRINTER_MAX_COMMAND_LENGTH) { - compression_run_lenth = 0; + compression_run_length = 0; } } } else { command_data[command_length++] = byte_received; - compression_run_lenth--; + compression_run_length--; } } else diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs index 74c7245136..bc528700a7 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs @@ -432,8 +432,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy /// length of at least 10, please [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void gambatte_getregs(IntPtr core, int[] dest); + + /// + /// set reg and flag values + /// + /// opaque state pointer + /// length of at least 10, please + [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void gambatte_setregs(IntPtr core, int[] src); - public enum RegIndicies : int + public enum RegIndices : int { PC, SP, A, B, C, D, E, F, H, L } diff --git a/submodules/gambatte b/submodules/gambatte index 1f125d8d00..1fe731bd6f 160000 --- a/submodules/gambatte +++ b/submodules/gambatte @@ -1 +1 @@ -Subproject commit 1f125d8d00174546a4dc4b95752ad2f4a63fce61 +Subproject commit 1fe731bd6f0ca40737aa2e577d791ea6b084829a