Implement IDebuggable for the new bsnes core
This commit is contained in:
parent
a43eaaeee9
commit
24c74c0e36
Binary file not shown.
|
@ -59,6 +59,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
|
||||||
public abstract void snes_load_cartridge_normal(string baseRomPath, byte[] romData, int romSize);
|
public abstract void snes_load_cartridge_normal(string baseRomPath, byte[] romData, int romSize);
|
||||||
[BizImport(CallingConvention.Cdecl)]
|
[BizImport(CallingConvention.Cdecl)]
|
||||||
public abstract void snes_load_cartridge_super_gameboy(string baseRomPath, byte[] romData, byte[] sgbRomData, ulong mergedRomSizes);
|
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
|
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 string snes_path_request_t(int slot, string hint, bool required);
|
||||||
public delegate void snes_trace_t(string disassembly, string register_info);
|
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)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct LayerEnables
|
public struct LayerEnables
|
||||||
{
|
{
|
||||||
|
@ -165,17 +195,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
|
||||||
public snes_path_request_t pathRequestCb;
|
public snes_path_request_t pathRequestCb;
|
||||||
public snes_trace_t snesTraceCb;
|
public snes_trace_t snesTraceCb;
|
||||||
|
|
||||||
private static List<FieldInfo> FieldsInOrder = null;
|
private static List<FieldInfo> FieldsInOrder;
|
||||||
|
|
||||||
public IEnumerable<Delegate> AllDelegatesInMemoryOrder()
|
public IEnumerable<Delegate> AllDelegatesInMemoryOrder()
|
||||||
{
|
{
|
||||||
if (FieldsInOrder == null)
|
FieldsInOrder ??= GetType()
|
||||||
{
|
.GetFields()
|
||||||
FieldsInOrder = GetType()
|
.OrderBy(BizInvokerUtilities.ComputeFieldOffset)
|
||||||
.GetFields()
|
.ToList();
|
||||||
.OrderBy(fi => BizInvokerUtilities.ComputeFieldOffset(fi))
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
return FieldsInOrder
|
return FieldsInOrder
|
||||||
.Select(f => (Delegate)f.GetValue(this));
|
.Select(f => (Delegate)f.GetValue(this));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<string, RegisterValue> 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<string, RegisterValue>
|
||||||
|
{
|
||||||
|
["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()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,8 +53,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
|
||||||
Api.core.snes_set_audio_enabled(renderSound);
|
Api.core.snes_set_audio_enabled(renderSound);
|
||||||
|
|
||||||
// run the core for one frame
|
// run the core for one frame
|
||||||
Frame++;
|
|
||||||
Api.core.snes_run();
|
Api.core.snes_run();
|
||||||
|
Frame++;
|
||||||
|
|
||||||
if (IsLagFrame)
|
if (IsLagFrame)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BizHawk.Common;
|
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Nintendo.BSNES
|
namespace BizHawk.Emulation.Cores.Nintendo.BSNES
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
|
||||||
{
|
{
|
||||||
[PortedCore(CoreNames.Bsnes115, "bsnes team", "v115+", "https://bsnes.dev", isReleased: false)]
|
[PortedCore(CoreNames.Bsnes115, "bsnes team", "v115+", "https://bsnes.dev", isReleased: false)]
|
||||||
[ServiceNotApplicable(new[] { typeof(IDriveLight) })]
|
[ServiceNotApplicable(new[] { typeof(IDriveLight) })]
|
||||||
public unsafe partial class BsnesCore : IEmulator, IVideoProvider, ISaveRam, IStatable, IInputPollable, IRegionable, ISettable<BsnesCore.SnesSettings, BsnesCore.SnesSyncSettings>
|
public unsafe partial class BsnesCore : IEmulator, IDebuggable, IVideoProvider, ISaveRam, IStatable, IInputPollable, IRegionable, ISettable<BsnesCore.SnesSettings, BsnesCore.SnesSyncSettings>
|
||||||
{
|
{
|
||||||
private BsnesApi.SNES_REGION _region;
|
private BsnesApi.SNES_REGION _region;
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ auto CPU::Enter() -> void {
|
||||||
while(true) {
|
while(true) {
|
||||||
scheduler.synchronize();
|
scheduler.synchronize();
|
||||||
cpu.main();
|
cpu.main();
|
||||||
|
if (scheduler.StepOnce) scheduler.leave(Scheduler::Event::Desynchronized);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ namespace SuperFamicom {
|
||||||
cothread_t host = nullptr;
|
cothread_t host = nullptr;
|
||||||
cothread_t active = nullptr;
|
cothread_t active = nullptr;
|
||||||
bool desynchronized = false;
|
bool desynchronized = false;
|
||||||
|
bool StepOnce = false;
|
||||||
|
|
||||||
auto enter() -> void {
|
auto enter() -> void {
|
||||||
host = co_active();
|
host = co_active();
|
||||||
|
|
|
@ -361,3 +361,55 @@ EXPORT void snes_bus_write(unsigned addr, uint8_t value)
|
||||||
{
|
{
|
||||||
bus.write(addr, 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;
|
||||||
|
}
|
||||||
|
|
|
@ -33,6 +33,15 @@ struct LayerEnables
|
||||||
bool Obj_Prio0, Obj_Prio1, Obj_Prio2, Obj_Prio3;
|
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
|
// below code unused; would be useful for the graphics debugger
|
||||||
//$2105
|
//$2105
|
||||||
|
|
Loading…
Reference in New Issue