snes: waterboxification phase 2

This commit is contained in:
nattthebear 2017-06-10 13:43:03 -04:00
parent 9975a05695
commit 36be9e9fc7
11 changed files with 204 additions and 289 deletions

View File

@ -659,9 +659,11 @@ namespace BizHawk.Client.EmuHawk
SNESGraphicsDecoder NewDecoder()
{
//wtf to do? now we need an api all the time
throw new NotImplementedException("TODO");
/*
if (currentSnesCore != null)
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()

View File

@ -10,28 +10,44 @@ using BizHawk.Common.BizInvoke;
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)]
public static unsafe extern void* CopyMemory(void* dest, void* src, ulong count);*/
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
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 CoreImpl _core;
private bool _disposed;
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)]
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);
_exe.Enter();
}
public void Exit()
{
_exe.Exit();
}
public LibsnesApi(string dllPath)
{
@ -41,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
Path = dllPath,
SbrkHeapSizeKB = 32 * 1024,
InvisibleHeapSizeKB = 32 * 1024,
MmapHeapSizeKB = 32 * 1024,
MmapHeapSizeKB = 256 * 1024,
PlainHeapSizeKB = 32 * 1024,
SealedHeapSizeKB = 32 * 1024
});
@ -54,6 +70,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
{
if (!_disposed)
{
_disposed = true;
_exe.Dispose();
_exe = null;
_core = null;
@ -137,9 +154,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
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_input_poll_t input_poll;
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);
[StructLayout(LayoutKind.Explicit)]
public struct CPURegs
{
[FieldOffset(0)]
public uint pc;
[FieldOffset(4)]
public ushort a, x, y, z, s, d, vector; //7x
[FieldOffset(18)]
public byte p, nothing;
[FieldOffset(20)]
public uint aa, rd;
[FieldOffset(28)]
public byte sp, dp, db, mdr;
}
[StructLayout(LayoutKind.Sequential)]
public struct LayerEnables
{
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); } }
}
[StructLayout(LayoutKind.Explicit)]
struct CommStruct
{
[FieldOffset(0)]
//the cmd being executed
public eMessage cmd;
[FieldOffset(4)]
//the status of the core
public eStatus status;
[FieldOffset(8)]
//the SIG or BRK that the core is halted in
public eMessage reason;
//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..
//but I think it will beat having to have some kind of extremely verbose custom layouts for every message
[FieldOffset(16)]
public sbyte* str;
[FieldOffset(24)]
public void* ptr;
[FieldOffset(32)]
public uint id, addr, value, size;
[FieldOffset(48)]
public int port, device, index, slot;
[FieldOffset(64)]
public int width, height;
[FieldOffset(72)]
public int scanline;
[FieldOffset(76)]
public fixed int inports[2];
[FieldOffset(88)]
//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];
[FieldOffset(128)]
//bleck. this is a long so that it can be a 32/64bit pointer
public fixed long cdl_ptr[4];
[FieldOffset(160)]
public fixed int cdl_size[4];
[FieldOffset(176)]
public CPURegs cpuregs;
[FieldOffset(208)]
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
public SNES_REGION region;
[FieldOffset(224)]
public SNES_MAPPER mapper;
[FieldOffset(228)]
public int unused;
//utilities
//TODO: make internal, wrap on the API instead of the comm
public unsafe string GetAscii() { return _getAscii(str); }

View File

@ -76,16 +76,5 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
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();
}
}
}

View File

@ -31,9 +31,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
public byte* QUERY_get_memory_data(SNES_MEMORY id)
{
string name = QUERY_MemoryNameForId(id);
if (!SharedMemoryBlocks.ContainsKey(name)) return null;
var smb = SharedMemoryBlocks[name];
return (byte*)smb.Ptr;
IntPtr ret;
_sharedMemoryBlocks.TryGetValue(name, out ret);
return (byte*)ret;
}
public byte QUERY_peek(SNES_MEMORY id, uint addr)

View File

