mGBA update, various cleanups and some reorg

This commit is contained in:
CasualPokePlayer 2022-12-15 01:00:36 -08:00
parent f6503adfb8
commit f3ee3e7956
19 changed files with 327 additions and 284 deletions

2
.gitmodules vendored
View File

@ -14,7 +14,7 @@
[submodule "mgba"] [submodule "mgba"]
path = submodules/mgba path = submodules/mgba
url = https://github.com/TASEmulators/mgba.git url = https://github.com/TASEmulators/mgba.git
branch = bizhawk-0.10 branch = bizhawk-0.11
[submodule "waterbox/melon/melonDS"] [submodule "waterbox/melon/melonDS"]
path = waterbox/melon/melonDS path = waterbox/melon/melonDS
url = https://github.com/TASEmulators/melonDS.git url = https://github.com/TASEmulators/melonDS.git

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,5 @@
using System; using System;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Nintendo.GBA namespace BizHawk.Emulation.Cores.Nintendo.GBA

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using BizHawk.BizInvoke; using BizHawk.BizInvoke;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
@ -8,7 +9,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
public abstract class LibmGBA public abstract class LibmGBA
{ {
[Flags] [Flags]
public enum Buttons : int public enum Buttons : ushort
{ {
A = 1, A = 1,
B = 2, B = 2,
@ -91,7 +92,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
CHB = 32 CHB = 32
} }
public enum mWatchpointType public enum mWatchpointType : int
{ {
WATCHPOINT_WRITE = 1, WATCHPOINT_WRITE = 1,
WATCHPOINT_READ = 2, WATCHPOINT_READ = 2,
@ -100,16 +101,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public class OverrideInfo public struct OverrideInfo
{ {
public SaveType Savetype; public SaveType Savetype;
public Hardware Hardware; public Hardware Hardware;
public uint IdleLoop = IDLE_LOOP_NONE; public uint IdleLoop;
public const uint IDLE_LOOP_NONE = unchecked((uint)0xffffffff); public bool VbaBugCompat;
public bool DetectPokemonRomHacks;
public const uint IDLE_LOOP_NONE = 0xffffffffu;
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public class MemoryAreas public struct MemoryAreas
{ {
public IntPtr bios; public IntPtr bios;
public IntPtr wram; public IntPtr wram;
@ -122,29 +126,29 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
public IntPtr sram; public IntPtr sram;
} }
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract void BizDestroy(IntPtr ctx); public abstract void BizDestroy(IntPtr ctx);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract IntPtr BizCreate(byte[] bios, byte[] data, int length, [In]OverrideInfo dbinfo, bool skipBios); public abstract IntPtr BizCreate(byte[] bios, byte[] data, int length, ref OverrideInfo overrides, bool skipBios);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract void BizReset(IntPtr ctx); public abstract void BizReset(IntPtr ctx);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract bool BizAdvance(IntPtr ctx, Buttons keys, int[] vbuff, ref int nsamp, short[] sbuff, public abstract bool BizAdvance(IntPtr ctx, Buttons keys, int[] vbuff, ref int nsamp, short[] sbuff,
long time, short gyrox, short gyroy, short gyroz, byte luma); long time, short gyrox, short gyroy, short gyroz, byte luma);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract void BizSetPalette(IntPtr ctx, int[] palette); public abstract void BizSetPalette(IntPtr ctx, int[] palette);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract void BizGetMemoryAreas(IntPtr ctx, [Out]MemoryAreas dst); public abstract void BizGetMemoryAreas(IntPtr ctx, out MemoryAreas dst);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract int BizGetSaveRam(IntPtr ctx, byte[] dest, int maxsize); public abstract int BizGetSaveRam(IntPtr ctx, byte[] dest, int maxsize);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract void BizPutSaveRam(IntPtr ctx, byte[] src, int size); public abstract void BizPutSaveRam(IntPtr ctx, byte[] src, int size);
/// <summary> /// <summary>
@ -153,8 +157,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
/// <param name="p">private parameter to be passed to BizFinishGetState</param> /// <param name="p">private parameter to be passed to BizFinishGetState</param>
/// <param name="size">size of buffer to be allocated for BizFinishGetState</param> /// <param name="size">size of buffer to be allocated for BizFinishGetState</param>
/// <returns>if false, operation failed and BizFinishGetState should not be called</returns> /// <returns>if false, operation failed and BizFinishGetState should not be called</returns>
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract bool BizStartGetState(IntPtr ctx, ref IntPtr p, ref int size); public abstract bool BizStartGetState(IntPtr ctx, out IntPtr p, out int size);
/// <summary> /// <summary>
/// finish a savestate operation. if StartGetState returned true, this must be called else memory leaks /// finish a savestate operation. if StartGetState returned true, this must be called else memory leaks
@ -162,57 +166,61 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
/// <param name="p">returned by BizStartGetState</param> /// <param name="p">returned by BizStartGetState</param>
/// <param name="dest">buffer of length size</param> /// <param name="dest">buffer of length size</param>
/// <param name="size">returned by BizStartGetState</param> /// <param name="size">returned by BizStartGetState</param>
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract void BizFinishGetState(IntPtr p, byte[] dest, int size); public abstract void BizFinishGetState(IntPtr p, byte[] dest, int size);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract bool BizPutState(IntPtr ctx, byte[] src, int size); public abstract bool BizPutState(IntPtr ctx, byte[] src, int size);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract void BizSetLayerMask(IntPtr ctx, Layers mask); public abstract void BizSetLayerMask(IntPtr ctx, Layers mask);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract void BizSetSoundMask(IntPtr ctx, Sounds mask); public abstract void BizSetSoundMask(IntPtr ctx, Sounds mask);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract void BizGetRegisters(IntPtr ctx, int[] dest); public abstract void BizGetRegisters(IntPtr ctx, int[] dest);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract void BizSetRegister(IntPtr ctx, int index, int value); public abstract void BizSetRegister(IntPtr ctx, int index, int value);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract ulong BizGetGlobalTime(IntPtr ctx); public abstract ulong BizGetGlobalTime(IntPtr ctx);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract void BizWriteBus(IntPtr ctx, uint addr, byte val); public abstract void BizWriteBus(IntPtr ctx, uint addr, byte val);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract byte BizReadBus(IntPtr ctx, uint addr); public abstract byte BizReadBus(IntPtr ctx, uint addr);
[UnmanagedFunctionPointer(cc)] [UnmanagedFunctionPointer(cc)]
public delegate void InputCallback(); public delegate void InputCallback();
[BizImport(cc, Compatibility = true)]
[BizImport(cc)]
public abstract void BizSetInputCallback(IntPtr ctx, InputCallback cb); public abstract void BizSetInputCallback(IntPtr ctx, InputCallback cb);
[UnmanagedFunctionPointer(cc)] [UnmanagedFunctionPointer(cc)]
public delegate void TraceCallback(string msg); public delegate void TraceCallback(string msg);
[BizImport(cc, Compatibility = true)]
[BizImport(cc)]
public abstract void BizSetTraceCallback(IntPtr ctx, TraceCallback cb); public abstract void BizSetTraceCallback(IntPtr ctx, TraceCallback cb);
[UnmanagedFunctionPointer(cc)] [UnmanagedFunctionPointer(cc)]
public delegate void MemCallback(uint addr, mWatchpointType type, uint oldValue, uint newValue); public delegate void MemCallback(uint addr, mWatchpointType type, uint oldValue, uint newValue);
[BizImport(cc, Compatibility = true)]
[BizImport(cc)]
public abstract void BizSetMemCallback(IntPtr ctx, MemCallback cb); public abstract void BizSetMemCallback(IntPtr ctx, MemCallback cb);
[UnmanagedFunctionPointer(cc)] [UnmanagedFunctionPointer(cc)]
public delegate void ExecCallback(uint pc); public delegate void ExecCallback(uint pc);
[BizImport(cc, Compatibility = true)]
[BizImport(cc)]
public abstract void BizSetExecCallback(IntPtr ctx, ExecCallback cb); public abstract void BizSetExecCallback(IntPtr ctx, ExecCallback cb);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract int BizSetWatchpoint(IntPtr ctx, uint addr, mWatchpointType type); public abstract long BizSetWatchpoint(IntPtr ctx, uint addr, mWatchpointType type);
[BizImport(cc, Compatibility = true)] [BizImport(cc)]
public abstract bool BizClearWatchpoint(IntPtr ctx, int id); public abstract bool BizClearWatchpoint(IntPtr ctx, long id);
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Nintendo.GBA namespace BizHawk.Emulation.Cores.Nintendo.GBA
@ -13,7 +14,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
var ret = new Dictionary<string, RegisterValue>(); var ret = new Dictionary<string, RegisterValue>();
for (var i = 0; i < RegisterNames.Length; i++) for (var i = 0; i < RegisterNames.Length; i++)
{ {
ret[RegisterNames[i]] = new RegisterValue(values[i]); ret[RegisterNames[i]] = new(values[i]);
} }
return ret; return ret;
@ -41,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
"R15" => 15, "R15" => 15,
"CPSR" => 16, "CPSR" => 16,
"SPSR" => 17, "SPSR" => 17,
_=> -1 _ => -1
}; };
if (index != -1) if (index != -1)
@ -50,7 +51,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
} }
} }
public IMemoryCallbackSystem MemoryCallbacks { get; } private readonly MGBAMemoryCallbackSystem _memoryCallbacks;
public IMemoryCallbackSystem MemoryCallbacks => _memoryCallbacks;
public bool CanStep(StepType type) => false; public bool CanStep(StepType type) => false;

