diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/LibmGBA.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/LibmGBA.cs index 6f9f3084c5..c3f3064fca 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/LibmGBA.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/LibmGBA.cs @@ -8,15 +8,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA const string dll = "mgba.dll"; const CallingConvention cc = CallingConvention.Cdecl; - [DllImport(dll, CallingConvention = cc)] - public static extern void BizDestroy(IntPtr ctx); - - [DllImport(dll, CallingConvention = cc)] - public static extern IntPtr BizCreate(byte[] bios, byte[] data, int length, [In]OverrideInfo dbinfo, bool skipBios); - - [DllImport(dll, CallingConvention = cc)] - public static extern void BizReset(IntPtr ctx); - public enum SaveType : int { Autodetect = -1, @@ -41,6 +32,35 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA NoOverride = 0x8000 // can probably ignore this } + [Flags] + public enum Layers : int + { + BG0 = 1, + BG1 = 2, + BG2 = 4, + BG3 = 8, + OBJ = 16 + } + + [Flags] + public enum Sounds : int + { + CH0 = 1, + CH1 = 2, + CH2 = 4, + CH3 = 8, + CHA = 16, + CHB = 32 + } + + public enum mWatchpointType + { + WATCHPOINT_WRITE = 1, + WATCHPOINT_READ = 2, + WATCHPOINT_RW = 3, + WATCHPOINT_WRITE_CHANGE = 4, + }; + [StructLayout(LayoutKind.Sequential)] public class OverrideInfo { @@ -50,13 +70,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA public const uint IDLE_LOOP_NONE = unchecked((uint)0xffffffff); } - [DllImport(dll, CallingConvention = cc)] - public static extern bool BizAdvance(IntPtr ctx, LibVBANext.Buttons keys, int[] vbuff, ref int nsamp, short[] sbuff, - long time, short gyrox, short gyroy, short gyroz, byte luma); - - [DllImport(dll, CallingConvention = cc)] - public static extern void BizSetPalette(IntPtr ctx, int[] palette); - [StructLayout(LayoutKind.Sequential)] public class MemoryAreas { @@ -72,11 +85,28 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA public int sram_size; } + [DllImport(dll, CallingConvention = cc)] + public static extern void BizDestroy(IntPtr ctx); + + [DllImport(dll, CallingConvention = cc)] + public static extern IntPtr BizCreate(byte[] bios, byte[] data, int length, [In]OverrideInfo dbinfo, bool skipBios); + + [DllImport(dll, CallingConvention = cc)] + public static extern void BizReset(IntPtr ctx); + + [DllImport(dll, CallingConvention = cc)] + public static extern bool BizAdvance(IntPtr ctx, LibVBANext.Buttons keys, int[] vbuff, ref int nsamp, short[] sbuff, + long time, short gyrox, short gyroy, short gyroz, byte luma); + + [DllImport(dll, CallingConvention = cc)] + public static extern void BizSetPalette(IntPtr ctx, int[] palette); + [DllImport(dll, CallingConvention = cc)] public static extern void BizGetMemoryAreas(IntPtr ctx, [Out]MemoryAreas dst); [DllImport(dll, CallingConvention = cc)] public static extern int BizGetSaveRam(IntPtr ctx, byte[] dest, int maxsize); + [DllImport(dll, CallingConvention = cc)] public static extern void BizPutSaveRam(IntPtr ctx, byte[] src, int size); @@ -101,30 +131,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA [DllImport(dll, CallingConvention = cc)] public static extern bool BizPutState(IntPtr ctx, byte[] src, int size); - [Flags] - public enum Layers : int - { - BG0 = 1, - BG1 = 2, - BG2 = 4, - BG3 = 8, - OBJ = 16 - } - [DllImport(dll, CallingConvention = cc)] public static extern void BizSetLayerMask(IntPtr ctx, Layers mask); - [Flags] - public enum Sounds : int - { - CH0 = 1, - CH1 = 2, - CH2 = 4, - CH3 = 8, - CHA = 16, - CHB = 32 - } - [DllImport(dll, CallingConvention = cc)] public static extern void BizSetSoundMask(IntPtr ctx, Sounds mask); @@ -139,9 +148,23 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA [UnmanagedFunctionPointer(cc)] public delegate void TraceCallback(string msg); - [DllImport(dll, CallingConvention = cc)] public static extern void BizSetTraceCallback(TraceCallback cb); + [UnmanagedFunctionPointer(cc)] + public delegate void MemCallback(uint addr, mWatchpointType type, uint oldValue, uint newValue); + [DllImport(dll, CallingConvention = cc)] + public static extern void BizSetMemCallback(MemCallback cb); + + [UnmanagedFunctionPointer(cc)] + public delegate void ExecCallback(uint pc); + [DllImport(dll, CallingConvention = cc)] + public static extern void BizSetExecCallback(ExecCallback cb); + + [DllImport(dll, CallingConvention = cc)] + public static extern int BizSetWatchpoint(IntPtr ctx, uint addr, mWatchpointType type); + + [DllImport(dll, CallingConvention = cc)] + public static extern bool BizClearWatchpoint(IntPtr ctx, int id); } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IDebuggable.cs index 1fd8c676d9..2726f8ec96 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IDebuggable.cs @@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA public IDictionary GetCpuFlagsAndRegisters() { var values = new int[RegisterNames.Length]; - LibmGBA.BizGetRegisters(_core, values); + LibmGBA.BizGetRegisters(Core, values); var ret = new Dictionary(); for (var i = 0; i < RegisterNames.Length; i++) { @@ -27,7 +27,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA } [FeatureNotImplemented] - public IMemoryCallbackSystem MemoryCallbacks => throw new NotImplementedException(); + public IMemoryCallbackSystem MemoryCallbacks { get; } public bool CanStep(StepType type) => false; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IMemoryDomains.cs index 730e339447..7a4c33a85b 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IMemoryDomains.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IMemoryDomains.cs @@ -43,7 +43,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA throw new ArgumentOutOfRangeException(); } - return LibmGBA.BizReadBus(_core, a); + return LibmGBA.BizReadBus(Core, a); }, delegate (long addr, byte val) { @@ -53,7 +53,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA throw new ArgumentOutOfRangeException(); } - LibmGBA.BizWriteBus(_core, a, val); + LibmGBA.BizWriteBus(Core, a, val); }, 4)); _memoryDomains = new MemoryDomainList(mm); @@ -63,7 +63,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA private void WireMemoryDomainPointers() { var s = new LibmGBA.MemoryAreas(); - LibmGBA.BizGetMemoryAreas(_core, s); + LibmGBA.BizGetMemoryAreas(Core, s); _iwram.Data = s.iwram; _ewram.Data = s.wram; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISaveRam.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISaveRam.cs index 1cbd24c493..134b7cf0d1 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISaveRam.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISaveRam.cs @@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA { public byte[] CloneSaveRam() { - int len = LibmGBA.BizGetSaveRam(_core, _saveScratch, _saveScratch.Length); + int len = LibmGBA.BizGetSaveRam(Core, _saveScratch, _saveScratch.Length); if (len == _saveScratch.Length) { throw new InvalidOperationException("Save buffer not long enough"); @@ -33,10 +33,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA data = LegacyFix(data); } - LibmGBA.BizPutSaveRam(_core, data, data.Length); + LibmGBA.BizPutSaveRam(Core, data, data.Length); } - public bool SaveRamModified => LibmGBA.BizGetSaveRam(_core, _saveScratch, _saveScratch.Length) > 0; + public bool SaveRamModified => LibmGBA.BizGetSaveRam(Core, _saveScratch, _saveScratch.Length) > 0; private static byte[] LegacyFix(byte[] saveram) { diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISettable.cs index 7a655c3b02..4ae7bb545c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISettable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.ISettable.cs @@ -23,7 +23,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA if (o.DisplayBG2) mask |= LibmGBA.Layers.BG2; if (o.DisplayBG3) mask |= LibmGBA.Layers.BG3; if (o.DisplayOBJ) mask |= LibmGBA.Layers.OBJ; - LibmGBA.BizSetLayerMask(_core, mask); + LibmGBA.BizSetLayerMask(Core, mask); LibmGBA.Sounds smask = 0; if (o.PlayCh0) smask |= LibmGBA.Sounds.CH0; @@ -32,7 +32,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA if (o.PlayCh3) smask |= LibmGBA.Sounds.CH3; if (o.PlayChA) smask |= LibmGBA.Sounds.CHA; if (o.PlayChB) smask |= LibmGBA.Sounds.CHB; - LibmGBA.BizSetSoundMask(_core, smask); + LibmGBA.BizSetSoundMask(Core, smask); var palette = new int[65536]; GBColors.ColorType c = GBColors.ColorType.vivid; @@ -48,7 +48,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA GBColors.GetLut(c, palette); for (var i = 32768; i < 65536; i++) palette[i] = palette[i - 32768]; - LibmGBA.BizSetPalette(_core, palette); + LibmGBA.BizSetPalette(Core, palette); _settings = o; return false; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IStatable.cs index b822df8c92..ecb750c9ea 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IStatable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.IStatable.cs @@ -14,7 +14,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA { IntPtr p = IntPtr.Zero; int size = 0; - if (!LibmGBA.BizStartGetState(_core, ref p, ref size)) + if (!LibmGBA.BizStartGetState(Core, ref p, ref size)) { throw new InvalidOperationException("Core failed to save!"); } @@ -55,7 +55,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA } reader.Read(_savebuff, 0, length); - if (!LibmGBA.BizPutState(_core, _savebuff, length)) + if (!LibmGBA.BizPutState(Core, _savebuff, length)) { throw new InvalidOperationException("Core rejected the savestate!"); } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs index 1de6286ade..6349c41ced 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs @@ -38,8 +38,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA var skipBios = !DeterministicEmulation && _syncSettings.SkipBios; - _core = LibmGBA.BizCreate(bios, file, file.Length, GetOverrideInfo(game), skipBios); - if (_core == IntPtr.Zero) + Core = LibmGBA.BizCreate(bios, file, file.Length, GetOverrideInfo(game), skipBios); + if (Core == IntPtr.Zero) { throw new InvalidOperationException($"{nameof(LibmGBA.BizCreate)}() returned NULL! Bad BIOS? and/or ROM?"); } @@ -60,10 +60,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA }; _tracecb = new LibmGBA.TraceCallback((msg) => _tracer.Put(_traceInfo(msg))); ser.Register(_tracer); + MemoryCallbacks = new MGBAMemoryCallbackSystem(this); } catch { - LibmGBA.BizDestroy(_core); + LibmGBA.BizDestroy(Core); throw; } } @@ -104,7 +105,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA Frame++; if (controller.IsPressed("Power")) { - LibmGBA.BizReset(_core); + LibmGBA.BizReset(Core); // BizReset caused memorydomain pointers to change. WireMemoryDomainPointers(); @@ -113,7 +114,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA LibmGBA.BizSetTraceCallback(_tracer.Enabled ? _tracecb : null); IsLagFrame = LibmGBA.BizAdvance( - _core, + Core, VBANext.GetButtons(controller), render ? _videobuff : _dummyvideobuff, ref _nsamp, @@ -150,10 +151,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA public void Dispose() { - if (_core != IntPtr.Zero) + if (Core != IntPtr.Zero) { - LibmGBA.BizDestroy(_core); - _core = IntPtr.Zero; + LibmGBA.BizDestroy(Core); + Core = IntPtr.Zero; } } @@ -169,7 +170,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA } private readonly byte[] _saveScratch = new byte[262144]; - private IntPtr _core; + internal IntPtr Core; private static LibmGBA.OverrideInfo GetOverrideInfo(GameInfo game) { diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAMemoryCallbackSystem.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAMemoryCallbackSystem.cs new file mode 100644 index 0000000000..99ca418e09 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAMemoryCallbackSystem.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.GBA +{ + public class MGBAMemoryCallbackSystem : IMemoryCallbackSystem + { + private readonly MGBAHawk _mgba; + + public MGBAMemoryCallbackSystem(MGBAHawk mgba) + { + _mgba = mgba; + } + + private readonly List _callbacks = new List(); + + public string[] AvailableScopes { get; } = { "System Bus" }; + public bool ExecuteCallbacksAvailable => false; + + public bool HasReads => _callbacks.Any(c => c.Callback.Type == MemoryCallbackType.Read); + public bool HasWrites => _callbacks.Any(c => c.Callback.Type == MemoryCallbackType.Write); + public bool HasExecutes => _callbacks.Any(c => c.Callback.Type == MemoryCallbackType.Execute); + + public bool HasReadsForScope(string scope) => + _callbacks.Any(c => c.Callback.Scope == scope + && c.Callback.Type == MemoryCallbackType.Read); + + public bool HasWritesForScope(string scope) => + _callbacks.Any(c => c.Callback.Scope == scope + && c.Callback.Type == MemoryCallbackType.Write); + + public bool HasExecutesForScope(string scope) => + _callbacks.Any(c => c.Callback.Scope == scope + && c.Callback.Type == MemoryCallbackType.Execute); + + public void Add(IMemoryCallback callback) + { + if (!AvailableScopes.Contains(callback.Scope)) + { + throw new InvalidOperationException($"{callback.Scope} is not currently supported for callbacks"); + } + + if (!callback.Address.HasValue) + { + throw new NotImplementedException("Wildcard callbacks (no address specified) not currently implemented."); + } + + var container = new CallbackContainer(callback); + + if (container.Callback.Type == MemoryCallbackType.Execute) + { + // TODO + } + else + { + LibmGBA.BizSetMemCallback(container.CallDelegate); + container.ID = LibmGBA.BizSetWatchpoint(_mgba.Core, callback.Address.Value, container.WatchPointType); + } + + _callbacks.Add(container); + } + + public void Remove(MemoryCallbackDelegate action) + { + var cbToRemove = _callbacks.Single(container => container.Callback.Callback == action); + + if (LibmGBA.BizClearWatchpoint(_mgba.Core, cbToRemove.ID)) + { + _callbacks.Remove(cbToRemove); + } + } + + public void RemoveAll(IEnumerable actions) + { + foreach (var action in actions) + { + Remove(action); + } + } + + public void Clear() + { + foreach (var cb in _callbacks) + { + if (LibmGBA.BizClearWatchpoint(_mgba.Core, cb.ID)) + { + _callbacks.Remove(cb); + } + } + } + + public IEnumerator GetEnumerator() => _callbacks.Select(c => c.Callback).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _callbacks.Select(c => c.Callback).GetEnumerator(); + + public void CallMemoryCallbacks(uint addr, uint value, uint flags, string scope) + { + // Not a thing in this implementation + } + } + + internal class CallbackContainer + { + public CallbackContainer(IMemoryCallback callBack) + { + Callback = callBack; + CallDelegate = Call; + } + + public IMemoryCallback Callback { get; } + + // 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 + { + switch (Callback.Type) + { + default: + case MemoryCallbackType.Read: + return LibmGBA.mWatchpointType.WATCHPOINT_READ; + case MemoryCallbackType.Write: + return LibmGBA.mWatchpointType.WATCHPOINT_WRITE; + case MemoryCallbackType.Execute: + throw new NotImplementedException("Executes not implemented yet"); + } + } + } + + public LibmGBA.MemCallback CallDelegate; + + void Call(uint addr, LibmGBA.mWatchpointType type, uint oldValue, uint newValue) + { + Callback.Callback?.Invoke(addr, newValue, 0); + } + } +}