@ -8,127 +8,89 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
{
bool Handle_SIG(eMessage msg)
{
switch (msg)
using (_exe.EnterExit())
{
default:
return false;
switch (msg)
{
default:
return false;
case eMessage.eMessage_SIG_video_refresh:
{
int width = comm->width;
int height = comm->height;
if (video_refresh != null)
case eMessage.eMessage_SIG_video_refresh:
{
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;
}
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)
case eMessage.eMessage_SIG_input_state:
{
ushort* audiobuffer = ((ushort*)comm->ptr);
for (uint i = 0; i < nsamples; )
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:
{
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 right = audiobuffer[i++];
audio_sample(left, right);
ushort* audiobuffer = ((ushort*)_comm->ptr);
for (uint i = 0; i < nsamples;)
{
ushort left = audiobuffer[i++];
ushort right = audiobuffer[i++];
audio_sample(left, right);
}
}
break;
}
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))
case eMessage.eMessage_SIG_path_request:
{
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;
}
//try reusing existing block; dispose it if it exists and if the size doesnt match
SharedMemoryBlock smb = null;
if (DeallocatedMemoryBlocks.ContainsKey(name))
case eMessage.eMessage_SIG_trace_callback:
{
smb = DeallocatedMemoryBlocks[name];
DeallocatedMemoryBlocks.Remove(name);
if (smb.Size != size)
{
smb.Dispose();
smb = null;
}
traceCallback?.Invoke(_comm->value, _comm->GetAscii());
break;
}
//allocate a new block if we have to
if (smb == null)
case eMessage.eMessage_SIG_allocSharedMemory:
{
smb = new SharedMemoryBlock();
smb.Name = name;
smb.Size = (int)size;
smb.BlockName = InstanceName + smb.Name;
smb.Allocate();
}
// NB: shared memory blocks are allocated on the unmanaged side
var name = _comm->GetAscii();
var size = _comm->size;
var ptr = _comm->ptr;
comm->ptr = smb.Ptr;
SharedMemoryBlocks[smb.Name] = smb;
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)
if (_sharedMemoryBlocks.ContainsKey(name))
throw new InvalidOperationException("Re-defined a shared memory block. Check bsnes init/shutdown code. Block name: " + name);
Message(eMessage.eMessage_Resume);
return true;
_sharedMemoryBlocks.Add(name, (IntPtr)ptr);
break;
}
case eMessage.eMessage_SIG_freeSharedMemory:
throw new InvalidOperationException("Unexpected call: SIG_freeSharedMemory");
} //switch(msg)
_core.Message(eMessage.eMessage_Resume);
return true;
}
}
}
}
}

View File

@ -22,7 +22,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
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
@ -36,21 +36,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
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
Api.QUERY_set_audio_sample(rendersound ? _soundcb : null);
@ -104,14 +89,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
}
public string SystemId { get; }
// 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 bool DeterministicEmulation => true;
public void ResetCounters()
{
@ -129,15 +107,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
return;
}
_disposed = true;
Api.CMD_unload_cartridge();
Api.CMD_term();
_resampler.Dispose();
Api.Dispose();
_resampler.Dispose();
_currCdl?.Unpin();
_disposed = true;
}
}
}

View File

@ -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)
{
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 (size == 0)
@ -95,46 +93,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
byte* blockptr = Api.QUERY_get_memory_data(id);
MemoryDomain md;
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);
}
var md = new MemoryDomainIntPtrMonitor(name, MemoryDomain.Endian.Little, (IntPtr)blockptr, size, true, byteSize, Api);
_memoryDomainList.Add(md);
}
@ -152,19 +111,25 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
var md = new MemoryDomainDelegate("System Bus", 0x1000000, MemoryDomain.Endian.Little,
addr =>
{
var a = FakeBusMap((int)addr);
if (a.HasValue)
using (Api.EnterExit())
{
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) =>
{
var a = FakeBusMap((int)addr);
if (a.HasValue)
blockptr[a.Value] = val;
using (Api.EnterExit())
{
var a = FakeBusMap((int)addr);
if (a.HasValue)
blockptr[a.Value] = val;
}
}, wordSize: 2);
_memoryDomainList.Add(md);
}