View File

@ -0,0 +1,81 @@
using System;
using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Nintendo.GBA
{
public partial class MGBAHawk : IEmulator
{
public IEmulatorServiceProvider ServiceProvider { get; }
public ControllerDefinition ControllerDefinition => GBAController;
public bool FrameAdvance(IController controller, bool render, bool renderSound = true)
{
if (controller.IsPressed("Power"))
{
LibmGBA.BizReset(Core);
// BizReset caused memorydomain pointers to change.
WireMemoryDomainPointers();
}
LibmGBA.BizSetTraceCallback(Core, Tracer.IsEnabled() ? _tracecb : null);
IsLagFrame = LibmGBA.BizAdvance(
Core,
LibmGBA.GetButtons(controller),
render ? _videobuff : _dummyvideobuff,
ref _nsamp,
renderSound ? _soundbuff : _dummysoundbuff,
RTCTime(),
(short)controller.AxisValue("Tilt X"),
(short)controller.AxisValue("Tilt Y"),
(short)controller.AxisValue("Tilt Z"),
(byte)(255 - controller.AxisValue("Light Sensor")));
if (IsLagFrame)
{
LagCount++;
}
// this should be called in hblank on the appropriate line, but until we implement that, just do it here
_scanlinecb?.Invoke();
Frame++;
return true;
}
public int Frame { get; private set; }
public string SystemId => VSystemID.Raw.GBA;
public bool DeterministicEmulation { get; }
public void ResetCounters()
{
Frame = 0;
LagCount = 0;
IsLagFrame = false;
}
public void Dispose()
{
if (Core != IntPtr.Zero)
{
LibmGBA.BizDestroy(Core);
Core = IntPtr.Zero;
_memoryCallbacks.Dispose();
}
}
public static readonly ControllerDefinition GBAController = new ControllerDefinition("GBA Controller")
{
BoolButtons = { "Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "L", "R", "Power" }
}.AddXYZTriple("Tilt {0}", (-32767).RangeTo(32767), 0)
.AddAxis("Light Sensor", 0.RangeTo(255), 0)
.MakeImmutable();
}
}

View File

@ -0,0 +1,17 @@
using System;
namespace BizHawk.Emulation.Cores.Nintendo.GBA
{
public partial class MGBAHawk : IGBAGPUViewable
{
public GBAGPUMemoryAreas GetMemoryAreas()
=> _gpumem;
public void SetScanlineCallback(Action callback, int scanline)
=> _scanlinecb = callback;
private Action _scanlinecb;
private GBAGPUMemoryAreas _gpumem;
}
}

View File

@ -7,12 +7,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
private readonly LibmGBA.InputCallback InputCallback; private readonly LibmGBA.InputCallback InputCallback;
public int LagCount { get; set; } public int LagCount { get; set; }
public bool IsLagFrame { get; set; } public bool IsLagFrame { get; set; }
private void InputCb() public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem();
{
// most things are already handled in the core, this is just for event.oninputpoll
InputCallbacks.Call();
}
private InputCallbackSystem _inputCallbacks = new InputCallbackSystem();
public IInputCallbackSystem InputCallbacks => _inputCallbacks;
} }
} }

