1216 lines
34 KiB
C#
1216 lines
34 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.IO.Pipes;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using System.IO.MemoryMappedFiles;
|
|
|
|
namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
|
{
|
|
|
|
/// <summary>
|
|
/// a ring buffer suitable for IPC. It uses a spinlock to control access, so overhead can be kept to a minimum.
|
|
/// you'll probably need to use this in pairs, so it will occupy two threads and degrade entirely if there is less than one processor.
|
|
/// </summary>
|
|
unsafe class IPCRingBuffer : IDisposable
|
|
{
|
|
MemoryMappedFile mmf;
|
|
MemoryMappedViewAccessor mmva;
|
|
|
|
byte* mmvaPtr;
|
|
volatile byte* begin;
|
|
volatile int* head, tail;
|
|
int bufsize;
|
|
|
|
public string Id;
|
|
public bool Owner;
|
|
|
|
/// <summary>
|
|
/// note that a few bytes of the size will be used for a management area
|
|
/// </summary>
|
|
/// <param name="size"></param>
|
|
public void Allocate(int size)
|
|
{
|
|
Owner = true;
|
|
Id = SuperGloballyUniqueID.Next();
|
|
mmf = MemoryMappedFile.CreateNew(Id, size);
|
|
Setup(size);
|
|
}
|
|
|
|
public void Open(string id)
|
|
{
|
|
Id = id;
|
|
mmf = MemoryMappedFile.OpenExisting(id);
|
|
Setup(-1);
|
|
}
|
|
|
|
void Setup(int size)
|
|
{
|
|
bool init = size != -1;
|
|
|
|
mmva = mmf.CreateViewAccessor();
|
|
byte* tempPtr = null;
|
|
mmva.SafeMemoryMappedViewHandle.AcquirePointer(ref tempPtr);
|
|
mmvaPtr = tempPtr;
|
|
|
|
//setup management area
|
|
head = (int*)mmvaPtr;
|
|
tail = (int*)mmvaPtr + 1;
|
|
int* bufsizeptr = (int*)mmvaPtr + 2;
|
|
begin = mmvaPtr + 12;
|
|
|
|
if (init)
|
|
*bufsizeptr = bufsize = size - 12;
|
|
else bufsize = *bufsizeptr;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (mmf == null) return;
|
|
mmva.Dispose();
|
|
mmf.Dispose();
|
|
mmf = null;
|
|
}
|
|
|
|
void WaitForWriteCapacity(int amt)
|
|
{
|
|
for (; ; )
|
|
{
|
|
//dont return when available == amt because then we would consume the buffer and be unable to distinguish between full and empty
|
|
if (Available > amt)
|
|
return;
|
|
//this is a greedy spinlock.
|
|
}
|
|
}
|
|
|
|
public int Available
|
|
{
|
|
get
|
|
{
|
|
return bufsize - Size;
|
|
}
|
|
}
|
|
|
|
|
|
public int Size
|
|
{
|
|
get
|
|
{
|
|
int h = *head;
|
|
int t = *tail;
|
|
int size = h - t;
|
|
if (size < 0) size += bufsize;
|
|
else if (size >= bufsize)
|
|
{
|
|
//shouldnt be possible for size to be anything but bufsize here, but just in case...
|
|
if (size > bufsize)
|
|
throw new InvalidOperationException("Critical error code pickpocket panda! This MUST be reported to the developers!");
|
|
size = 0;
|
|
}
|
|
return size;
|
|
}
|
|
}
|
|
|
|
int WaitForSomethingToRead()
|
|
{
|
|
for (; ; )
|
|
{
|
|
int available = Size;
|
|
if (available > 0)
|
|
return available;
|
|
//this is a greedy spinlock.
|
|
}
|
|
}
|
|
|
|
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
|
|
static unsafe extern void* CopyMemory(void* dest, void* src, ulong count);
|
|
|
|
public void Write(IntPtr ptr, int amt)
|
|
{
|
|
byte* bptr = (byte*)ptr;
|
|
int ofs = 0;
|
|
while (amt > 0)
|
|
{
|
|
int todo = amt;
|
|
|
|
//make sure we don't write a big chunk beyond the end of the buffer
|
|
int remain = bufsize - *head;
|
|
if (todo > remain) todo = remain;
|
|
|
|
//dont request the entire buffer. we would never get that much available, because we never completely fill up the buffer
|
|
if (todo > bufsize - 1) todo = bufsize - 1;
|
|
|
|
//a super efficient approach would chunk this several times maybe instead of waiting for the buffer to be emptied before writing again. but who cares
|
|
WaitForWriteCapacity(todo);
|
|
|
|
//messages are likely to be small. we should probably just loop to copy in here. but for now..
|
|
CopyMemory(begin + *head, bptr + ofs, (ulong)todo);
|
|
|
|
amt -= todo;
|
|
ofs += todo;
|
|
*head += todo;
|
|
if (*head >= bufsize) *head -= bufsize;
|
|
}
|
|
}
|
|
|
|
public void Read(IntPtr ptr, int amt)
|
|
{
|
|
byte* bptr = (byte*)ptr;
|
|
int ofs = 0;
|
|
while (amt > 0)
|
|
{
|
|
int available = WaitForSomethingToRead();
|
|
int todo = amt;
|
|
if (todo > available) todo = available;
|
|
|
|
//make sure we don't read a big chunk beyond the end of the buffer
|
|
int remain = bufsize - *tail;
|
|
if (todo > remain) todo = remain;
|
|
|
|
//messages are likely to be small. we should probably just loop to copy in here. but for now..
|
|
CopyMemory(bptr + ofs, begin + *tail, (ulong)todo);
|
|
|
|
amt -= todo;
|
|
ofs += todo;
|
|
*tail += todo;
|
|
if (*tail >= bufsize) *tail -= bufsize;
|
|
}
|
|
}
|
|
|
|
public class Tester
|
|
{
|
|
Queue<byte> shazam = new Queue<byte>();
|
|
string bufid;
|
|
|
|
unsafe void a()
|
|
{
|
|
var buf = new IPCRingBuffer();
|
|
buf.Allocate(1024);
|
|
bufid = buf.Id;
|
|
|
|
int ctr = 0;
|
|
for (; ; )
|
|
{
|
|
Random r = new Random(ctr);
|
|
ctr++;
|
|
Console.WriteLine("Writing: {0}", ctr);
|
|
|
|
byte[] temp = new byte[r.Next(2048) + 1];
|
|
r.NextBytes(temp);
|
|
for (int i = 0; i < temp.Length; i++)
|
|
lock (shazam) shazam.Enqueue(temp[i]);
|
|
fixed (byte* tempptr = &temp[0])
|
|
buf.Write((IntPtr)tempptr, temp.Length);
|
|
//Console.WriteLine("wrote {0}; ringbufsize={1}", temp.Length, buf.Size);
|
|
}
|
|
}
|
|
|
|
unsafe void b()
|
|
{
|
|
var buf = new IPCRingBuffer();
|
|
buf.Open(bufid);
|
|
|
|
int ctr = 0;
|
|
for (; ; )
|
|
{
|
|
Random r = new Random(ctr + 1000);
|
|
ctr++;
|
|
Console.WriteLine("Reading : {0}", ctr);
|
|
|
|
int tryRead = r.Next(2048) + 1;
|
|
byte[] temp = new byte[tryRead];
|
|
fixed (byte* tempptr = &temp[0])
|
|
buf.Read((IntPtr)tempptr, tryRead);
|
|
//Console.WriteLine("read {0}; ringbufsize={1}", temp.Length, buf.Size);
|
|
for (int i = 0; i < temp.Length; i++)
|
|
{
|
|
byte b;
|
|
lock (shazam) b = shazam.Dequeue();
|
|
Debug.Assert(b == temp[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Test()
|
|
{
|
|
var ta = new System.Threading.Thread(a);
|
|
var tb = new System.Threading.Thread(b);
|
|
ta.Start();
|
|
while (bufid == null) { }
|
|
tb.Start();
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe class IPCRingBufferStream : Stream
|
|
{
|
|
public IPCRingBufferStream(IPCRingBuffer buf)
|
|
{
|
|
this.buf = buf;
|
|
}
|
|
IPCRingBuffer buf;
|
|
public override bool CanRead { get { return true; } }
|
|
public override bool CanSeek { get { return false; } }
|
|
public override bool CanWrite { get { return true; } }
|
|
public override void Flush() { }
|
|
public override long Length { get { throw new NotImplementedException(); } }
|
|
public override long Position { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
|
|
public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); }
|
|
public override void SetLength(long value) { throw new NotImplementedException(); }
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
{
|
|
if (buffer.Length < offset + count) throw new IndexOutOfRangeException();
|
|
if(buffer.Length != 0)
|
|
fixed (byte* pbuffer = &buffer[offset])
|
|
buf.Read((IntPtr)pbuffer, count);
|
|
return count;
|
|
}
|
|
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
{
|
|
if (buffer.Length < offset + count) throw new IndexOutOfRangeException();
|
|
if(buffer.Length != 0)
|
|
fixed (byte* pbuffer = &buffer[offset])
|
|
buf.Write((IntPtr)pbuffer, count);
|
|
}
|
|
}
|
|
|
|
class SuperGloballyUniqueID
|
|
{
|
|
public static string Next()
|
|
{
|
|
int myctr;
|
|
lock (typeof(SuperGloballyUniqueID))
|
|
myctr = ctr++;
|
|
return staticPart + "-" + myctr;
|
|
}
|
|
|
|
static SuperGloballyUniqueID()
|
|
{
|
|
staticPart = "bizhawk-" + System.Diagnostics.Process.GetCurrentProcess().Id.ToString() + "-" + Guid.NewGuid().ToString();
|
|
}
|
|
|
|
static int ctr;
|
|
static string staticPart;
|
|
}
|
|
|
|
public unsafe class LibsnesApi : IDisposable
|
|
{
|
|
//this wouldve been the ideal situation to learn protocol buffers, but since the number of messages here is so limited, it took less time to roll it by hand.
|
|
//todo - could optimize a lot of the apis once we decide to commit to this. will we? then we wont be able to debug bsnes as well
|
|
// well, we could refactor it a lot and let the debuggable static dll version be the one that does annoying workarounds
|
|
//todo - more intelligent use of buffers to avoid so many copies (especially framebuffer from bsnes? supply framebuffer to-be-used to libsnes? same for audiobuffer)
|
|
//todo - refactor to use a smarter set of pipe reader and pipe writer classes
|
|
//todo - combine messages / tracecallbacks into one system with a channel number enum additionally
|
|
//todo - consider refactoring bsnes to allocate memory blocks through the interface, and set ours up to allocate from a large arena of shared memory.
|
|
// this is a lot of work, but it will be some decent speedups. who wouldve ever thought to make an emulator this way? I will, from now on...
|
|
//todo - use a reader/writer ring buffer for communication instead of pipe
|
|
//todo - when exe wrapper is fully baked, put it into mingw so we can just have libsneshawk.exe without a separate dll. it hardly needs any debugging presently, it should be easy to maintain.
|
|
|
|
//space optimizations to deploy later (only if people complain about so many files)
|
|
//todo - put executables in zipfiles and search for them there; dearchive to a .cache folder. check timestamps to know when to freshen. this is weird.....
|
|
|
|
//speedups to deploy later:
|
|
//todo - convey rom data faster than pipe blob (use shared memory) (WARNING: right now our general purpose shared memory is only 1MB. maybe wait until ring buffer IPC)
|
|
//todo - collapse input messages to one IPC operation. right now theresl ike 30 of them
|
|
//todo - collect all memory block names whenever a memory block is alloc/dealloced. that way we avoid the overhead when using them for gui stuff (gfx debugger, hex editor)
|
|
|
|
string InstanceName;
|
|
Process process;
|
|
NamedPipeServerStream pipe;
|
|
BinaryWriter bwPipe;
|
|
BinaryReader brPipe;
|
|
MemoryMappedFile mmf;
|
|
MemoryMappedViewAccessor mmva;
|
|
byte* mmvaPtr;
|
|
IPCRingBuffer rbuf, wbuf;
|
|
IPCRingBufferStream rbufstr, wbufstr;
|
|
SwitcherStream rstream, wstream;
|
|
bool bufio;
|
|
|
|
public enum eMessage : int
|
|
{
|
|
eMessage_Complete,
|
|
|
|
eMessage_snes_library_id,
|
|
eMessage_snes_library_revision_major,
|
|
eMessage_snes_library_revision_minor,
|
|
|
|
eMessage_snes_init,
|
|
eMessage_snes_power,
|
|
eMessage_snes_reset,
|
|
eMessage_snes_run,
|
|
eMessage_snes_term,
|
|
eMessage_snes_unload_cartridge,
|
|
|
|
//snes_set_cartridge_basename, //not used
|
|
|
|
eMessage_snes_load_cartridge_normal,
|
|
eMessage_snes_load_cartridge_super_game_boy,
|
|
|
|
//incoming from bsnes
|
|
eMessage_snes_cb_video_refresh,
|
|
eMessage_snes_cb_input_poll,
|
|
eMessage_snes_cb_input_state,
|
|
eMessage_snes_cb_input_notify,
|
|
eMessage_snes_cb_audio_sample,
|
|
eMessage_snes_cb_scanlineStart,
|
|
eMessage_snes_cb_path_request,
|
|
eMessage_snes_cb_trace_callback,
|
|
|
|
eMessage_snes_get_region,
|
|
|
|
eMessage_snes_get_memory_size,
|
|
eMessage_snes_get_memory_data,
|
|
eMessage_peek,
|
|
eMessage_poke,
|
|
|
|
eMessage_snes_serialize_size,
|
|
|
|
eMessage_snes_serialize,
|
|
eMessage_snes_unserialize,
|
|
|
|
eMessage_snes_poll_message,
|
|
eMessage_snes_dequeue_message,
|
|
|
|
eMessage_snes_set_color_lut,
|
|
|
|
eMessage_snes_enable_trace,
|
|
eMessage_snes_enable_scanline,
|
|
eMessage_snes_enable_audio,
|
|
eMessage_snes_set_layer_enable,
|
|
eMessage_snes_set_backdropColor,
|
|
eMessage_snes_peek_logical_register,
|
|
|
|
eMessage_snes_allocSharedMemory,
|
|
eMessage_snes_freeSharedMemory,
|
|
eMessage_GetMemoryIdName,
|
|
|
|
eMessage_SetBuffer,
|
|
eMessage_BeginBufferIO,
|
|
eMessage_EndBufferIO,
|
|
|
|
eMessage_set_state_hook_exec,
|
|
eMessage_set_state_hook_read,
|
|
eMessage_set_state_hook_write,
|
|
eMessage_set_state_hook_nmi,
|
|
eMessage_set_state_hook_irq,
|
|
eMessage_snes_cb_hook_exec,
|
|
eMessage_snes_cb_hook_read,
|
|
eMessage_snes_cb_hook_write,
|
|
eMessage_snes_cb_hook_nmi,
|
|
eMessage_snes_cb_hook_irq,
|
|
};
|
|
|
|
static bool DryRun(string exePath)
|
|
{
|
|
ProcessStartInfo oInfo = new ProcessStartInfo(exePath, "Bongizong");
|
|
oInfo.WorkingDirectory = Path.GetDirectoryName(exePath);
|
|
oInfo.UseShellExecute = false;
|
|
oInfo.CreateNoWindow = true;
|
|
oInfo.RedirectStandardOutput = true;
|
|
oInfo.RedirectStandardError = true;
|
|
|
|
Process proc = System.Diagnostics.Process.Start(oInfo);
|
|
string result = proc.StandardError.ReadToEnd();
|
|
proc.WaitForExit();
|
|
|
|
//yongou chonganong nongo tong rongeadong
|
|
//pongigong chong hongi nonge songe
|
|
if (result == "Honga Wongkong" && proc.ExitCode == 0x16817)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static HashSet<string> okExes = new HashSet<string>();
|
|
public LibsnesApi(string exePath)
|
|
{
|
|
//make sure we've checked this exe for OKness.. the dry run should keep us from freezing up or crashing weirdly if the external process isnt correct
|
|
if (!okExes.Contains(exePath))
|
|
{
|
|
bool ok = DryRun(exePath);
|
|
if (!ok)
|
|
throw new InvalidOperationException(string.Format("Couldn't launch {0} to run SNES core. Not sure why this would have happened. Try redownloading BizHawk first.", Path.GetFileName(exePath)));
|
|
okExes.Add(exePath);
|
|
}
|
|
|
|
InstanceName = "libsneshawk_" + Guid.NewGuid().ToString();
|
|
|
|
//use this to get a debug console with libsnes output
|
|
//InstanceName = "console-" + InstanceName;
|
|
|
|
var pipeName = InstanceName;
|
|
|
|
mmf = MemoryMappedFile.CreateNew(pipeName, 1024 * 1024);
|
|
mmva = mmf.CreateViewAccessor();
|
|
mmva.SafeMemoryMappedViewHandle.AcquirePointer(ref mmvaPtr);
|
|
|
|
pipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.None, 1024 * 1024, 1024);
|
|
|
|
process = new Process();
|
|
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(exePath);
|
|
process.StartInfo.FileName = exePath;
|
|
process.StartInfo.Arguments = pipeName;
|
|
process.StartInfo.ErrorDialog = true;
|
|
process.Start();
|
|
|
|
//TODO - start a thread to wait for process to exit and gracefully handle errors? how about the pipe?
|
|
|
|
pipe.WaitForConnection();
|
|
|
|
rbuf = new IPCRingBuffer();
|
|
wbuf = new IPCRingBuffer();
|
|
rbuf.Allocate(1024);
|
|
wbuf.Allocate(1024);
|
|
rbufstr = new IPCRingBufferStream(rbuf);
|
|
wbufstr = new IPCRingBufferStream(wbuf);
|
|
|
|
rstream = new SwitcherStream();
|
|
wstream = new SwitcherStream();
|
|
|
|
rstream.SetCurrStream(pipe);
|
|
wstream.SetCurrStream(pipe);
|
|
|
|
brPipe = new BinaryReader(rstream);
|
|
bwPipe = new BinaryWriter(wstream);
|
|
|
|
WritePipeMessage(eMessage.eMessage_SetBuffer);
|
|
bwPipe.Write(1);
|
|
WritePipeString(rbuf.Id);
|
|
WritePipeMessage(eMessage.eMessage_SetBuffer);
|
|
bwPipe.Write(0);
|
|
WritePipeString(wbuf.Id);
|
|
bwPipe.Flush();
|
|
}
|
|
|
|
class SwitcherStream : Stream
|
|
{
|
|
//switchstream method? flush old stream?
|
|
Stream CurrStream = null;
|
|
|
|
public void SetCurrStream(Stream str) { CurrStream = str; }
|
|
|
|
public SwitcherStream()
|
|
{
|
|
}
|
|
|
|
public override bool CanRead { get { return CurrStream.CanRead; } }
|
|
public override bool CanSeek { get { return CurrStream.CanSeek; } }
|
|
public override bool CanWrite { get { return CurrStream.CanWrite; } }
|
|
public override void Flush()
|
|
{
|
|
CurrStream.Flush();
|
|
}
|
|
|
|
public override long Length { get { return CurrStream.Length; } }
|
|
|
|
public override long Position
|
|
{
|
|
get
|
|
{
|
|
return CurrStream.Position;
|
|
}
|
|
set
|
|
{
|
|
CurrStream.Position = Position;
|
|
}
|
|
}
|
|
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
{
|
|
return CurrStream.Read(buffer, offset, count);
|
|
}
|
|
|
|
public override long Seek(long offset, SeekOrigin origin)
|
|
{
|
|
return CurrStream.Seek(offset, origin);
|
|
}
|
|
|
|
public override void SetLength(long value)
|
|
{
|
|
CurrStream.SetLength(value);
|
|
}
|
|
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
{
|
|
CurrStream.Write(buffer, offset, count);
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
process.Kill();
|
|
process.Dispose();
|
|
process = null;
|
|
pipe.Dispose();
|
|
mmva.Dispose();
|
|
mmf.Dispose();
|
|
rbuf.Dispose();
|
|
wbuf.Dispose();
|
|
foreach (var smb in DeallocatedMemoryBlocks.Values)
|
|
smb.Dispose();
|
|
DeallocatedMemoryBlocks.Clear();
|
|
}
|
|
|
|
public void BeginBufferIO()
|
|
{
|
|
bufio = true;
|
|
WritePipeMessage(eMessage.eMessage_BeginBufferIO);
|
|
rstream.SetCurrStream(rbufstr);
|
|
wstream.SetCurrStream(wbufstr);
|
|
}
|
|
|
|
public void EndBufferIO()
|
|
{
|
|
if(!bufio) return;
|
|
bufio = false;
|
|
WritePipeMessage(eMessage.eMessage_EndBufferIO);
|
|
rstream.SetCurrStream(pipe);
|
|
wstream.SetCurrStream(pipe);
|
|
}
|
|
|
|
void WritePipeString(string str)
|
|
{
|
|
WritePipeBlob(System.Text.Encoding.ASCII.GetBytes(str));
|
|
}
|
|
|
|
byte[] ReadPipeBlob()
|
|
{
|
|
int len = brPipe.ReadInt32();
|
|
var ret = new byte[len];
|
|
brPipe.Read(ret, 0, len);
|
|
return ret;
|
|
}
|
|
|
|
void WritePipeBlob(byte[] blob)
|
|
{
|
|
bwPipe.Write(blob.Length);
|
|
bwPipe.Write(blob);
|
|
bwPipe.Flush();
|
|
}
|
|
|
|
public int MessageCounter;
|
|
|
|
void WritePipeMessage(eMessage msg)
|
|
{
|
|
if(!bufio) MessageCounter++;
|
|
bwPipe.Write((int)msg);
|
|
bwPipe.Flush();
|
|
}
|
|
|
|
eMessage ReadPipeMessage()
|
|
{
|
|
return (eMessage)brPipe.ReadInt32();
|
|
}
|
|
|
|
string ReadPipeString()
|
|
{
|
|
int len = brPipe.ReadInt32();
|
|
var bytes = brPipe.ReadBytes(len);
|
|
return System.Text.ASCIIEncoding.ASCII.GetString(bytes);
|
|
}
|
|
|
|
public string snes_library_id()
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_library_id);
|
|
return ReadPipeString();
|
|
}
|
|
|
|
|
|
public uint snes_library_revision_major()
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_library_revision_major);
|
|
return brPipe.ReadUInt32();
|
|
}
|
|
|
|
public uint snes_library_revision_minor()
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_library_revision_minor);
|
|
return brPipe.ReadUInt32();
|
|
}
|
|
|
|
public void snes_init()
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_init);
|
|
WaitForCompletion();
|
|
}
|
|
public void snes_power() { WritePipeMessage(eMessage.eMessage_snes_power); }
|
|
public void snes_reset() { WritePipeMessage(eMessage.eMessage_snes_reset); }
|
|
public void snes_run()
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_run);
|
|
WaitForCompletion();
|
|
}
|
|
public void snes_term() { WritePipeMessage(eMessage.eMessage_snes_term); }
|
|
public void snes_unload_cartridge() { WritePipeMessage(eMessage.eMessage_snes_unload_cartridge); }
|
|
|
|
public void snes_set_state_hook_exec(bool state)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_set_state_hook_exec);
|
|
bwPipe.Write(state);
|
|
}
|
|
|
|
public void snes_set_state_hook_read(bool state)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_set_state_hook_read);
|
|
bwPipe.Write(state);
|
|
}
|
|
|
|
public void snes_set_state_hook_write(bool state)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_set_state_hook_write);
|
|
bwPipe.Write(state);
|
|
}
|
|
|
|
public bool snes_load_cartridge_super_game_boy(string rom_xml, byte[] rom_data, uint rom_size, string dmg_xml, byte[] dmg_data, uint dmg_size)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_load_cartridge_super_game_boy);
|
|
WritePipeString(rom_xml ?? "");
|
|
WritePipeBlob(rom_data);
|
|
WritePipeString(rom_xml ?? "");
|
|
WritePipeBlob(dmg_data);
|
|
//not a very obvious order.. because we do tons of work immediately after the last param goes down and need to answer messages
|
|
WaitForCompletion();
|
|
bool ret = brPipe.ReadBoolean();
|
|
return ret;
|
|
}
|
|
|
|
public bool snes_load_cartridge_normal(byte[] rom_xml, byte[] rom_data)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_load_cartridge_normal);
|
|
WritePipeBlob(rom_xml ?? new byte[0]);
|
|
WritePipeBlob(rom_data ?? new byte[0]);
|
|
//not a very obvious order.. because we do tons of work immediately after the last param goes down and need to answer messages
|
|
WaitForCompletion();
|
|
bool ret = brPipe.ReadBoolean();
|
|
return ret;
|
|
}
|
|
|
|
public SNES_REGION snes_get_region()
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_get_region);
|
|
return (SNES_REGION)brPipe.ReadByte();
|
|
}
|
|
|
|
public int snes_get_memory_size(SNES_MEMORY id)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_get_memory_size);
|
|
bwPipe.Write((int)id);
|
|
bwPipe.Flush();
|
|
return brPipe.ReadInt32();
|
|
}
|
|
|
|
string MemoryNameForId(SNES_MEMORY id)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_GetMemoryIdName);
|
|
bwPipe.Write((uint)id);
|
|
bwPipe.Flush();
|
|
return ReadPipeString();
|
|
}
|
|
|
|
public byte* snes_get_memory_data(SNES_MEMORY id)
|
|
{
|
|
string name = MemoryNameForId(id);
|
|
var smb = SharedMemoryBlocks[name];
|
|
return (byte*)smb.Ptr;
|
|
}
|
|
|
|
public byte peek(SNES_MEMORY id, uint addr)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_peek);
|
|
bwPipe.Write((uint)id);
|
|
bwPipe.Write(addr);
|
|
bwPipe.Flush();
|
|
return brPipe.ReadByte();
|
|
}
|
|
public void poke(SNES_MEMORY id, uint addr, byte val)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_poke);
|
|
bwPipe.Write((uint)id);
|
|
bwPipe.Write(addr);
|
|
bwPipe.Write(val);
|
|
bwPipe.Flush();
|
|
}
|
|
|
|
public int snes_serialize_size()
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_serialize_size);
|
|
return brPipe.ReadInt32();
|
|
}
|
|
|
|
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
|
|
public static unsafe extern void* CopyMemory(void* dest, void* src, ulong count);
|
|
|
|
public bool snes_serialize(IntPtr data, int size)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_serialize);
|
|
bwPipe.Write(size);
|
|
bwPipe.Write(0); //mapped memory location to serialize to
|
|
bwPipe.Flush();
|
|
WaitForCompletion(); //serialize/unserialize can cause traces to get called (because serialize can cause execution?)
|
|
bool ret = brPipe.ReadBoolean();
|
|
if (ret)
|
|
{
|
|
CopyMemory(data.ToPointer(), mmvaPtr, (ulong)size);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
public bool snes_unserialize(IntPtr data, int size)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_unserialize);
|
|
CopyMemory(mmvaPtr, data.ToPointer(), (ulong)size);
|
|
bwPipe.Write(size);
|
|
bwPipe.Write(0); //mapped memory location to serialize from
|
|
bwPipe.Flush();
|
|
WaitForCompletion(); //serialize/unserialize can cause traces to get called (because serialize can cause execution?)
|
|
bool ret = brPipe.ReadBoolean();
|
|
return ret;
|
|
}
|
|
|
|
int snes_poll_message()
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_poll_message);
|
|
return brPipe.ReadInt32();
|
|
}
|
|
|
|
public bool HasMessage { get { return snes_poll_message() != -1; } }
|
|
|
|
|
|
public string DequeueMessage()
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_dequeue_message);
|
|
return ReadPipeString();
|
|
}
|
|
|
|
public void snes_set_color_lut(IntPtr colors)
|
|
{
|
|
int len = 4 * 16 * 32768;
|
|
byte[] buf = new byte[len];
|
|
Marshal.Copy(colors, buf, 0, len);
|
|
|
|
WritePipeMessage(eMessage.eMessage_snes_set_color_lut);
|
|
WritePipeBlob(buf);
|
|
}
|
|
|
|
public void snes_set_layer_enable(int layer, int priority, bool enable)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_set_layer_enable);
|
|
bwPipe.Write(layer);
|
|
bwPipe.Write(priority);
|
|
bwPipe.Write(enable);
|
|
bwPipe.Flush();
|
|
}
|
|
|
|
public void snes_set_backdropColor(int backdropColor)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_set_backdropColor);
|
|
bwPipe.Write(backdropColor);
|
|
bwPipe.Flush();
|
|
}
|
|
|
|
public int snes_peek_logical_register(SNES_REG reg)
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_snes_peek_logical_register);
|
|
bwPipe.Write((int)reg);
|
|
bwPipe.Flush();
|
|
return brPipe.ReadInt32();
|
|
}
|
|
|
|
void WaitForCompletion()
|
|
{
|
|
for (; ; )
|
|
{
|
|
var msg = ReadPipeMessage();
|
|
if (!bufio) MessageCounter++;
|
|
//Console.WriteLine(msg);
|
|
switch (msg)
|
|
{
|
|
case eMessage.eMessage_Complete:
|
|
return;
|
|
|
|
case eMessage.eMessage_snes_cb_video_refresh:
|
|
{
|
|
int width = brPipe.ReadInt32();
|
|
int height = brPipe.ReadInt32();
|
|
bwPipe.Write(0); //offset in mapped memory buffer
|
|
bwPipe.Flush();
|
|
brPipe.ReadBoolean(); //dummy synchronization
|
|
if (video_refresh != null)
|
|
{
|
|
video_refresh((int*)mmvaPtr, width, height);
|
|
}
|
|
break;
|
|
}
|
|
case eMessage.eMessage_snes_cb_input_poll:
|
|
break;
|
|
case eMessage.eMessage_snes_cb_input_state:
|
|
{
|
|
int port = brPipe.ReadInt32();
|
|
int device = brPipe.ReadInt32();
|
|
int index = brPipe.ReadInt32();
|
|
int id = brPipe.ReadInt32();
|
|
ushort ret = 0;
|
|
if (input_state != null)
|
|
ret = input_state(port, device, index, id);
|
|
bwPipe.Write(ret);
|
|
bwPipe.Flush();
|
|
break;
|
|
}
|
|
case eMessage.eMessage_snes_cb_input_notify:
|
|
{
|
|
int index = brPipe.ReadInt32();
|
|
if (input_notify != null)
|
|
input_notify(index);
|
|
break;
|
|
}
|
|
case eMessage.eMessage_snes_cb_audio_sample:
|
|
{
|
|
int nsamples = brPipe.ReadInt32();
|
|
bwPipe.Write(0); //location to store audio buffer in
|
|
bwPipe.Flush();
|
|
brPipe.ReadInt32(); //dummy synchronization
|
|
|
|
if (audio_sample != null)
|
|
{
|
|
ushort* audiobuffer = ((ushort*)mmvaPtr);
|
|
for (int i = 0; i < nsamples; )
|
|
{
|
|
ushort left = audiobuffer[i++];
|
|
ushort right = audiobuffer[i++];
|
|
audio_sample(left, right);
|
|
}
|
|
}
|
|
|
|
bwPipe.Write(0); //dummy synchronization
|
|
bwPipe.Flush();
|
|
brPipe.ReadInt32(); //dummy synchronization
|
|
break;
|
|
}
|
|
case eMessage.eMessage_snes_cb_scanlineStart:
|
|
{
|
|
int line = brPipe.ReadInt32();
|
|
if (scanlineStart != null)
|
|
scanlineStart(line);
|
|
|
|
//we have to notify the unmanaged process that we're done peeking thruogh its memory and whatnot so it can proceed with emulation
|
|
WritePipeMessage(eMessage.eMessage_Complete);
|
|
break;
|
|
}
|
|
case eMessage.eMessage_snes_cb_path_request:
|
|
{
|
|
int slot = brPipe.ReadInt32();
|
|
string hint = ReadPipeString();
|
|
string ret = hint;
|
|
if (pathRequest != null)
|
|
hint = pathRequest(slot, hint);
|
|
WritePipeString(hint);
|
|
break;
|
|
}
|
|
case eMessage.eMessage_snes_cb_trace_callback:
|
|
{
|
|
var trace = ReadPipeString();
|
|
if (traceCallback != null)
|
|
traceCallback(trace);
|
|
break;
|
|
}
|
|
case eMessage.eMessage_snes_allocSharedMemory:
|
|
{
|
|
var name = ReadPipeString();
|
|
var size = brPipe.ReadInt32();
|
|
|
|
if (SharedMemoryBlocks.ContainsKey(name))
|
|
{
|
|
throw new InvalidOperationException("Re-defined a shared memory block. Check bsnes init/shutdown code. Block name: " + name);
|
|
}
|
|
|
|
//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];
|
|
DeallocatedMemoryBlocks.Remove(name);
|
|
if (smb.Size != size)
|
|
{
|
|
smb.Dispose();
|
|
smb = null;
|
|
}
|
|
}
|
|
|
|
//allocate a new block if we have to
|
|
if(smb == null)
|
|
{
|
|
smb = new SharedMemoryBlock();
|
|
smb.Name = name;
|
|
smb.Size = size;
|
|
smb.BlockName = InstanceName + smb.Name;
|
|
smb.Allocate();
|
|
}
|
|
|
|
SharedMemoryBlocks[smb.Name] = smb;
|
|
WritePipeString(smb.BlockName);
|
|
break;
|
|
}
|
|
case eMessage.eMessage_snes_freeSharedMemory:
|
|
{
|
|
string name = ReadPipeString();
|
|
var smb = SharedMemoryBlocks[name];
|
|
DeallocatedMemoryBlocks[name] = smb;
|
|
SharedMemoryBlocks.Remove(name);
|
|
break;
|
|
}
|
|
|
|
case eMessage.eMessage_snes_cb_hook_exec:
|
|
{
|
|
var addr = brPipe.ReadInt32();
|
|
ExecHook((uint)addr);
|
|
break;
|
|
}
|
|
case eMessage.eMessage_snes_cb_hook_read:
|
|
{
|
|
var addr = brPipe.ReadInt32();
|
|
ReadHook((uint)addr);
|
|
break;
|
|
}
|
|
case eMessage.eMessage_snes_cb_hook_write:
|
|
{
|
|
var addr = brPipe.ReadInt32();
|
|
var value = brPipe.ReadByte();
|
|
WriteHook((uint)addr,value);
|
|
break;
|
|
}
|
|
case eMessage.eMessage_snes_cb_hook_nmi:
|
|
break;
|
|
case eMessage.eMessage_snes_cb_hook_irq:
|
|
break;
|
|
}
|
|
}
|
|
} //WaitForCompletion()
|
|
|
|
public Action<uint> ReadHook, ExecHook;
|
|
public Action<uint, byte> WriteHook;
|
|
|
|
class SharedMemoryBlock : IDisposable
|
|
{
|
|
public string Name;
|
|
public string BlockName;
|
|
public int Size;
|
|
public MemoryMappedFile mmf;
|
|
public MemoryMappedViewAccessor mmva;
|
|
public byte* Ptr;
|
|
|
|
public void Allocate()
|
|
{
|
|
//we can't allocate 0 bytes here.. so just allocate 1 byte here if 0 was requested. it should be OK, and we dont have to handle cases where blocks havent been allocated
|
|
int sizeToAlloc = Size;
|
|
if (sizeToAlloc == 0) sizeToAlloc = 1;
|
|
mmf = MemoryMappedFile.CreateNew(BlockName, sizeToAlloc);
|
|
mmva = mmf.CreateViewAccessor();
|
|
mmva.SafeMemoryMappedViewHandle.AcquirePointer(ref Ptr);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (mmf == null) return;
|
|
mmva.Dispose();
|
|
mmf.Dispose();
|
|
mmf = null;
|
|
}
|
|
}
|
|
|
|
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;
|
|
snes_input_notify_t input_notify;
|
|
snes_audio_sample_t audio_sample;
|
|
snes_scanlineStart_t scanlineStart;
|
|
snes_path_request_t pathRequest;
|
|
snes_trace_t traceCallback;
|
|
|
|
public void snes_set_video_refresh(snes_video_refresh_t video_refresh) { this.video_refresh = video_refresh; }
|
|
public void snes_set_input_poll(snes_input_poll_t input_poll) { this.input_poll = input_poll; }
|
|
public void snes_set_input_state(snes_input_state_t input_state) { this.input_state = input_state; }
|
|
public void snes_set_input_notify(snes_input_notify_t input_notify) { this.input_notify = input_notify; }
|
|
public void snes_set_audio_sample(snes_audio_sample_t audio_sample)
|
|
{
|
|
this.audio_sample = audio_sample;
|
|
WritePipeMessage(eMessage.eMessage_snes_enable_audio);
|
|
bwPipe.Write(audio_sample != null);
|
|
bwPipe.Flush();
|
|
}
|
|
public void snes_set_path_request(snes_path_request_t pathRequest) { this.pathRequest = pathRequest; }
|
|
public void snes_set_scanlineStart(snes_scanlineStart_t scanlineStart)
|
|
{
|
|
this.scanlineStart = scanlineStart;
|
|
WritePipeMessage(eMessage.eMessage_snes_enable_scanline);
|
|
bwPipe.Write(scanlineStart != null);
|
|
bwPipe.Flush();
|
|
}
|
|
public void snes_set_trace_callback(snes_trace_t callback)
|
|
{
|
|
this.traceCallback = callback;
|
|
WritePipeMessage(eMessage.eMessage_snes_enable_trace);
|
|
bwPipe.Write(callback != null);
|
|
bwPipe.Flush();
|
|
}
|
|
|
|
public delegate void snes_video_refresh_t(int* data, int width, int height);
|
|
public delegate void snes_input_poll_t();
|
|
public delegate ushort snes_input_state_t(int port, int device, int index, int id);
|
|
public delegate void snes_input_notify_t(int index);
|
|
public delegate void snes_audio_sample_t(ushort left, ushort right);
|
|
public delegate void snes_scanlineStart_t(int line);
|
|
public delegate string snes_path_request_t(int slot, string hint);
|
|
public delegate void snes_trace_t(string msg);
|
|
|
|
public enum SNES_REG : int
|
|
{
|
|
//$2105
|
|
BG_MODE = 0,
|
|
BG3_PRIORITY = 1,
|
|
BG1_TILESIZE = 2,
|
|
BG2_TILESIZE = 3,
|
|
BG3_TILESIZE = 4,
|
|
BG4_TILESIZE = 5,
|
|
//$2107
|
|
BG1_SCADDR = 10,
|
|
BG1_SCSIZE = 11,
|
|
//$2108
|
|
BG2_SCADDR = 12,
|
|
BG2_SCSIZE = 13,
|
|
//$2109
|
|
BG3_SCADDR = 14,
|
|
BG3_SCSIZE = 15,
|
|
//$210A
|
|
BG4_SCADDR = 16,
|
|
BG4_SCSIZE = 17,
|
|
//$210B
|
|
BG1_TDADDR = 20,
|
|
BG2_TDADDR = 21,
|
|
//$210C
|
|
BG3_TDADDR = 22,
|
|
BG4_TDADDR = 23,
|
|
//$2133 SETINI
|
|
SETINI_MODE7_EXTBG = 30,
|
|
SETINI_HIRES = 31,
|
|
SETINI_OVERSCAN = 32,
|
|
SETINI_OBJ_INTERLACE = 33,
|
|
SETINI_SCREEN_INTERLACE = 34,
|
|
//$2130 CGWSEL
|
|
CGWSEL_COLORMASK = 40,
|
|
CGWSEL_COLORSUBMASK = 41,
|
|
CGWSEL_ADDSUBMODE = 42,
|
|
CGWSEL_DIRECTCOLOR = 43,
|
|
//$2101 OBSEL
|
|
OBSEL_NAMEBASE = 50,
|
|
OBSEL_NAMESEL = 51,
|
|
OBSEL_SIZE = 52,
|
|
//$2131 CGADSUB
|
|
CGADSUB_MODE = 60,
|
|
CGADSUB_HALF = 61,
|
|
CGADSUB_BG4 = 62,
|
|
CGADSUB_BG3 = 63,
|
|
CGADSUB_BG2 = 64,
|
|
CGADSUB_BG1 = 65,
|
|
CGADSUB_OBJ = 66,
|
|
CGADSUB_BACKDROP = 67,
|
|
//$212C TM
|
|
TM_BG1 = 70,
|
|
TM_BG2 = 71,
|
|
TM_BG3 = 72,
|
|
TM_BG4 = 73,
|
|
TM_OBJ = 74,
|
|
//$212D TM
|
|
TS_BG1 = 80,
|
|
TS_BG2 = 81,
|
|
TS_BG3 = 82,
|
|
TS_BG4 = 83,
|
|
TS_OBJ = 84,
|
|
//Mode7 regs
|
|
M7SEL_REPEAT = 90,
|
|
M7SEL_HFLIP = 91,
|
|
M7SEL_VFLIP = 92,
|
|
M7A = 93,
|
|
M7B = 94,
|
|
M7C = 95,
|
|
M7D = 96,
|
|
M7X = 97,
|
|
M7Y = 98,
|
|
//BG scroll regs
|
|
BG1HOFS = 100,
|
|
BG1VOFS = 101,
|
|
BG2HOFS = 102,
|
|
BG2VOFS = 103,
|
|
BG3HOFS = 104,
|
|
BG3VOFS = 105,
|
|
BG4HOFS = 106,
|
|
BG4VOFS = 107,
|
|
M7HOFS = 108,
|
|
M7VOFS = 109,
|
|
}
|
|
|
|
public enum SNES_MEMORY : uint
|
|
{
|
|
CARTRIDGE_RAM = 0,
|
|
CARTRIDGE_RTC = 1,
|
|
BSX_RAM = 2,
|
|
BSX_PRAM = 3,
|
|
SUFAMI_TURBO_A_RAM = 4,
|
|
SUFAMI_TURBO_B_RAM = 5,
|
|
SGB_CARTRAM = 6,
|
|
SGB_RTC = 7,
|
|
SGB_WRAM = 8,
|
|
SGB_HRAM = 9,
|
|
|
|
WRAM = 100,
|
|
APURAM = 101,
|
|
VRAM = 102,
|
|
OAM = 103,
|
|
CGRAM = 104,
|
|
|
|
SYSBUS = 200,
|
|
LOGICAL_REGS = 201
|
|
}
|
|
|
|
public enum SNES_REGION : byte
|
|
{
|
|
NTSC = 0,
|
|
PAL = 1,
|
|
}
|
|
|
|
public enum SNES_DEVICE : uint
|
|
{
|
|
NONE = 0,
|
|
JOYPAD = 1,
|
|
MULTITAP = 2,
|
|
MOUSE = 3,
|
|
SUPER_SCOPE = 4,
|
|
JUSTIFIER = 5,
|
|
JUSTIFIERS = 6,
|
|
SERIAL_CABLE = 7
|
|
}
|
|
|
|
public enum SNES_DEVICE_ID : uint
|
|
{
|
|
JOYPAD_B = 0,
|
|
JOYPAD_Y = 1,
|
|
JOYPAD_SELECT = 2,
|
|
JOYPAD_START = 3,
|
|
JOYPAD_UP = 4,
|
|
JOYPAD_DOWN = 5,
|
|
JOYPAD_LEFT = 6,
|
|
JOYPAD_RIGHT = 7,
|
|
JOYPAD_A = 8,
|
|
JOYPAD_X = 9,
|
|
JOYPAD_L = 10,
|
|
JOYPAD_R = 11
|
|
}
|
|
}
|
|
|
|
}
|