2013-01-18 05:06:26 +00:00
using System ;
2012-12-25 20:36:04 +00:00
using System.Diagnostics ;
using System.IO ;
using System.IO.Pipes ;
using System.Collections.Generic ;
using System.Runtime.InteropServices ;
using System.IO.MemoryMappedFiles ;
2013-11-22 09:33:56 +00:00
using BizHawk.Common ;
2013-11-13 23:36:21 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.SNES
2012-12-25 20:36:04 +00:00
{
2013-11-22 09:33:56 +00:00
public unsafe partial class LibsnesApi : IDisposable
2012-12-25 20:36:04 +00:00
{
//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.
2012-12-26 21:25:39 +00:00
//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.....
2012-12-25 20:36:04 +00:00
//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)
2013-11-22 09:33:56 +00:00
2012-12-25 20:36:04 +00:00
string InstanceName ;
Process process ;
2014-06-17 22:03:08 +00:00
System . Threading . EventWaitHandle watchdogEvent ;
2012-12-25 20:36:04 +00:00
NamedPipeServerStream pipe ;
BinaryWriter bwPipe ;
BinaryReader brPipe ;
MemoryMappedFile mmf ;
MemoryMappedViewAccessor mmva ;
byte * mmvaPtr ;
2012-12-27 07:59:19 +00:00
IPCRingBuffer rbuf , wbuf ;
IPCRingBufferStream rbufstr , wbufstr ;
SwitcherStream rstream , wstream ;
bool bufio ;
2012-12-25 20:36:04 +00:00
2013-11-22 09:33:56 +00:00
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
public static unsafe extern void * CopyMemory ( void * dest , void * src , ulong count ) ;
2012-12-25 20:36:04 +00:00
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 ( ) ;
2013-11-25 20:51:26 +00:00
#if DEBUG
2012-12-25 20:36:04 +00:00
//use this to get a debug console with libsnes output
2013-11-25 20:51:26 +00:00
InstanceName = "console-" + InstanceName ;
#endif
2012-12-25 20:36:04 +00:00
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 ) ;
2014-06-17 22:03:08 +00:00
//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 ) ;
2012-12-25 20:36:04 +00:00
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 ( ) ;
2012-12-27 07:59:19 +00:00
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 ( ) ;
}
2012-12-25 20:36:04 +00:00
public void Dispose ( )
{
2014-06-17 22:03:08 +00:00
watchdogEvent . Dispose ( ) ;
2012-12-25 20:36:04 +00:00
process . Kill ( ) ;
process . Dispose ( ) ;
process = null ;
pipe . Dispose ( ) ;
mmva . Dispose ( ) ;
mmf . Dispose ( ) ;
2012-12-27 07:59:19 +00:00
rbuf . Dispose ( ) ;
wbuf . Dispose ( ) ;
2013-01-18 04:46:17 +00:00
foreach ( var smb in DeallocatedMemoryBlocks . Values )
smb . Dispose ( ) ;
DeallocatedMemoryBlocks . Clear ( ) ;
2012-12-27 07:59:19 +00:00
}
public void BeginBufferIO ( )
{
bufio = true ;
WritePipeMessage ( eMessage . eMessage_BeginBufferIO ) ;
rstream . SetCurrStream ( rbufstr ) ;
wstream . SetCurrStream ( wbufstr ) ;
}
public void EndBufferIO ( )
{
2013-01-18 05:06:26 +00:00
if ( ! bufio ) return ;
2012-12-27 07:59:19 +00:00
bufio = false ;
WritePipeMessage ( eMessage . eMessage_EndBufferIO ) ;
rstream . SetCurrStream ( pipe ) ;
wstream . SetCurrStream ( pipe ) ;
2012-12-25 20:36:04 +00:00
}
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 ) ;
2012-12-27 07:59:19 +00:00
bwPipe . Flush ( ) ;
2012-12-25 20:36:04 +00:00
}
public int MessageCounter ;
void WritePipeMessage ( eMessage msg )
{
2012-12-27 07:59:19 +00:00
if ( ! bufio ) MessageCounter + + ;
2013-11-22 09:33:56 +00:00
//Console.WriteLine("write pipe message: " + msg);
2012-12-25 20:36:04 +00:00
bwPipe . Write ( ( int ) msg ) ;
2012-12-27 07:59:19 +00:00
bwPipe . Flush ( ) ;
2012-12-25 20:36:04 +00:00
}
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 ( ) ;
2012-12-27 07:59:19 +00:00
if ( ! bufio ) MessageCounter + + ;
2013-11-22 09:33:56 +00:00
//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 ;
2012-12-25 20:36:04 +00:00
}
2013-11-22 09:33:56 +00:00
}
2012-12-25 20:36:04 +00:00
2013-11-12 02:34:56 +00:00
public Action < uint > ReadHook , ExecHook ;
public Action < uint , byte > WriteHook ;
2012-12-25 20:36:04 +00:00
Dictionary < string , SharedMemoryBlock > SharedMemoryBlocks = new Dictionary < string , SharedMemoryBlock > ( ) ;
2013-01-18 04:46:17 +00:00
Dictionary < string , SharedMemoryBlock > DeallocatedMemoryBlocks = new Dictionary < string , SharedMemoryBlock > ( ) ;
2012-12-25 20:36:04 +00:00
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 ;
2013-11-22 09:33:56 +00:00
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 ; }
2012-12-25 20:36:04 +00:00
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 ) ;
2013-11-22 09:33:56 +00:00
public void SPECIAL_Resume ( )
2012-12-25 20:36:04 +00:00
{
2013-12-29 02:20:13 +00:00
WritePipeMessage ( eMessage . eMessage_ResumeAfterBRK ) ;
2012-12-25 20:36:04 +00:00
}
}
}