View File

@ -21,23 +21,24 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
private void CreateMemoryDomains(int romsize) private void CreateMemoryDomains(int romsize)
{ {
var le = MemoryDomain.Endian.Little; const MemoryDomain.Endian le = MemoryDomain.Endian.Little;
var mm = new List<MemoryDomain>(); var mm = new List<MemoryDomain>
mm.Add(_iwram = new MemoryDomainIntPtr("IWRAM", le, IntPtr.Zero, 32 * 1024, true, 4)); {
mm.Add(_ewram = new MemoryDomainIntPtr("EWRAM", le, IntPtr.Zero, 256 * 1024, true, 4)); (_iwram = new MemoryDomainIntPtr("IWRAM", le, IntPtr.Zero, 32 * 1024, true, 4)),
mm.Add(_bios = new MemoryDomainIntPtr("BIOS", le, IntPtr.Zero, 16 * 1024, false, 4)); (_ewram = new MemoryDomainIntPtr("EWRAM", le, IntPtr.Zero, 256 * 1024, true, 4)),
mm.Add(_palram = new MemoryDomainIntPtr("PALRAM", le, IntPtr.Zero, 1024, true, 4)); (_bios = new MemoryDomainIntPtr("BIOS", le, IntPtr.Zero, 16 * 1024, false, 4)),
mm.Add(_vram = new MemoryDomainIntPtr("VRAM", le, IntPtr.Zero, 96 * 1024, true, 4)); (_palram = new MemoryDomainIntPtr("PALRAM", le, IntPtr.Zero, 1024, true, 4)),
mm.Add(_oam = new MemoryDomainIntPtr("OAM", le, IntPtr.Zero, 1024, true, 4)); (_vram = new MemoryDomainIntPtr("VRAM", le, IntPtr.Zero, 96 * 1024, true, 4)),
mm.Add(_rom = new MemoryDomainIntPtr("ROM", le, IntPtr.Zero, romsize, true, 4)); (_oam = new MemoryDomainIntPtr("OAM", le, IntPtr.Zero, 1024, true, 4)),
// 128 KB is the max size for GBA savedata (_rom = new MemoryDomainIntPtr("ROM", le, IntPtr.Zero, romsize, true, 4)),
// mGBA does not know a game's save type (and as a result actual savedata size) on startup. // 128 KB is the max size for GBA savedata
// Instead, BizHawk's savedata buffer will be accessed directly for a consistent interface. // mGBA does not know a game's save type (and as a result actual savedata size) on startup.
mm.Add(_sram = new MemoryDomainIntPtr("SRAM", le, IntPtr.Zero, 128 * 1024, true, 4)); // Instead, BizHawk's savedata buffer will be accessed directly for a consistent interface.
mm.Add(_cwram = new MemoryDomainDelegate("Combined WRAM", (256 + 32) * 1024, le, null, null, 4)); (_sram = new MemoryDomainIntPtr("SRAM", le, IntPtr.Zero, 128 * 1024, true, 4)),
(_cwram = new MemoryDomainDelegate("Combined WRAM", (256 + 32) * 1024, le, null, null, 4)),
mm.Add(new MemoryDomainDelegate("System Bus", 0x10000000, le, new MemoryDomainDelegate("System Bus", 0x10000000, le,
addr => addr =>
{ {
var a = (uint)addr; var a = (uint)addr;
@ -49,16 +50,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
var a = (uint)addr; var a = (uint)addr;
if (a >= 0x10000000) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range"); if (a >= 0x10000000) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range");
LibmGBA.BizWriteBus(Core, a, val); LibmGBA.BizWriteBus(Core, a, val);
}, 4)); }, 4)
};
_memoryDomains = new MemoryDomainList(mm); _memoryDomains = new(mm);
WireMemoryDomainPointers(); WireMemoryDomainPointers();
} }
private void WireMemoryDomainPointers() private void WireMemoryDomainPointers()
{ {
var s = new LibmGBA.MemoryAreas(); LibmGBA.BizGetMemoryAreas(Core, out var s);
LibmGBA.BizGetMemoryAreas(Core, s);
_iwram.Data = s.iwram; _iwram.Data = s.iwram;
_ewram.Data = s.wram; _ewram.Data = s.wram;
@ -96,7 +97,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
} }
}; };
_gpumem = new GBAGPUMemoryAreas _gpumem = new()
{ {
mmio = s.mmio, mmio = s.mmio,
oam = s.oam, oam = s.oam,

View File

@ -1,13 +1,15 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Nintendo.GBA namespace BizHawk.Emulation.Cores.Nintendo.GBA
{ {
public partial class MGBAHawk : ISaveRam public partial class MGBAHawk : ISaveRam
{ {
private readonly byte[] _saveScratch = new byte[262144];
public byte[] CloneSaveRam() public byte[] CloneSaveRam()
{ {
int len = LibmGBA.BizGetSaveRam(Core, _saveScratch, _saveScratch.Length); int len = LibmGBA.BizGetSaveRam(Core, _saveScratch, _saveScratch.Length);
@ -16,6 +18,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
throw new InvalidOperationException("Save buffer not long enough"); throw new InvalidOperationException("Save buffer not long enough");
} }
len = TruncateRTCIfUsingDeterministicTime(len);
if (len == 0) if (len == 0)
{ {
return null; return null;
@ -26,14 +30,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
return ret; return ret;
} }
private static readonly byte[] _legacyHeader = Encoding.ASCII.GetBytes("GBABATT\0");
public void StoreSaveRam(byte[] data) public void StoreSaveRam(byte[] data)
{ {
if (data.Take(8).SequenceEqual(Encoding.ASCII.GetBytes("GBABATT\0"))) if (data.AsSpan().Slice(0, 8).SequenceEqual(_legacyHeader))
{ {
data = LegacyFix(data); data = LegacyFix(data);
} }
LibmGBA.BizPutSaveRam(Core, data, data.Length); LibmGBA.BizPutSaveRam(Core, data, TruncateRTCIfUsingDeterministicTime(data.Length));
} }
public bool SaveRamModified => LibmGBA.BizGetSaveRam(Core, _saveScratch, _saveScratch.Length) > 0; public bool SaveRamModified => LibmGBA.BizGetSaveRam(Core, _saveScratch, _saveScratch.Length) > 0;
@ -61,5 +67,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
// well, isn't this a sticky situation! // well, isn't this a sticky situation!
return flash; // woops return flash; // woops
} }
// probably don't want RTC data in the save if a user is not using real time
private int TruncateRTCIfUsingDeterministicTime(int len)
=> (!DeterministicEmulation && _syncSettings.RTCUseRealTime) ? len : len & ~0xff;
} }
} }

