272 lines
10 KiB
C#
272 lines
10 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;
|
|
|
|
using BizHawk.Common;
|
|
|
|
namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
|
{
|
|
public unsafe partial 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;
|
|
System.Threading.EventWaitHandle watchdogEvent;
|
|
NamedPipeServerStream pipe;
|
|
BinaryWriter bwPipe;
|
|
BinaryReader brPipe;
|
|
MemoryMappedFile mmf;
|
|
MemoryMappedViewAccessor mmva;
|
|
byte* mmvaPtr;
|
|
IPCRingBuffer rbuf, wbuf;
|
|
IPCRingBufferStream rbufstr, wbufstr;
|
|
SwitcherStream rstream, wstream;
|
|
bool bufio;
|
|
|
|
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
|
|
public static unsafe extern void* CopyMemory(void* dest, void* src, ulong count);
|
|
|
|
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();
|
|
|
|
#if DEBUG
|
|
//use this to get a debug console with libsnes output
|
|
InstanceName = "console-" + InstanceName;
|
|
#endif
|
|
|
|
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);
|
|
|
|
//slim chance this might be useful sometimes:
|
|
//http://stackoverflow.com/questions/2590334/creating-a-cross-process-eventwaithandle
|
|
//create an event for the child process to monitor with a watchdog, to make sure it terminates when the emuhawk process terminates.
|
|
//NOTE: this is alarming! for some reason .net releases this event when it gets finalized, instead of when i (dont) dispose it.
|
|
bool createdNew;
|
|
watchdogEvent = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.AutoReset, InstanceName + "-event", out createdNew);
|
|
|
|
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();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
watchdogEvent.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++;
|
|
//Console.WriteLine("write pipe message: " + msg);
|
|
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);
|
|
}
|
|
|
|
void WaitForCompletion()
|
|
{
|
|
for (; ; )
|
|
{
|
|
var msg = ReadPipeMessage();
|
|
if (!bufio) MessageCounter++;
|
|
//Console.WriteLine("read pipe message: " + msg);
|
|
|
|
if (msg == eMessage.eMessage_BRK_Complete)
|
|
return;
|
|
|
|
//this approach is slower than having one big case. but, its easier to manage. once the code is stable, someone could clean it up (probably creating a delegate table would be best)
|
|
if (Handle_SIG(msg)) continue;
|
|
if (Handle_BRK(msg)) continue;
|
|
}
|
|
}
|
|
|
|
public Action<uint> ReadHook, ExecHook;
|
|
public Action<uint, byte> WriteHook;
|
|
|
|
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 QUERY_set_video_refresh(snes_video_refresh_t video_refresh) { this.video_refresh = video_refresh; }
|
|
public void QUERY_set_input_poll(snes_input_poll_t input_poll) { this.input_poll = input_poll; }
|
|
public void QUERY_set_input_state(snes_input_state_t input_state) { this.input_state = input_state; }
|
|
public void QUERY_set_input_notify(snes_input_notify_t input_notify) { this.input_notify = input_notify; }
|
|
public void QUERY_set_path_request(snes_path_request_t pathRequest) { this.pathRequest = pathRequest; }
|
|
|
|
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 void SPECIAL_Resume()
|
|
{
|
|
WritePipeMessage(eMessage.eMessage_ResumeAfterBRK);
|
|
}
|
|
}
|
|
|
|
}
|