snes: waterboxification phase 2
This commit is contained in:
parent
9975a05695
commit
36be9e9fc7
|
@ -659,9 +659,11 @@ namespace BizHawk.Client.EmuHawk
|
||||||
SNESGraphicsDecoder NewDecoder()
|
SNESGraphicsDecoder NewDecoder()
|
||||||
{
|
{
|
||||||
//wtf to do? now we need an api all the time
|
//wtf to do? now we need an api all the time
|
||||||
|
throw new NotImplementedException("TODO");
|
||||||
|
/*
|
||||||
if (currentSnesCore != null)
|
if (currentSnesCore != null)
|
||||||
return new SNESGraphicsDecoder(currentSnesCore.Api, currentSnesCore.CurrPalette);
|
return new SNESGraphicsDecoder(currentSnesCore.Api, currentSnesCore.CurrPalette);
|
||||||
else return new SNESGraphicsDecoder(currentSnesCore.Api, SnesColors.ColorType.BizHawk);
|
else return new SNESGraphicsDecoder(currentSnesCore.Api, SnesColors.ColorType.BizHawk);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderPalette()
|
void RenderPalette()
|
||||||
|
|
|
@ -10,28 +10,44 @@ using BizHawk.Common.BizInvoke;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
{
|
{
|
||||||
public unsafe partial class LibsnesApi : IDisposable
|
public abstract unsafe class CoreImpl
|
||||||
{
|
{
|
||||||
/*[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
|
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
|
||||||
public static unsafe extern void* CopyMemory(void* dest, void* src, ulong count);*/
|
public abstract IntPtr DllInit();
|
||||||
|
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
|
||||||
|
public abstract void Message(LibsnesApi.eMessage msg);
|
||||||
|
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
|
||||||
|
public abstract void CopyBuffer(int id, void* ptr, int size);
|
||||||
|
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
|
||||||
|
public abstract void SetBuffer(int id, void* ptr, int size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe partial class LibsnesApi : IDisposable, IMonitor
|
||||||
|
{
|
||||||
|
static LibsnesApi()
|
||||||
|
{
|
||||||
|
if (sizeof(CommStruct) != 232)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("sizeof(comm)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private PeRunner _exe;
|
private PeRunner _exe;
|
||||||
private CoreImpl _core;
|
private CoreImpl _core;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private CommStruct* _comm;
|
private CommStruct* _comm;
|
||||||
|
private readonly Dictionary<string, IntPtr> _sharedMemoryBlocks = new Dictionary<string, IntPtr>();
|
||||||
|
|
||||||
private abstract class CoreImpl
|
public void Enter()
|
||||||
{
|
{
|
||||||
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
|
_exe.Enter();
|
||||||
public abstract IntPtr DllInit();
|
|
||||||
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
|
|
||||||
public abstract void Message(eMessage msg);
|
|
||||||
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
|
|
||||||
public abstract void CopyBuffer(int id, void* ptr, int size);
|
|
||||||
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
|
|
||||||
public abstract void SetBuffer(int id, void* ptr, int size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Exit()
|
||||||
|
{
|
||||||
|
_exe.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
public LibsnesApi(string dllPath)
|
public LibsnesApi(string dllPath)
|
||||||
{
|
{
|
||||||
|
@ -41,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
Path = dllPath,
|
Path = dllPath,
|
||||||
SbrkHeapSizeKB = 32 * 1024,
|
SbrkHeapSizeKB = 32 * 1024,
|
||||||
InvisibleHeapSizeKB = 32 * 1024,
|
InvisibleHeapSizeKB = 32 * 1024,
|
||||||
MmapHeapSizeKB = 32 * 1024,
|
MmapHeapSizeKB = 256 * 1024,
|
||||||
PlainHeapSizeKB = 32 * 1024,
|
PlainHeapSizeKB = 32 * 1024,
|
||||||
SealedHeapSizeKB = 32 * 1024
|
SealedHeapSizeKB = 32 * 1024
|
||||||
});
|
});
|
||||||
|
@ -54,6 +70,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
{
|
{
|
||||||
if (!_disposed)
|
if (!_disposed)
|
||||||
{
|
{
|
||||||
|
_disposed = true;
|
||||||
_exe.Dispose();
|
_exe.Dispose();
|
||||||
_exe = null;
|
_exe = null;
|
||||||
_core = null;
|
_core = null;
|
||||||
|
@ -137,9 +154,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
BRR = 0x80,
|
BRR = 0x80,
|
||||||
};
|
};
|
||||||
|
|
||||||
//Dictionary<string, SharedMemoryBlock> SharedMemoryBlocks = new Dictionary<string, SharedMemoryBlock>();
|
|
||||||
//Dictionary<string, SharedMemoryBlock> DeallocatedMemoryBlocks = new Dictionary<string, SharedMemoryBlock>();
|
|
||||||
|
|
||||||
snes_video_refresh_t video_refresh;
|
snes_video_refresh_t video_refresh;
|
||||||
snes_input_poll_t input_poll;
|
snes_input_poll_t input_poll;
|
||||||
snes_input_state_t input_state;
|
snes_input_state_t input_state;
|
||||||
|
@ -165,15 +179,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
public delegate void snes_trace_t(uint which, string msg);
|
public delegate void snes_trace_t(uint which, string msg);
|
||||||
|
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public struct CPURegs
|
public struct CPURegs
|
||||||
{
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
public uint pc;
|
public uint pc;
|
||||||
|
[FieldOffset(4)]
|
||||||
public ushort a, x, y, z, s, d, vector; //7x
|
public ushort a, x, y, z, s, d, vector; //7x
|
||||||
|
[FieldOffset(18)]
|
||||||
public byte p, nothing;
|
public byte p, nothing;
|
||||||
|
[FieldOffset(20)]
|
||||||
public uint aa, rd;
|
public uint aa, rd;
|
||||||
|
[FieldOffset(28)]
|
||||||
public byte sp, dp, db, mdr;
|
public byte sp, dp, db, mdr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct LayerEnables
|
public struct LayerEnables
|
||||||
{
|
{
|
||||||
byte _BG1_Prio0, _BG1_Prio1;
|
byte _BG1_Prio0, _BG1_Prio1;
|
||||||
|
@ -197,43 +218,63 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
public bool Obj_Prio3 { get { return _Obj_Prio3 != 0; } set { _Obj_Prio3 = (byte)(value ? 1 : 0); } }
|
public bool Obj_Prio3 { get { return _Obj_Prio3 != 0; } set { _Obj_Prio3 = (byte)(value ? 1 : 0); } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
struct CommStruct
|
struct CommStruct
|
||||||
{
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
//the cmd being executed
|
//the cmd being executed
|
||||||
public eMessage cmd;
|
public eMessage cmd;
|
||||||
|
[FieldOffset(4)]
|
||||||
//the status of the core
|
//the status of the core
|
||||||
public eStatus status;
|
public eStatus status;
|
||||||
|
[FieldOffset(8)]
|
||||||
//the SIG or BRK that the core is halted in
|
//the SIG or BRK that the core is halted in
|
||||||
public eMessage reason;
|
public eMessage reason;
|
||||||
|
|
||||||
//flexible in/out parameters
|
//flexible in/out parameters
|
||||||
//these are all "overloaded" a little so it isn't clear what's used for what in for any particular message..
|
//these are all "overloaded" a little so it isn't clear what's used for what in for any particular message..
|
||||||
//but I think it will beat having to have some kind of extremely verbose custom layouts for every message
|
//but I think it will beat having to have some kind of extremely verbose custom layouts for every message
|
||||||
|
[FieldOffset(16)]
|
||||||
public sbyte* str;
|
public sbyte* str;
|
||||||
|
[FieldOffset(24)]
|
||||||
public void* ptr;
|
public void* ptr;
|
||||||
|
[FieldOffset(32)]
|
||||||
public uint id, addr, value, size;
|
public uint id, addr, value, size;
|
||||||
|
[FieldOffset(48)]
|
||||||
public int port, device, index, slot;
|
public int port, device, index, slot;
|
||||||
|
[FieldOffset(64)]
|
||||||
public int width, height;
|
public int width, height;
|
||||||
|
[FieldOffset(72)]
|
||||||
public int scanline;
|
public int scanline;
|
||||||
|
[FieldOffset(76)]
|
||||||
public fixed int inports[2];
|
public fixed int inports[2];
|
||||||
|
|
||||||
|
[FieldOffset(88)]
|
||||||
//this should always be used in pairs
|
//this should always be used in pairs
|
||||||
public fixed uint buf[3]; //ACTUALLY A POINTER but can't marshal it :(
|
public fixed long buf[3]; //ACTUALLY A POINTER but can't marshal it :(
|
||||||
|
[FieldOffset(112)]
|
||||||
public fixed int buf_size[3];
|
public fixed int buf_size[3];
|
||||||
|
|
||||||
|
[FieldOffset(128)]
|
||||||
//bleck. this is a long so that it can be a 32/64bit pointer
|
//bleck. this is a long so that it can be a 32/64bit pointer
|
||||||
public fixed long cdl_ptr[4];
|
public fixed long cdl_ptr[4];
|
||||||
|
[FieldOffset(160)]
|
||||||
public fixed int cdl_size[4];
|
public fixed int cdl_size[4];
|
||||||
|
|
||||||
|
[FieldOffset(176)]
|
||||||
public CPURegs cpuregs;
|
public CPURegs cpuregs;
|
||||||
|
[FieldOffset(208)]
|
||||||
public LayerEnables layerEnables;
|
public LayerEnables layerEnables;
|
||||||
|
|
||||||
|
[FieldOffset(220)]
|
||||||
//static configuration-type information which can be grabbed off the core at any time without even needing a QUERY command
|
//static configuration-type information which can be grabbed off the core at any time without even needing a QUERY command
|
||||||
public SNES_REGION region;
|
public SNES_REGION region;
|
||||||
|
[FieldOffset(224)]
|
||||||
public SNES_MAPPER mapper;
|
public SNES_MAPPER mapper;
|
||||||
|
|
||||||
|
[FieldOffset(228)]
|
||||||
|
public int unused;
|
||||||
|
|
||||||
//utilities
|
//utilities
|
||||||
//TODO: make internal, wrap on the API instead of the comm
|
//TODO: make internal, wrap on the API instead of the comm
|
||||||
public unsafe string GetAscii() { return _getAscii(str); }
|
public unsafe string GetAscii() { return _getAscii(str); }
|
||||||
|
|
|
@ -76,16 +76,5 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
return _comm->GetBool();
|
return _comm->GetBool();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CMD_term()
|
|
||||||
{
|
|
||||||
_core.Message(eMessage.eMessage_CMD_term);
|
|
||||||
WaitForCMD();
|
|
||||||
}
|
|
||||||
public void CMD_unload_cartridge()
|
|
||||||
{
|
|
||||||
_core.Message(eMessage.eMessage_CMD_unload_cartridge);
|
|
||||||
WaitForCMD();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -31,9 +31,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
public byte* QUERY_get_memory_data(SNES_MEMORY id)
|
public byte* QUERY_get_memory_data(SNES_MEMORY id)
|
||||||
{
|
{
|
||||||
string name = QUERY_MemoryNameForId(id);
|
string name = QUERY_MemoryNameForId(id);
|
||||||
if (!SharedMemoryBlocks.ContainsKey(name)) return null;
|
IntPtr ret;
|
||||||
var smb = SharedMemoryBlocks[name];
|
_sharedMemoryBlocks.TryGetValue(name, out ret);
|
||||||
return (byte*)smb.Ptr;
|
return (byte*)ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte QUERY_peek(SNES_MEMORY id, uint addr)
|
public byte QUERY_peek(SNES_MEMORY id, uint addr)
|
||||||
|
|
|
@ -8,127 +8,89 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
{
|
{
|
||||||
bool Handle_SIG(eMessage msg)
|
bool Handle_SIG(eMessage msg)
|
||||||
{
|
{
|
||||||
switch (msg)
|
using (_exe.EnterExit())
|
||||||
{
|
{
|
||||||
default:
|
switch (msg)
|
||||||
return false;
|
{
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
|
||||||
case eMessage.eMessage_SIG_video_refresh:
|
case eMessage.eMessage_SIG_video_refresh:
|
||||||
{
|
|
||||||
int width = comm->width;
|
|
||||||
int height = comm->height;
|
|
||||||
if (video_refresh != null)
|
|
||||||
{
|
{
|
||||||
video_refresh((int*)comm->ptr, width, height);
|
int width = _comm->width;
|
||||||
|
int height = _comm->height;
|
||||||
|
video_refresh?.Invoke((int*)_comm->ptr, width, height);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case eMessage.eMessage_SIG_input_poll:
|
||||||
break;
|
break;
|
||||||
}
|
case eMessage.eMessage_SIG_input_state:
|
||||||
case eMessage.eMessage_SIG_input_poll:
|
|
||||||
break;
|
|
||||||
case eMessage.eMessage_SIG_input_state:
|
|
||||||
{
|
|
||||||
int port = comm->port;
|
|
||||||
int device = comm->device;
|
|
||||||
int index = comm->index;
|
|
||||||
int id = (int)comm->id;
|
|
||||||
if (input_state != null)
|
|
||||||
comm->value = (uint)input_state(port, device, index, id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eMessage.eMessage_SIG_input_notify:
|
|
||||||
{
|
|
||||||
if (input_notify != null)
|
|
||||||
input_notify(comm->index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eMessage.eMessage_SIG_audio_flush:
|
|
||||||
{
|
|
||||||
uint nsamples = comm->size;
|
|
||||||
|
|
||||||
if (audio_sample != null)
|
|
||||||
{
|
{
|
||||||
ushort* audiobuffer = ((ushort*)comm->ptr);
|
int port = _comm->port;
|
||||||
for (uint i = 0; i < nsamples; )
|
int device = _comm->device;
|
||||||
|
int index = _comm->index;
|
||||||
|
int id = (int)_comm->id;
|
||||||
|
if (input_state != null)
|
||||||
|
_comm->value = (uint)input_state(port, device, index, id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eMessage.eMessage_SIG_input_notify:
|
||||||
|
{
|
||||||
|
input_notify?.Invoke(_comm->index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eMessage.eMessage_SIG_audio_flush:
|
||||||
|
{
|
||||||
|
uint nsamples = _comm->size;
|
||||||
|
|
||||||
|
if (audio_sample != null)
|
||||||
{
|
{
|
||||||
ushort left = audiobuffer[i++];
|
ushort* audiobuffer = ((ushort*)_comm->ptr);
|
||||||
ushort right = audiobuffer[i++];
|
for (uint i = 0; i < nsamples;)
|
||||||
audio_sample(left, right);
|
{
|
||||||
|
ushort left = audiobuffer[i++];
|
||||||
|
ushort right = audiobuffer[i++];
|
||||||
|
audio_sample(left, right);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case eMessage.eMessage_SIG_path_request:
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eMessage.eMessage_SIG_path_request:
|
|
||||||
{
|
|
||||||
int slot = comm->slot;
|
|
||||||
string hint = comm->GetAscii();
|
|
||||||
string ret = hint;
|
|
||||||
if (pathRequest != null)
|
|
||||||
hint = pathRequest(slot, hint);
|
|
||||||
CopyAscii(0, hint);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eMessage.eMessage_SIG_trace_callback:
|
|
||||||
{
|
|
||||||
if (traceCallback != null)
|
|
||||||
traceCallback(comm->value, comm->GetAscii());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eMessage.eMessage_SIG_allocSharedMemory:
|
|
||||||
{
|
|
||||||
var name = comm->GetAscii();
|
|
||||||
var size = comm->size;
|
|
||||||
|
|
||||||
if (SharedMemoryBlocks.ContainsKey(name))
|
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Re-defined a shared memory block. Check bsnes init/shutdown code. Block name: " + name);
|
int slot = _comm->slot;
|
||||||
|
string hint = _comm->GetAscii();
|
||||||
|
string ret = hint;
|
||||||
|
if (pathRequest != null)
|
||||||
|
hint = pathRequest(slot, hint);
|
||||||
|
CopyAscii(0, hint);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case eMessage.eMessage_SIG_trace_callback:
|
||||||
//try reusing existing block; dispose it if it exists and if the size doesnt match
|
|
||||||
SharedMemoryBlock smb = null;
|
|
||||||
if (DeallocatedMemoryBlocks.ContainsKey(name))
|
|
||||||
{
|
{
|
||||||
smb = DeallocatedMemoryBlocks[name];
|
traceCallback?.Invoke(_comm->value, _comm->GetAscii());
|
||||||
DeallocatedMemoryBlocks.Remove(name);
|
break;
|
||||||
if (smb.Size != size)
|
|
||||||
{
|
|
||||||
smb.Dispose();
|
|
||||||
smb = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
case eMessage.eMessage_SIG_allocSharedMemory:
|
||||||
//allocate a new block if we have to
|
|
||||||
if (smb == null)
|
|
||||||
{
|
{
|
||||||
smb = new SharedMemoryBlock();
|
// NB: shared memory blocks are allocated on the unmanaged side
|
||||||
smb.Name = name;
|
var name = _comm->GetAscii();
|
||||||
smb.Size = (int)size;
|
var size = _comm->size;
|
||||||
smb.BlockName = InstanceName + smb.Name;
|
var ptr = _comm->ptr;
|
||||||
smb.Allocate();
|
|
||||||
}
|
|
||||||
|
|
||||||
comm->ptr = smb.Ptr;
|
if (_sharedMemoryBlocks.ContainsKey(name))
|
||||||
SharedMemoryBlocks[smb.Name] = smb;
|
throw new InvalidOperationException("Re-defined a shared memory block. Check bsnes init/shutdown code. Block name: " + name);
|
||||||
CopyAscii(0, smb.BlockName);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case eMessage.eMessage_SIG_freeSharedMemory:
|
|
||||||
{
|
|
||||||
foreach (var block in SharedMemoryBlocks.Values)
|
|
||||||
{
|
|
||||||
if (block.Ptr == comm->ptr)
|
|
||||||
{
|
|
||||||
DeallocatedMemoryBlocks[block.Name] = block;
|
|
||||||
SharedMemoryBlocks.Remove(block.Name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} //switch(msg)
|
|
||||||
|
|
||||||
Message(eMessage.eMessage_Resume);
|
_sharedMemoryBlocks.Add(name, (IntPtr)ptr);
|
||||||
return true;
|
break;
|
||||||
|
}
|
||||||
|
case eMessage.eMessage_SIG_freeSharedMemory:
|
||||||
|
throw new InvalidOperationException("Unexpected call: SIG_freeSharedMemory");
|
||||||
|
} //switch(msg)
|
||||||
|
|
||||||
|
_core.Message(eMessage.eMessage_Resume);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
|
|
||||||
IsLagFrame = true;
|
IsLagFrame = true;
|
||||||
|
|
||||||
if (!_nocallbacks && _tracer.Enabled)
|
if (_tracer.Enabled)
|
||||||
{
|
{
|
||||||
//Api.QUERY_set_trace_callback(1<<(int)LibsnesApi.eTRACE.SMP, _tracecb); //TEST -- it works but theres no way to control it from the frontend now
|
//Api.QUERY_set_trace_callback(1<<(int)LibsnesApi.eTRACE.SMP, _tracecb); //TEST -- it works but theres no way to control it from the frontend now
|
||||||
|
|
||||||
|
@ -36,21 +36,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
Api.QUERY_set_trace_callback(0,null);
|
Api.QUERY_set_trace_callback(0,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for deterministic emulation, save the state we're going to use before frame advance
|
|
||||||
// don't do this during nocallbacks though, since it's already been done
|
|
||||||
if (!_nocallbacks && DeterministicEmulation)
|
|
||||||
{
|
|
||||||
var ms = new MemoryStream();
|
|
||||||
var bw = new BinaryWriter(ms);
|
|
||||||
bw.Write(CoreSaveState());
|
|
||||||
bw.Write(false); // not framezero
|
|
||||||
var ssc = new SaveController();
|
|
||||||
ssc.CopyFrom(controller);
|
|
||||||
ssc.Serialize(bw);
|
|
||||||
bw.Close();
|
|
||||||
_savestatebuff = ms.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
// speedup when sound rendering is not needed
|
// speedup when sound rendering is not needed
|
||||||
Api.QUERY_set_audio_sample(rendersound ? _soundcb : null);
|
Api.QUERY_set_audio_sample(rendersound ? _soundcb : null);
|
||||||
|
|
||||||
|
@ -104,14 +89,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SystemId { get; }
|
public string SystemId { get; }
|
||||||
|
public bool DeterministicEmulation => true;
|
||||||
// adelikat: Nasty hack to force new business logic. Compatibility (and Accuracy when fully supported) will ALWAYS be in deterministic mode,
|
|
||||||
// a consequence is a permanent performance hit to the compatibility core
|
|
||||||
// Perormance will NEVER be in deterministic mode (and the client side logic will prohibit movie recording on it)
|
|
||||||
// feos: Nasty hack to a nasty hack. Allow user disable it with a strong warning.
|
|
||||||
public bool DeterministicEmulation =>
|
|
||||||
_settings.ForceDeterminism
|
|
||||||
&& (CurrentProfile == "Compatibility" || CurrentProfile == "Accuracy");
|
|
||||||
|
|
||||||
public void ResetCounters()
|
public void ResetCounters()
|
||||||
{
|
{
|
||||||
|
@ -129,15 +107,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
|
|
||||||
Api.CMD_unload_cartridge();
|
|
||||||
Api.CMD_term();
|
|
||||||
|
|
||||||
_resampler.Dispose();
|
|
||||||
Api.Dispose();
|
Api.Dispose();
|
||||||
|
_resampler.Dispose();
|
||||||
|
|
||||||
_currCdl?.Unpin();
|
_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,8 +84,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
private unsafe void MakeMemoryDomain(string name, LibsnesApi.SNES_MEMORY id, MemoryDomain.Endian endian, int byteSize = 1)
|
private unsafe void MakeMemoryDomain(string name, LibsnesApi.SNES_MEMORY id, MemoryDomain.Endian endian, int byteSize = 1)
|
||||||
{
|
{
|
||||||
int size = Api.QUERY_get_memory_size(id);
|
int size = Api.QUERY_get_memory_size(id);
|
||||||
int mask = size - 1;
|
|
||||||
bool pow2 = Util.IsPowerOfTwo(size);
|
|
||||||
|
|
||||||
// if this type of memory isnt available, dont make the memory domain (most commonly save ram)
|
// if this type of memory isnt available, dont make the memory domain (most commonly save ram)
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
|
@ -95,46 +93,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
|
|
||||||
byte* blockptr = Api.QUERY_get_memory_data(id);
|
byte* blockptr = Api.QUERY_get_memory_data(id);
|
||||||
|
|
||||||
MemoryDomain md;
|
var md = new MemoryDomainIntPtrMonitor(name, MemoryDomain.Endian.Little, (IntPtr)blockptr, size, true, byteSize, Api);
|
||||||
|
|
||||||
if (id == LibsnesApi.SNES_MEMORY.OAM)
|
|
||||||
{
|
|
||||||
// OAM is actually two differently sized banks of memory which arent truly considered adjacent.
|
|
||||||
// maybe a better way to visualize it is with an empty bus and adjacent banks
|
|
||||||
// so, we just throw away everything above its size of 544 bytes
|
|
||||||
if (size != 544)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("oam size isnt 544 bytes.. wtf?");
|
|
||||||
}
|
|
||||||
|
|
||||||
md = new MemoryDomainDelegate(
|
|
||||||
name,
|
|
||||||
size,
|
|
||||||
endian,
|
|
||||||
addr => addr < 544 ? blockptr[addr] : (byte)0x00,
|
|
||||||
(addr, value) => { if (addr < 544) { blockptr[addr] = value; } },
|
|
||||||
byteSize);
|
|
||||||
}
|
|
||||||
else if (pow2)
|
|
||||||
{
|
|
||||||
md = new MemoryDomainDelegate(
|
|
||||||
name,
|
|
||||||
size,
|
|
||||||
endian,
|
|
||||||
addr => blockptr[addr & mask],
|
|
||||||
(addr, value) => blockptr[addr & mask] = value,
|
|
||||||
byteSize);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
md = new MemoryDomainDelegate(
|
|
||||||
name,
|
|
||||||
size,
|
|
||||||
endian,
|
|
||||||
addr => blockptr[addr % size],
|
|
||||||
(addr, value) => blockptr[addr % size] = value,
|
|
||||||
byteSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
_memoryDomainList.Add(md);
|
_memoryDomainList.Add(md);
|
||||||
}
|
}
|
||||||
|
@ -152,19 +111,25 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
var md = new MemoryDomainDelegate("System Bus", 0x1000000, MemoryDomain.Endian.Little,
|
var md = new MemoryDomainDelegate("System Bus", 0x1000000, MemoryDomain.Endian.Little,
|
||||||
addr =>
|
addr =>
|
||||||
{
|
{
|
||||||
var a = FakeBusMap((int)addr);
|
using (Api.EnterExit())
|
||||||
if (a.HasValue)
|
|
||||||
{
|
{
|
||||||
return blockptr[a.Value];
|
var a = FakeBusMap((int)addr);
|
||||||
}
|
if (a.HasValue)
|
||||||
|
{
|
||||||
|
return blockptr[a.Value];
|
||||||
|
}
|
||||||
|
|
||||||
return FakeBusRead((int)addr);
|
return FakeBusRead((int)addr);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(addr, val) =>
|
(addr, val) =>
|
||||||
{
|
{
|
||||||
var a = FakeBusMap((int)addr);
|
using (Api.EnterExit())
|
||||||
if (a.HasValue)
|
{
|
||||||
blockptr[a.Value] = val;
|
var a = FakeBusMap((int)addr);
|
||||||
|
if (a.HasValue)
|
||||||
|
blockptr[a.Value] = val;
|
||||||
|
}
|
||||||
}, wordSize: 2);
|
}, wordSize: 2);
|
||||||
_memoryDomainList.Add(md);
|
_memoryDomainList.Add(md);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,26 +12,26 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
|
|
||||||
public void SaveStateText(TextWriter writer)
|
public void SaveStateText(TextWriter writer)
|
||||||
{
|
{
|
||||||
var temp = SaveStateBinary();
|
/*var temp = SaveStateBinary();
|
||||||
temp.SaveAsHexFast(writer);
|
temp.SaveAsHexFast(writer);
|
||||||
writer.WriteLine("Frame {0}", Frame); // we don't parse this, it's only for the client to use
|
writer.WriteLine("Frame {0}", Frame); // we don't parse this, it's only for the client to use
|
||||||
writer.WriteLine("Profile {0}", CurrentProfile);
|
writer.WriteLine("Profile {0}", CurrentProfile);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadStateText(TextReader reader)
|
public void LoadStateText(TextReader reader)
|
||||||
{
|
{
|
||||||
string hex = reader.ReadLine();
|
/*string hex = reader.ReadLine();
|
||||||
byte[] state = new byte[hex.Length / 2];
|
byte[] state = new byte[hex.Length / 2];
|
||||||
state.ReadFromHexFast(hex);
|
state.ReadFromHexFast(hex);
|
||||||
LoadStateBinary(new BinaryReader(new MemoryStream(state)));
|
LoadStateBinary(new BinaryReader(new MemoryStream(state)));
|
||||||
reader.ReadLine(); // Frame #
|
reader.ReadLine(); // Frame #
|
||||||
var profile = reader.ReadLine().Split(' ')[1];
|
var profile = reader.ReadLine().Split(' ')[1];
|
||||||
ValidateLoadstateProfile(profile);
|
ValidateLoadstateProfile(profile);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveStateBinary(BinaryWriter writer)
|
public void SaveStateBinary(BinaryWriter writer)
|
||||||
{
|
{
|
||||||
writer.Write(DeterministicEmulation ? _savestatebuff : CoreSaveState());
|
/*writer.Write(DeterministicEmulation ? _savestatebuff : CoreSaveState());
|
||||||
|
|
||||||
// other variables
|
// other variables
|
||||||
writer.Write(IsLagFrame);
|
writer.Write(IsLagFrame);
|
||||||
|
@ -39,12 +39,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
writer.Write(Frame);
|
writer.Write(Frame);
|
||||||
writer.Write(CurrentProfile);
|
writer.Write(CurrentProfile);
|
||||||
|
|
||||||
writer.Flush();
|
writer.Flush();*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadStateBinary(BinaryReader reader)
|
public void LoadStateBinary(BinaryReader reader)
|
||||||
{
|
{
|
||||||
int size = Api.QUERY_serialize_size();
|
/*int size = Api.QUERY_serialize_size();
|
||||||
byte[] buf = reader.ReadBytes(size);
|
byte[] buf = reader.ReadBytes(size);
|
||||||
CoreLoadState(buf);
|
CoreLoadState(buf);
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
LagCount = reader.ReadInt32();
|
LagCount = reader.ReadInt32();
|
||||||
Frame = reader.ReadInt32();
|
Frame = reader.ReadInt32();
|
||||||
var profile = reader.ReadString();
|
var profile = reader.ReadString();
|
||||||
ValidateLoadstateProfile(profile);
|
ValidateLoadstateProfile(profile);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] SaveStateBinary()
|
public byte[] SaveStateBinary()
|
||||||
|
@ -90,34 +90,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
return ms.ToArray();
|
return ms.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle the unmanaged part of loadstating
|
|
||||||
private void CoreLoadState(byte[] data)
|
|
||||||
{
|
|
||||||
int size = Api.QUERY_serialize_size();
|
|
||||||
if (data.Length != size)
|
|
||||||
{
|
|
||||||
throw new Exception("Libsnes internal savestate size mismatch!");
|
|
||||||
}
|
|
||||||
|
|
||||||
Api.CMD_init();
|
|
||||||
|
|
||||||
// zero 01-sep-2014 - this approach isn't being used anymore, it's too slow!
|
|
||||||
// LoadCurrent(); //need to make sure chip roms are reloaded
|
|
||||||
fixed (byte* pbuf = &data[0])
|
|
||||||
Api.CMD_unserialize(new IntPtr(pbuf), size);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// handle the unmanaged part of savestating
|
|
||||||
private byte[] CoreSaveState()
|
|
||||||
{
|
|
||||||
int size = Api.QUERY_serialize_size();
|
|
||||||
byte[] buf = new byte[size];
|
|
||||||
fixed (byte* pbuf = &buf[0])
|
|
||||||
Api.CMD_serialize(new IntPtr(pbuf), size);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ValidateLoadstateProfile(string profile)
|
private void ValidateLoadstateProfile(string profile)
|
||||||
{
|
{
|
||||||
if (profile != CurrentProfile)
|
if (profile != CurrentProfile)
|
||||||
|
@ -125,8 +97,5 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
throw new InvalidOperationException($"You've attempted to load a savestate made using a different SNES profile ({profile}) than your current configuration ({CurrentProfile}). We COULD automatically switch for you, but we havent done that yet. This error is to make sure you know that this isnt going to work right now.");
|
throw new InvalidOperationException($"You've attempted to load a savestate made using a different SNES profile ({profile}) than your current configuration ({CurrentProfile}). We COULD automatically switch for you, but we havent done that yet. This error is to make sure you know that this isnt going to work right now.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// most recent internal savestate, for deterministic mode ONLY
|
|
||||||
private byte[] _savestatebuff;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
_settings = (SnesSettings)settings ?? new SnesSettings();
|
_settings = (SnesSettings)settings ?? new SnesSettings();
|
||||||
_syncSettings = (SnesSyncSettings)syncSettings ?? new SnesSyncSettings();
|
_syncSettings = (SnesSyncSettings)syncSettings ?? new SnesSyncSettings();
|
||||||
|
|
||||||
Api = new LibsnesApi(GetDllPath())
|
// TODO: pass profile here
|
||||||
|
Api = new LibsnesApi(CoreComm.CoreFileProvider.DllPath())
|
||||||
{
|
{
|
||||||
ReadHook = ReadHook,
|
ReadHook = ReadHook,
|
||||||
ExecHook = ExecHook,
|
ExecHook = ExecHook,
|
||||||
|
@ -169,17 +170,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
|
|
||||||
SetupMemoryDomains(romData, sgbRomData);
|
SetupMemoryDomains(romData, sgbRomData);
|
||||||
|
|
||||||
// DeterministicEmulation = deterministicEmulation; // Note we don't respect the value coming in and force it instead
|
|
||||||
if (DeterministicEmulation) // save frame-0 savestate now
|
|
||||||
{
|
|
||||||
var ms = new MemoryStream();
|
|
||||||
var bw = new BinaryWriter(ms);
|
|
||||||
bw.Write(CoreSaveState());
|
|
||||||
bw.Write(true); // framezero, so no controller follows and don't frameadvance on load
|
|
||||||
bw.Close();
|
|
||||||
_savestatebuff = ms.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CurrentProfile == "Compatibility")
|
if (CurrentProfile == "Compatibility")
|
||||||
{
|
{
|
||||||
ser.Register<ITraceable>(_tracer);
|
ser.Register<ITraceable>(_tracer);
|
||||||
|
@ -198,7 +188,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
private LoadParams _currLoadParams;
|
private LoadParams _currLoadParams;
|
||||||
private SpeexResampler _resampler;
|
private SpeexResampler _resampler;
|
||||||
private int _timeFrameCounter;
|
private int _timeFrameCounter;
|
||||||
private bool _nocallbacks; // disable all external callbacks. the front end should not even know the core is frame advancing
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public bool IsSGB { get; }
|
public bool IsSGB { get; }
|
||||||
|
@ -222,7 +211,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LibsnesApi Api { get; }
|
private LibsnesApi Api { get; }
|
||||||
|
|
||||||
public SnesColors.ColorType CurrPalette { get; private set; }
|
public SnesColors.ColorType CurrPalette { get; private set; }
|
||||||
|
|
||||||
|
@ -374,20 +363,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
||||||
Api.QUERY_set_color_lut((IntPtr)p);
|
Api.QUERY_set_color_lut((IntPtr)p);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetDllPath()
|
|
||||||
{
|
|
||||||
var exename = "libsneshawk-32-" + CurrentProfile.ToLower() + ".dll";
|
|
||||||
|
|
||||||
string dllPath = Path.Combine(CoreComm.CoreFileProvider.DllPath(), exename);
|
|
||||||
|
|
||||||
if (!File.Exists(dllPath))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Couldn't locate the DLL for SNES emulation for profile: " + CurrentProfile + ". Please make sure you're using a fresh dearchive of a BizHawk distribution.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return dllPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadHook(uint addr)
|
private void ReadHook(uint addr)
|
||||||
{
|
{
|
||||||
MemoryCallbacks.CallReads(addr);
|
MemoryCallbacks.CallReads(addr);
|
||||||
|
|
|
@ -2,9 +2,13 @@ section .text
|
||||||
global co_swap
|
global co_swap
|
||||||
global __imp_co_swap
|
global __imp_co_swap
|
||||||
|
|
||||||
|
; TODO: how to tell GCC it doesn't need this?
|
||||||
|
align 16
|
||||||
|
__imp_co_swap:
|
||||||
|
dq co_swap
|
||||||
|
|
||||||
align 16
|
align 16
|
||||||
co_swap:
|
co_swap:
|
||||||
__imp_co_swap:
|
|
||||||
mov [rdx],rsp
|
mov [rdx],rsp
|
||||||
mov rsp,[rcx]
|
mov rsp,[rcx]
|
||||||
pop rax
|
pop rax
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#define LIBSNES_IMPORT
|
#define LIBSNES_IMPORT
|
||||||
#include "snes/snes.hpp"
|
#include "snes/snes.hpp"
|
||||||
#include "libsnes.hpp"
|
#include "libsnes.hpp"
|
||||||
|
#include <emulibc.h>
|
||||||
|
|
||||||
#include <libco.h>
|
#include <libco.h>
|
||||||
|
|
||||||
|
@ -136,6 +137,8 @@ struct CommStruct
|
||||||
//the SIG or BRK that the core is halted in
|
//the SIG or BRK that the core is halted in
|
||||||
eMessage reason;
|
eMessage reason;
|
||||||
|
|
||||||
|
int32 padding1;
|
||||||
|
|
||||||
//flexible in/out parameters
|
//flexible in/out parameters
|
||||||
//these are all "overloaded" a little so it isn't clear what's used for what in for any particular message..
|
//these are all "overloaded" a little so it isn't clear what's used for what in for any particular message..
|
||||||
//but I think it will beat having to have some kind of extremely verbose custom layouts for every message
|
//but I think it will beat having to have some kind of extremely verbose custom layouts for every message
|
||||||
|
@ -147,10 +150,14 @@ struct CommStruct
|
||||||
int32 scanline;
|
int32 scanline;
|
||||||
SNES::Input::Device inports[2];
|
SNES::Input::Device inports[2];
|
||||||
|
|
||||||
|
int32 padding2;
|
||||||
|
|
||||||
//always used in pairs
|
//always used in pairs
|
||||||
void* buf[3];
|
void* buf[3];
|
||||||
int32 buf_size[3];
|
int32 buf_size[3];
|
||||||
|
|
||||||
|
int32 padding3;
|
||||||
|
|
||||||
int64 cdl_ptr[4];
|
int64 cdl_ptr[4];
|
||||||
int32 cdl_size[4];
|
int32 cdl_size[4];
|
||||||
|
|
||||||
|
@ -161,6 +168,8 @@ struct CommStruct
|
||||||
uint32 region;
|
uint32 region;
|
||||||
uint32 mapper;
|
uint32 mapper;
|
||||||
|
|
||||||
|
int32 padding4;
|
||||||
|
|
||||||
//===========================================================
|
//===========================================================
|
||||||
|
|
||||||
//private stuff
|
//private stuff
|
||||||
|
@ -282,8 +291,11 @@ void* snes_allocSharedMemory(const char* memtype, size_t amt)
|
||||||
//its important that this happen before the message marshaling because allocation/free attempts can happen before the marshaling is setup (or at shutdown time, in case of errors?)
|
//its important that this happen before the message marshaling because allocation/free attempts can happen before the marshaling is setup (or at shutdown time, in case of errors?)
|
||||||
//if(!running) return NULL;
|
//if(!running) return NULL;
|
||||||
|
|
||||||
|
auto ret = alloc_plain(amt);
|
||||||
|
|
||||||
comm.str = (char*)memtype;
|
comm.str = (char*)memtype;
|
||||||
comm.size = amt;
|
comm.size = amt;
|
||||||
|
comm.ptr = ret;
|
||||||
|
|
||||||
BREAK(eMessage_SIG_allocSharedMemory);
|
BREAK(eMessage_SIG_allocSharedMemory);
|
||||||
|
|
||||||
|
@ -573,6 +585,29 @@ void new_emuthread()
|
||||||
|
|
||||||
EXPORT void* DllInit()
|
EXPORT void* DllInit()
|
||||||
{
|
{
|
||||||
|
#define T(s,n) static_assert(offsetof(CommStruct,s)==n,#n)
|
||||||
|
T(cmd, 0);
|
||||||
|
T(status, 4);
|
||||||
|
T(reason, 8);
|
||||||
|
T(str, 16);
|
||||||
|
T(ptr, 24);
|
||||||
|
T(id, 32);
|
||||||
|
T(port, 48);
|
||||||
|
T(width, 64);
|
||||||
|
T(scanline, 72);
|
||||||
|
T(inports, 76);
|
||||||
|
T(buf, 88);
|
||||||
|
T(buf_size, 112);
|
||||||
|
T(cdl_ptr, 128);
|
||||||
|
T(cdl_size, 160);
|
||||||
|
T(cpuregs, 176);
|
||||||
|
T(layerEnables, 208);
|
||||||
|
T(region, 220);
|
||||||
|
T(mapper, 224);
|
||||||
|
// start of private stuff
|
||||||
|
T(privbuf, 232);
|
||||||
|
#undef T
|
||||||
|
|
||||||
memset(&comm,0,sizeof(comm));
|
memset(&comm,0,sizeof(comm));
|
||||||
|
|
||||||
//make a coroutine thread to run the emulation in. we'll switch back to this cothread when communicating with the frontend
|
//make a coroutine thread to run the emulation in. we'll switch back to this cothread when communicating with the frontend
|
||||||
|
|
Loading…
Reference in New Issue