View File

@ -1,9 +1,9 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using BizHawk.Common; using BizHawk.Common;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
using System.ComponentModel.DataAnnotations;
using BizHawk.Emulation.Cores.Nintendo.Gameboy; using BizHawk.Emulation.Cores.Nintendo.Gameboy;
namespace BizHawk.Emulation.Cores.Nintendo.GBA namespace BizHawk.Emulation.Cores.Nintendo.GBA
@ -11,9 +11,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
public partial class MGBAHawk : ISettable<MGBAHawk.Settings, MGBAHawk.SyncSettings> public partial class MGBAHawk : ISettable<MGBAHawk.Settings, MGBAHawk.SyncSettings>
{ {
public Settings GetSettings() public Settings GetSettings()
{ => _settings.Clone();
return _settings.Clone();
}
public PutSettingsDirtyBits PutSettings(Settings o) public PutSettingsDirtyBits PutSettings(Settings o)
{ {
@ -35,17 +33,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
LibmGBA.BizSetSoundMask(Core, smask); LibmGBA.BizSetSoundMask(Core, smask);
var palette = new int[65536]; var palette = new int[65536];
GBColors.ColorType c = GBColors.ColorType.vivid; var c = o.ColorType switch
switch (o.ColorType)
{ {
case Settings.ColorTypes.SameBoy: c = GBColors.ColorType.sameboy; break; Settings.ColorTypes.SameBoy => GBColors.ColorType.sameboy,
case Settings.ColorTypes.Gambatte: c = GBColors.ColorType.gambatte; break; Settings.ColorTypes.Gambatte => GBColors.ColorType.gambatte,
case Settings.ColorTypes.Vivid: c = GBColors.ColorType.vivid; break; Settings.ColorTypes.Vivid => GBColors.ColorType.vivid,
case Settings.ColorTypes.VbaVivid: c = GBColors.ColorType.vbavivid; break; Settings.ColorTypes.VbaVivid => GBColors.ColorType.vbavivid,
case Settings.ColorTypes.VbaGbNew: c = GBColors.ColorType.vbagbnew; break; Settings.ColorTypes.VbaGbNew => GBColors.ColorType.vbagbnew,
case Settings.ColorTypes.VbaGbOld: c = GBColors.ColorType.vbabgbold; break; Settings.ColorTypes.VbaGbOld => GBColors.ColorType.vbabgbold,
case Settings.ColorTypes.BizhawkGba: c = GBColors.ColorType.gba; break; Settings.ColorTypes.BizhawkGba => GBColors.ColorType.gba,
} _ => GBColors.ColorType.vivid,
};
GBColors.GetLut(c, palette, agb: true); GBColors.GetLut(c, palette, agb: true);
for (var i = 32768; i < 65536; i++) for (var i = 32768; i < 65536; i++)
palette[i] = palette[i - 32768]; palette[i] = palette[i - 32768];
@ -126,21 +124,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
[TypeConverter(typeof(DescribableEnumConverter))] [TypeConverter(typeof(DescribableEnumConverter))]
public ColorTypes ColorType { get; set; } public ColorTypes ColorType { get; set; }
public Settings Clone()
{
return (Settings)MemberwiseClone();
}
public Settings() public Settings()
{ => SettingsUtil.SetDefaultValues(this);
SettingsUtil.SetDefaultValues(this);
} public Settings Clone()
=> (Settings)MemberwiseClone();
} }
public SyncSettings GetSyncSettings() public SyncSettings GetSyncSettings()
{ => _syncSettings.Clone();
return _syncSettings.Clone();
}
public PutSettingsDirtyBits PutSyncSettings(SyncSettings o) public PutSettingsDirtyBits PutSyncSettings(SyncSettings o)
{ {
@ -210,20 +202,24 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
[DefaultValue(false)] [DefaultValue(false)]
public bool OverrideGbPlayerDetect { get; set; } public bool OverrideGbPlayerDetect { get; set; }
public SyncSettings() [DisplayName("VBA Bug Compatibility Mode")]
{ [Description("Enables a compatibility mode for buggy Pokemon romhacks which rely on VBA bugs. Generally you don't need to enable yourself as Pokemon romhack detection will enable this itself.")]
SettingsUtil.SetDefaultValues(this); [DefaultValue(false)]
} public bool OverrideVbaBugCompat { get; set; }
public static bool NeedsReboot(SyncSettings x, SyncSettings y) [DisplayName("Detect Pokemon Romhacks")]
{ [Description("Detects Pokemon romhacks and enables compatibility options due to their generally buggy nature. Will override other override settings.")]
return !DeepEquality.DeepEquals(x, y); [DefaultValue(true)]
} public bool OverridePokemonRomhackDetect { get; set; }
public SyncSettings()
=> SettingsUtil.SetDefaultValues(this);
public SyncSettings Clone() public SyncSettings Clone()
{ => (SyncSettings)MemberwiseClone();
return (SyncSettings)MemberwiseClone();
} public static bool NeedsReboot(SyncSettings x, SyncSettings y)
=> !DeepEquality.DeepEquals(x, y);
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Nintendo.GBA namespace BizHawk.Emulation.Cores.Nintendo.GBA

View File

@ -7,18 +7,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
{ {
public partial class MGBAHawk : IStatable public partial class MGBAHawk : IStatable
{ {
private byte[] _savebuff = new byte[0]; private byte[] _savebuff = Array.Empty<byte>();
public void SaveStateBinary(BinaryWriter writer) public void SaveStateBinary(BinaryWriter writer)
{ {
IntPtr p = IntPtr.Zero; if (!LibmGBA.BizStartGetState(Core, out var p, out var size))
int size = 0;
if (!LibmGBA.BizStartGetState(Core, ref p, ref size))
{ {
throw new InvalidOperationException("Core failed to save!"); throw new InvalidOperationException("Core failed to save!");
} }
if (size != _savebuff.Length) if (size > _savebuff.Length)
{ {
_savebuff = new byte[size]; _savebuff = new byte[size];
} }
@ -37,7 +35,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
public void LoadStateBinary(BinaryReader reader) public void LoadStateBinary(BinaryReader reader)
{ {
int length = reader.ReadInt32(); int length = reader.ReadInt32();
if (length != _savebuff.Length) if (length > _savebuff.Length)
{ {
_savebuff = new byte[length]; _savebuff = new byte[length];
} }

View File

@ -0,0 +1,34 @@
using System.Text;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Nintendo.GBA
{
public partial class MGBAHawk
{
private ITraceable Tracer { get; }
private readonly LibmGBA.TraceCallback _tracecb;
private void MakeTrace(string msg)
{
var disasm = msg.Split('|')[1];
var split = disasm.Split(':');
var machineCode = split[0].PadLeft(8);
var instruction = split[1].Trim();
var regs = GetCpuFlagsAndRegisters();
var wordSize = (regs["CPSR"].Value & 32) == 0 ? 4UL : 2UL;
var pc = regs["R15"].Value - wordSize * 2;
var sb = new StringBuilder();
for (var i = 0; i < RegisterNames.Length; i++)
{
sb.Append($" { RegisterNames[i] }:{ regs[RegisterNames[i]].Value:X8}");
}
Tracer.Put(new(
disassembly: $"{pc:X8}: { machineCode } { instruction }".PadRight(50),
registerInfo: sb.ToString()));
}
}
}

View File

@ -4,10 +4,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
{ {
public partial class MGBAHawk : IVideoProvider public partial class MGBAHawk : IVideoProvider
{ {
public int[] GetVideoBuffer() private readonly int[] _videobuff = new int[240 * 160];
{ private readonly int[] _dummyvideobuff = new int[240 * 160];
return _videobuff;
} public int[] GetVideoBuffer() => _videobuff;
public int VirtualWidth => 240; public int VirtualWidth => 240;
public int VirtualHeight => 160; public int VirtualHeight => 160;
@ -17,13 +17,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
public int BackgroundColor => unchecked((int)0xff000000); public int BackgroundColor => unchecked((int)0xff000000);
public int VsyncNumerator => 262144; public int VsyncNumerator => 262144;
public int VsyncDenominator => 4389; public int VsyncDenominator => 4389;
private readonly int[] _videobuff = new int[240 * 160];
private readonly int[] _dummyvideobuff = new int[240 * 160];
} }
} }

View File

@ -1,19 +1,16 @@
using System; using System;
using System.Text;
using BizHawk.BizInvoke; using BizHawk.BizInvoke;
using BizHawk.Common; using BizHawk.Common;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Nintendo.GBA namespace BizHawk.Emulation.Cores.Nintendo.GBA
{ {
[PortedCore(CoreNames.Mgba, "endrift", "0.9.1", "https://mgba.io/")] [PortedCore(CoreNames.Mgba, "endrift", "0.10", "https://mgba.io/")]
[ServiceNotApplicable(new[] { typeof(IDriveLight), typeof(IRegionable) })] [ServiceNotApplicable(new[] { typeof(IDriveLight), typeof(IRegionable) })]
public partial class MGBAHawk : IEmulator, IVideoProvider, ISoundProvider, IGBAGPUViewable, public partial class MGBAHawk
ISaveRam, IStatable, IInputPollable, ISettable<MGBAHawk.Settings, MGBAHawk.SyncSettings>,
IDebuggable
{ {
private static readonly LibmGBA LibmGBA; private static readonly LibmGBA LibmGBA;
public static LibmGBA ZZHacky => LibmGBA;
static MGBAHawk() static MGBAHawk()
{ {
@ -22,24 +19,29 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
LibmGBA = BizInvoker.GetInvoker<LibmGBA>(resolver, CallingConventionAdapters.Native); LibmGBA = BizInvoker.GetInvoker<LibmGBA>(resolver, CallingConventionAdapters.Native);
} }
[CoreConstructor(VSystemID.Raw.GBA)] private IntPtr Core;
public MGBAHawk(byte[] file, CoreComm comm, SyncSettings syncSettings, Settings settings, bool deterministic, GameInfo game)
{
_syncSettings = syncSettings ?? new SyncSettings();
_settings = settings ?? new Settings();
var bios = comm.CoreFileProvider.GetFirmware(new("GBA", "Bios")); [CoreConstructor(VSystemID.Raw.GBA)]
public MGBAHawk(CoreLoadParameters<Settings, SyncSettings> lp)
{
_syncSettings = lp.SyncSettings ?? new();
_settings = lp.Settings ?? new();
var bios = lp.Comm.CoreFileProvider.GetFirmware(new("GBA", "Bios"));
if (bios is { Length: not 0x4000 }) throw new InvalidOperationException("BIOS must be exactly 16384 bytes!"); if (bios is { Length: not 0x4000 }) throw new InvalidOperationException("BIOS must be exactly 16384 bytes!");
if (deterministic && bios is null) throw new MissingFirmwareException("A BIOS is required for deterministic recordings!"); if (lp.DeterministicEmulationRequested && bios is null) throw new MissingFirmwareException("A BIOS is required for deterministic recordings!");
DeterministicEmulation = deterministic DeterministicEmulation = lp.DeterministicEmulationRequested
|| (bios is not null && !_syncSettings.RTCUseRealTime); // in this case, the core is deterministic even though it wasn't asked to be || (bios is not null && !_syncSettings.RTCUseRealTime); // in this case, the core is deterministic even though it wasn't asked to be
var rom = lp.Roms[0].FileData;
var overrides = GetOverrideInfo(_syncSettings);
Core = LibmGBA.BizCreate( Core = LibmGBA.BizCreate(
bios, bios,
file, rom,
file.Length, rom.Length,
GetOverrideInfo(_syncSettings), ref overrides,
skipBios: _syncSettings.SkipBios); skipBios: _syncSettings.SkipBios);
if (Core == IntPtr.Zero) if (Core == IntPtr.Zero)
{ {
throw new InvalidOperationException($"{nameof(LibmGBA.BizCreate)}() returned NULL! Bad BIOS? and/or ROM?"); throw new InvalidOperationException($"{nameof(LibmGBA.BizCreate)}() returned NULL! Bad BIOS? and/or ROM?");
@ -47,7 +49,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
try try
{ {
CreateMemoryDomains(file.Length); CreateMemoryDomains(rom.Length);
var ser = new BasicServiceProvider(this); var ser = new BasicServiceProvider(this);
ser.Register<IDisassemblable>(new ArmV4Disassembler()); ser.Register<IDisassemblable>(new ArmV4Disassembler());
ser.Register<IMemoryDomains>(_memoryDomains); ser.Register<IMemoryDomains>(_memoryDomains);
@ -57,9 +59,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
const string TRACE_HEADER = "ARM7: PC, machine code, mnemonic, operands, registers"; const string TRACE_HEADER = "ARM7: PC, machine code, mnemonic, operands, registers";
Tracer = new TraceBuffer(TRACE_HEADER); Tracer = new TraceBuffer(TRACE_HEADER);
_tracecb = msg => Tracer.Put(_traceInfo(msg)); _tracecb = MakeTrace;
ser.Register(Tracer); ser.Register(Tracer);
MemoryCallbacks = new MGBAMemoryCallbackSystem(this); _memoryCallbacks = new(LibmGBA, Core);
} }
catch catch
{ {
@ -67,117 +69,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
throw; throw;
} }
InputCallback = new LibmGBA.InputCallback(InputCb); // most things are already handled in the core, this is just for event.oninputpoll
InputCallback = InputCallbacks.Call;
LibmGBA.BizSetInputCallback(Core, InputCallback); LibmGBA.BizSetInputCallback(Core, InputCallback);
} }
public IEmulatorServiceProvider ServiceProvider { get; }
public ControllerDefinition ControllerDefinition => GBAController;
private ITraceable Tracer { get; }
private LibmGBA.TraceCallback _tracecb { get; set; }
private TraceInfo _traceInfo(string msg)
{
var disasm = msg.Split('|')[1];
var split = disasm.Split(':');
var machineCode = split[0].PadLeft(8);
var instruction = split[1].Trim();
var regs = GetCpuFlagsAndRegisters();
var wordSize = (regs["CPSR"].Value & 32) == 0 ? 4UL : 2UL;
var pc = regs["R15"].Value - wordSize * 2;
var sb = new StringBuilder();
for (var i = 0; i < RegisterNames.Length; i++)
{
sb.Append($" { RegisterNames[i] }:{ regs[RegisterNames[i]].Value:X8}");
}
return new(
disassembly: $"{pc:X8}: { machineCode } { instruction }".PadRight(50),
registerInfo: sb.ToString());
}
public bool FrameAdvance(IController controller, bool render, bool renderSound = true)
{
if (controller.IsPressed("Power"))
{
LibmGBA.BizReset(Core);
// BizReset caused memorydomain pointers to change.
WireMemoryDomainPointers();
}
LibmGBA.BizSetTraceCallback(Core, Tracer.IsEnabled() ? _tracecb : null);
IsLagFrame = LibmGBA.BizAdvance(
Core,
LibmGBA.GetButtons(controller),
render ? _videobuff : _dummyvideobuff,
ref _nsamp,
renderSound ? _soundbuff : _dummysoundbuff,
RTCTime(),
(short)controller.AxisValue("Tilt X"),
(short)controller.AxisValue("Tilt Y"),
(short)controller.AxisValue("Tilt Z"),
(byte)(255 - controller.AxisValue("Light Sensor")));
if (IsLagFrame)
{
LagCount++;
}
// this should be called in hblank on the appropriate line, but until we implement that, just do it here
_scanlinecb?.Invoke();
Frame++;
return true;
}
public int Frame { get; private set; }
public string SystemId => VSystemID.Raw.GBA;
public bool DeterministicEmulation { get; }
public void ResetCounters()
{
Frame = 0;
LagCount = 0;
IsLagFrame = false;
}
public void Dispose()
{
if (Core != IntPtr.Zero)
{
LibmGBA.BizDestroy(Core);
Core = IntPtr.Zero;
}
}
public GBAGPUMemoryAreas GetMemoryAreas()
{
return _gpumem;
}
public void SetScanlineCallback(Action callback, int scanline)
{
_scanlinecb = callback;
}
private readonly byte[] _saveScratch = new byte[262144];
internal IntPtr Core;
private static LibmGBA.OverrideInfo GetOverrideInfo(SyncSettings syncSettings) private static LibmGBA.OverrideInfo GetOverrideInfo(SyncSettings syncSettings)
{ {
var ret = new LibmGBA.OverrideInfo var ret = new LibmGBA.OverrideInfo
{ {
Savetype = syncSettings.OverrideSaveType, Savetype = syncSettings.OverrideSaveType,
Hardware = LibmGBA.Hardware.None Hardware = LibmGBA.Hardware.None,
IdleLoop = LibmGBA.OverrideInfo.IDLE_LOOP_NONE,
VbaBugCompat = syncSettings.OverrideVbaBugCompat,
DetectPokemonRomHacks = syncSettings.OverridePokemonRomhackDetect
}; };
if (syncSettings.OverrideRtc is SyncSettings.HardwareSelection.Autodetect) if (syncSettings.OverrideRtc is SyncSettings.HardwareSelection.Autodetect)
@ -233,27 +138,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
return ret; return ret;
} }
private Action _scanlinecb; private static readonly DateTime _epoch = new(1970, 1, 1);
private GBAGPUMemoryAreas _gpumem;
private long RTCTime() private long RTCTime()
{ {
if (!DeterministicEmulation && _syncSettings.RTCUseRealTime) if (!DeterministicEmulation && _syncSettings.RTCUseRealTime)
{ {
return (long)DateTime.Now.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; return (long)DateTime.Now.Subtract(_epoch).TotalSeconds;
} }
long baseTime = (long)_syncSettings.RTCInitialTime.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; long baseTime = (long)_syncSettings.RTCInitialTime.Subtract(_epoch).TotalSeconds;
long increment = Frame * 4389L >> 18; long increment = Frame * 4389L >> 18;
return baseTime + increment; return baseTime + increment;
} }
public static readonly ControllerDefinition GBAController = new ControllerDefinition("GBA Controller")
{
BoolButtons = { "Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "L", "R", "Power" }
}.AddXYZTriple("Tilt {0}", (-32767).RangeTo(32767), 0)
.AddAxis("Light Sensor", 0.RangeTo(255), 0)
.MakeImmutable();
} }
} }

View File

@ -2,13 +2,15 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Nintendo.GBA namespace BizHawk.Emulation.Cores.Nintendo.GBA
{ {
public class MGBAMemoryCallbackSystem : IMemoryCallbackSystem public class MGBAMemoryCallbackSystem : IMemoryCallbackSystem, IDisposable
{ {
private readonly MGBAHawk _mgba; private LibmGBA _mgba;
private IntPtr _core;
private readonly LibmGBA.MemCallback _readWriteCallback; private readonly LibmGBA.MemCallback _readWriteCallback;
private readonly LibmGBA.ExecCallback _executeCallback; private readonly LibmGBA.ExecCallback _executeCallback;
private readonly Dictionary<uint, MemoryCallbackDelegate> _readCallbacks = new(); private readonly Dictionary<uint, MemoryCallbackDelegate> _readCallbacks = new();
@ -16,9 +18,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
private readonly Dictionary<uint, MemoryCallbackDelegate> _execCallbacks = new(); private readonly Dictionary<uint, MemoryCallbackDelegate> _execCallbacks = new();
private readonly List<CallbackContainer> _callbacks = new(); private readonly List<CallbackContainer> _callbacks = new();
public MGBAMemoryCallbackSystem(MGBAHawk mgba) public MGBAMemoryCallbackSystem(LibmGBA mgba, IntPtr core)
{ {
_mgba = mgba; _mgba = mgba;
_core = core;
_readWriteCallback = RunReadWriteCallback; _readWriteCallback = RunReadWriteCallback;
_executeCallback = RunExecCallback; _executeCallback = RunExecCallback;
} }
@ -63,12 +66,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
if (container.Callback.Type == MemoryCallbackType.Execute) if (container.Callback.Type == MemoryCallbackType.Execute)
{ {
MGBAHawk.ZZHacky.BizSetExecCallback(_mgba.Core, _executeCallback); _mgba.BizSetExecCallback(_core, _executeCallback);
} }
else else
{ {
container.ID = MGBAHawk.ZZHacky.BizSetWatchpoint(_mgba.Core, callback.Address.Value, container.WatchPointType); container.ID = _mgba.BizSetWatchpoint(_core, callback.Address.Value, container.WatchPointType);
MGBAHawk.ZZHacky.BizSetMemCallback(_mgba.Core, _readWriteCallback); _mgba.BizSetMemCallback(_core, _readWriteCallback);
} }
var cbDict = container.Callback.Type switch var cbDict = container.Callback.Type switch
@ -101,12 +104,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
if (!HasExecutes) if (!HasExecutes)
{ {
MGBAHawk.ZZHacky.BizSetExecCallback(_mgba.Core, null); _mgba.BizSetExecCallback(_core, null);
} }
} }
else else
{ {
if (!MGBAHawk.ZZHacky.BizClearWatchpoint(_mgba.Core, cb.ID)) if (!_mgba.BizClearWatchpoint(_core, cb.ID))
{ {
throw new InvalidOperationException("Unable to clear watchpoint???"); throw new InvalidOperationException("Unable to clear watchpoint???");
} }
@ -126,7 +129,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
if (!HasReads && !HasWrites) if (!HasReads && !HasWrites)
{ {
MGBAHawk.ZZHacky.BizSetMemCallback(_mgba.Core, null); _mgba.BizSetMemCallback(_core, null);
} }
} }
} }
@ -196,31 +199,37 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
cb?.Invoke(pc, 0, (uint)MemoryCallbackFlags.AccessExecute); cb?.Invoke(pc, 0, (uint)MemoryCallbackFlags.AccessExecute);
} }
} }
}
internal class CallbackContainer public void Dispose()
{
public CallbackContainer(IMemoryCallback callBack)
{ {
Callback = callBack; _mgba = null;
_core = IntPtr.Zero;
} }
public IMemoryCallback Callback { get; } private class CallbackContainer
// the core returns this when setting a wp and needs it to clear that wp
public int ID { get; set; }
public LibmGBA.mWatchpointType WatchPointType
{ {
get public CallbackContainer(IMemoryCallback callBack)
{ {
return Callback.Type switch Callback = callBack;
}
public IMemoryCallback Callback { get; }
// the core returns this when setting a wp and needs it to clear that wp
public long ID { get; set; }
public LibmGBA.mWatchpointType WatchPointType
{
get
{ {
MemoryCallbackType.Read => LibmGBA.mWatchpointType.WATCHPOINT_READ, return Callback.Type switch
MemoryCallbackType.Write => LibmGBA.mWatchpointType.WATCHPOINT_WRITE, {
MemoryCallbackType.Execute => throw new NotImplementedException("Executes can not be used from watch points."), MemoryCallbackType.Read => LibmGBA.mWatchpointType.WATCHPOINT_READ,
_ => throw new InvalidOperationException("Invalid callback type"), MemoryCallbackType.Write => LibmGBA.mWatchpointType.WATCHPOINT_WRITE,
}; MemoryCallbackType.Execute => throw new NotImplementedException("Executes can not be used from watch points."),
_ => throw new InvalidOperationException("Invalid callback type"),
};
}
} }
} }
} }

@ -1 +1 @@
Subproject commit 707a6f8fbd07a64911667914d295c8b055e9cce3 Subproject commit f59c9650fd141fd68a9b3f51f7860bb3791aa193