View File

@ -12,26 +12,26 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
public void SaveStateText(TextWriter writer)
{
var temp = SaveStateBinary();
/*var temp = SaveStateBinary();
temp.SaveAsHexFast(writer);
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)
{
string hex = reader.ReadLine();
/*string hex = reader.ReadLine();
byte[] state = new byte[hex.Length / 2];
state.ReadFromHexFast(hex);
LoadStateBinary(new BinaryReader(new MemoryStream(state)));
reader.ReadLine(); // Frame #
var profile = reader.ReadLine().Split(' ')[1];
ValidateLoadstateProfile(profile);
ValidateLoadstateProfile(profile);*/
}
public void SaveStateBinary(BinaryWriter writer)
{
writer.Write(DeterministicEmulation ? _savestatebuff : CoreSaveState());
/*writer.Write(DeterministicEmulation ? _savestatebuff : CoreSaveState());
// other variables
writer.Write(IsLagFrame);
@ -39,12 +39,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
writer.Write(Frame);
writer.Write(CurrentProfile);
writer.Flush();
writer.Flush();*/
}
public void LoadStateBinary(BinaryReader reader)
{
int size = Api.QUERY_serialize_size();
/*int size = Api.QUERY_serialize_size();
byte[] buf = reader.ReadBytes(size);
CoreLoadState(buf);
@ -78,7 +78,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
LagCount = reader.ReadInt32();
Frame = reader.ReadInt32();
var profile = reader.ReadString();
ValidateLoadstateProfile(profile);
ValidateLoadstateProfile(profile);*/
}
public byte[] SaveStateBinary()
@ -90,34 +90,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
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)
{
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.");
}
}
// most recent internal savestate, for deterministic mode ONLY
private byte[] _savestatebuff;
}
}

View File

@ -58,7 +58,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
_settings = (SnesSettings)settings ?? new SnesSettings();
_syncSettings = (SnesSyncSettings)syncSettings ?? new SnesSyncSettings();
Api = new LibsnesApi(GetDllPath())
// TODO: pass profile here
Api = new LibsnesApi(CoreComm.CoreFileProvider.DllPath())
{
ReadHook = ReadHook,
ExecHook = ExecHook,
@ -169,17 +170,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
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")
{
ser.Register<ITraceable>(_tracer);
@ -198,7 +188,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
private LoadParams _currLoadParams;
private SpeexResampler _resampler;
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;
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; }
@ -374,20 +363,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
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)
{
MemoryCallbacks.CallReads(addr);

View File

@ -2,9 +2,13 @@ section .text
global 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
co_swap:
__imp_co_swap:
mov [rdx],rsp
mov rsp,[rcx]
pop rax

View File

@ -7,6 +7,7 @@
#define LIBSNES_IMPORT
#include "snes/snes.hpp"
#include "libsnes.hpp"
#include <emulibc.h>
#include <libco.h>
@ -136,6 +137,8 @@ struct CommStruct
//the SIG or BRK that the core is halted in
eMessage reason;
int32 padding1;
//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..
//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;
SNES::Input::Device inports[2];
int32 padding2;
//always used in pairs
void* buf[3];
int32 buf_size[3];
int32 padding3;
int64 cdl_ptr[4];
int32 cdl_size[4];
@ -161,6 +168,8 @@ struct CommStruct
uint32 region;
uint32 mapper;
int32 padding4;
//===========================================================
//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?)
//if(!running) return NULL;
auto ret = alloc_plain(amt);
comm.str = (char*)memtype;
comm.size = amt;
comm.ptr = ret;
BREAK(eMessage_SIG_allocSharedMemory);
@ -573,6 +585,29 @@ void new_emuthread()
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));
//make a coroutine thread to run the emulation in. we'll switch back to this cothread when communicating with the frontend