diff --git a/Assets/dll/bsnes.wbx.gz b/Assets/dll/bsnes.wbx.gz index 0cd59f4788..7cbe117119 100644 Binary files a/Assets/dll/bsnes.wbx.gz and b/Assets/dll/bsnes.wbx.gz differ diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs index f8e4714069..a15503f632 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs @@ -59,6 +59,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES public abstract void snes_load_cartridge_normal(string baseRomPath, byte[] romData, int romSize); [BizImport(CallingConvention.Cdecl)] public abstract void snes_load_cartridge_super_gameboy(string baseRomPath, byte[] romData, byte[] sgbRomData, ulong mergedRomSizes); + + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_get_cpu_registers(ref BsnesApi.CpuRegisters registers); + [BizImport(CallingConvention.Cdecl)] + public abstract void snes_set_cpu_register(string register, uint value); + [BizImport(CallingConvention.Cdecl)] + public abstract bool snes_cpu_step(); } public unsafe partial class BsnesApi : IDisposable, IMonitor, IStatable @@ -144,6 +151,29 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES public delegate string snes_path_request_t(int slot, string hint, bool required); public delegate void snes_trace_t(string disassembly, string register_info); + [StructLayout(LayoutKind.Sequential)] + public struct CpuRegisters + { + public uint pc; + public ushort a, x, y, z, s, d; + public byte b, p, mdr; + public bool e; + public ushort v, h; + } + + [Flags] + public enum RegisterFlags : byte + { + C = 1, + Z = 2, + I = 4, + D = 8, + X = 16, + M = 32, + V = 64, + N = 128, + } + [StructLayout(LayoutKind.Sequential)] public struct LayerEnables { @@ -165,17 +195,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES public snes_path_request_t pathRequestCb; public snes_trace_t snesTraceCb; - private static List FieldsInOrder = null; + private static List FieldsInOrder; public IEnumerable AllDelegatesInMemoryOrder() { - if (FieldsInOrder == null) - { - FieldsInOrder = GetType() - .GetFields() - .OrderBy(fi => BizInvokerUtilities.ComputeFieldOffset(fi)) - .ToList(); - } + FieldsInOrder ??= GetType() + .GetFields() + .OrderBy(BizInvokerUtilities.ComputeFieldOffset) + .ToList(); return FieldsInOrder .Select(f => (Delegate)f.GetValue(this)); } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IDebuggable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IDebuggable.cs new file mode 100644 index 0000000000..41eaa6ac8d --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IDebuggable.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; +using static BizHawk.Emulation.Cores.Nintendo.BSNES.BsnesApi; + +namespace BizHawk.Emulation.Cores.Nintendo.BSNES +{ + public partial class BsnesCore : IDebuggable + { + public IDictionary GetCpuFlagsAndRegisters() + { + CpuRegisters registers = default; + Api.core.snes_get_cpu_registers(ref registers); + + var flags = (RegisterFlags) registers.p; + + bool fc = (flags & RegisterFlags.C) != 0; + bool fz = (flags & RegisterFlags.Z) != 0; + bool fi = (flags & RegisterFlags.I) != 0; + bool fd = (flags & RegisterFlags.D) != 0; + bool fx = (flags & RegisterFlags.X) != 0; + bool fm = (flags & RegisterFlags.M) != 0; + bool fv = (flags & RegisterFlags.V) != 0; + bool fn = (flags & RegisterFlags.N) != 0; + + return new Dictionary + { + ["PC"] = registers.pc, + ["A"] = registers.a, + ["X"] = registers.x, + ["Y"] = registers.y, + ["Z"] = registers.z, + ["S"] = registers.s, + ["D"] = registers.d, + ["B"] = registers.b, + ["P"] = registers.p, + ["E"] = registers.e, + ["Flag C"] = fc, + ["Flag Z"] = fz, + ["Flag I"] = fi, + ["Flag D"] = fd, + ["Flag X"] = fx, + ["Flag M"] = fm, + ["Flag V"] = fv, + ["Flag N"] = fn, + ["MDR"] = registers.mdr, + ["V"] = registers.v, + ["H"] = registers.h + }; + } + + public void SetCpuRegister(string register, int value) + { + Api.core.snes_set_cpu_register(register, (uint) value); + } + + public IMemoryCallbackSystem MemoryCallbacks { get; } = new MemoryCallbackSystem(null); + + public bool CanStep(StepType type) + { + switch (type) + { + case StepType.Into: + case StepType.Over: + case StepType.Out: + return true; + default: + return false; + } + } + + public void Step(StepType type) + { + _framePassed = false; + switch (type) + { + case StepType.Into: + StepInto(); + break; + case StepType.Over: + StepOver(); + break; + case StepType.Out: + StepOut(); + break; + default: + throw new NotImplementedException(); + } + } + + public long TotalExecutedCycles { get; private set; } + + private void StepInto() + { + _framePassed = Api.core.snes_cpu_step(); + TotalExecutedCycles++; + if (_framePassed) + { + Frame++; + if (IsLagFrame) LagCount++; + } + } + + private void StepOver() + { + CpuRegisters registers = default; + Api.core.snes_get_cpu_registers(ref registers); + byte opcode = Api.core.snes_bus_read(registers.pc); + + if (IsSubroutineCall(opcode)) + { + uint destination = registers.pc + JumpInstructionLength(opcode); + do + { + StepInto(); + Api.core.snes_get_cpu_registers(ref registers); + } while (registers.pc != destination && !_framePassed); + } + else + { + StepInto(); + } + } + + private void StepOut() + { + CpuRegisters registers = default; + Api.core.snes_get_cpu_registers(ref registers); + byte opcode = Api.core.snes_bus_read(registers.pc); + + while (!IsReturn(opcode) && !_framePassed) + { + StepOver(); + Api.core.snes_get_cpu_registers(ref registers); + opcode = Api.core.snes_bus_read(registers.pc); + } + + if (!_framePassed) + { + StepInto(); + } + } + + private bool _framePassed; + private static bool IsSubroutineCall(byte opcode) => opcode is 0x20 or 0x22 or 0xfc; + private static bool IsReturn(byte opcode) => opcode is 0x60 or 0x6b; + + private static uint JumpInstructionLength(byte opcode) + { + return opcode switch + { + 0x20 or 0xfc => 3, + 0x22 => 4, + _ => throw new ArgumentOutOfRangeException() + }; + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IEmulator.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IEmulator.cs index d00a7190ac..0267eecb2a 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IEmulator.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IEmulator.cs @@ -53,8 +53,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES Api.core.snes_set_audio_enabled(renderSound); // run the core for one frame - Frame++; Api.core.snes_run(); + Frame++; if (IsLagFrame) { diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IMemoryDomains.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IMemoryDomains.cs index 39d995dc4f..17323932ae 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IMemoryDomains.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IMemoryDomains.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using BizHawk.Common; using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Nintendo.BSNES diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs index 9faea9845f..79ba002c48 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs @@ -13,7 +13,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES { [PortedCore(CoreNames.Bsnes115, "bsnes team", "v115+", "https://bsnes.dev", isReleased: false)] [ServiceNotApplicable(new[] { typeof(IDriveLight) })] - public unsafe partial class BsnesCore : IEmulator, IVideoProvider, ISaveRam, IStatable, IInputPollable, IRegionable, ISettable + public unsafe partial class BsnesCore : IEmulator, IDebuggable, IVideoProvider, ISaveRam, IStatable, IInputPollable, IRegionable, ISettable { private BsnesApi.SNES_REGION _region; diff --git a/waterbox/bsnescore/bsnes/sfc/cpu/cpu.cpp b/waterbox/bsnescore/bsnes/sfc/cpu/cpu.cpp index fbd59a139a..bce90f544c 100644 --- a/waterbox/bsnescore/bsnes/sfc/cpu/cpu.cpp +++ b/waterbox/bsnescore/bsnes/sfc/cpu/cpu.cpp @@ -28,6 +28,7 @@ auto CPU::Enter() -> void { while(true) { scheduler.synchronize(); cpu.main(); + if (scheduler.StepOnce) scheduler.leave(Scheduler::Event::Desynchronized); } } diff --git a/waterbox/bsnescore/bsnes/sfc/sfc.hpp b/waterbox/bsnescore/bsnes/sfc/sfc.hpp index a3f003aad2..eae1eb5407 100644 --- a/waterbox/bsnescore/bsnes/sfc/sfc.hpp +++ b/waterbox/bsnescore/bsnes/sfc/sfc.hpp @@ -34,6 +34,7 @@ namespace SuperFamicom { cothread_t host = nullptr; cothread_t active = nullptr; bool desynchronized = false; + bool StepOnce = false; auto enter() -> void { host = co_active(); diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp index 892ed23acb..aa04828d56 100644 --- a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp +++ b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp @@ -361,3 +361,55 @@ EXPORT void snes_bus_write(unsigned addr, uint8_t value) { bus.write(addr, value); } + +EXPORT void snes_get_cpu_registers(SnesRegisters* registers) +{ + registers->pc = SuperFamicom::cpu.r.pc.d; + registers->a = SuperFamicom::cpu.r.a.w; + registers->x = SuperFamicom::cpu.r.x.w; + registers->y = SuperFamicom::cpu.r.y.w; + registers->z = SuperFamicom::cpu.r.z.w; + registers->s = SuperFamicom::cpu.r.s.w; + registers->d = SuperFamicom::cpu.r.d.w; + + registers->b = SuperFamicom::cpu.r.b; + registers->p = SuperFamicom::cpu.r.p; + registers->mdr = SuperFamicom::cpu.r.mdr; + registers->e = SuperFamicom::cpu.r.e; + + registers->v = SuperFamicom::cpu.vcounter(); + registers->h = SuperFamicom::cpu.hdot(); +} + +EXPORT void snes_set_cpu_register(char* _register, uint32_t value) +{ + string register = _register; + if (register == "PC") SuperFamicom::cpu.r.pc = value; + if (register == "A") SuperFamicom::cpu.r.a = value; + if (register == "X") SuperFamicom::cpu.r.x = value; + if (register == "Y") SuperFamicom::cpu.r.y = value; + if (register == "Z") SuperFamicom::cpu.r.z = value; + if (register == "S") SuperFamicom::cpu.r.s = value; + if (register == "D") SuperFamicom::cpu.r.d = value; + if (register == "B") SuperFamicom::cpu.r.b = value; + if (register == "P") SuperFamicom::cpu.r.p = value; + if (register == "E") SuperFamicom::cpu.r.e = value; + if (register == "MDR") SuperFamicom::cpu.r.mdr = value; + + if (register == "FLAG C") SuperFamicom::cpu.r.p.c = value; + if (register == "FLAG Z") SuperFamicom::cpu.r.p.z = value; + if (register == "FLAG I") SuperFamicom::cpu.r.p.i = value; + if (register == "FLAG D") SuperFamicom::cpu.r.p.d = value; + if (register == "FLAG X") SuperFamicom::cpu.r.p.x = value; + if (register == "FLAG M") SuperFamicom::cpu.r.p.m = value; + if (register == "FLAG V") SuperFamicom::cpu.r.p.v = value; + if (register == "FLAG N") SuperFamicom::cpu.r.p.n = value; +} + +EXPORT bool snes_cpu_step() +{ + scheduler.StepOnce = true; + emulator->run(); + scheduler.StepOnce = false; + return scheduler.event == Scheduler::Event::Frame; +} diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.hpp b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.hpp index 1c76d18e06..38e822f349 100644 --- a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.hpp +++ b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.hpp @@ -33,6 +33,15 @@ struct LayerEnables bool Obj_Prio0, Obj_Prio1, Obj_Prio2, Obj_Prio3; }; +struct SnesRegisters +{ + uint32_t pc; + uint16_t a, x, y, z, s, d; + uint8_t b, p, mdr; + bool e; + uint16_t v, h; +}; + // below code unused; would be useful for the graphics debugger //$2105