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.
2017-03-06 09:21:10 +00:00
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 - 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)
2015-10-24 08:06:47 +00:00
InstanceDll instanceDll ;
2012-12-25 20:36:04 +00:00
string InstanceName ;
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
2015-10-24 08:06:47 +00:00
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
2017-03-06 09:21:10 +00:00
delegate CommStruct * DllInit ( ) ;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void MessageApi ( eMessage msg ) ;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void BufferApi ( int id , void * ptr , int size ) ;
CommStruct * comm ;
MessageApi Message ;
BufferApi CopyBuffer ; //TODO: consider making private and wrapping
BufferApi SetBuffer ; //TODO: consider making private and wrapping
2012-12-25 20:36:04 +00:00
2015-10-24 08:06:47 +00:00
public LibsnesApi ( string dllPath )
2012-12-25 20:36:04 +00:00
{
InstanceName = "libsneshawk_" + Guid . NewGuid ( ) . ToString ( ) ;
var pipeName = InstanceName ;
2015-10-24 08:06:47 +00:00
instanceDll = new InstanceDll ( dllPath ) ;
var dllinit = ( DllInit ) Marshal . GetDelegateForFunctionPointer ( instanceDll . GetProcAddress ( "DllInit" ) , typeof ( DllInit ) ) ;
2017-03-06 09:21:10 +00:00
Message = ( MessageApi ) Marshal . GetDelegateForFunctionPointer ( instanceDll . GetProcAddress ( "Message" ) , typeof ( MessageApi ) ) ;
CopyBuffer = ( BufferApi ) Marshal . GetDelegateForFunctionPointer ( instanceDll . GetProcAddress ( "CopyBuffer" ) , typeof ( BufferApi ) ) ;
SetBuffer = ( BufferApi ) Marshal . GetDelegateForFunctionPointer ( instanceDll . GetProcAddress ( "SetBuffer" ) , typeof ( BufferApi ) ) ;
2012-12-27 07:59:19 +00:00
2017-03-06 09:21:10 +00:00
comm = dllinit ( ) ;
2012-12-27 07:59:19 +00:00
}
2012-12-25 20:36:04 +00:00
public void Dispose ( )
{
2015-10-24 08:06:47 +00:00
instanceDll . 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
}
2017-03-06 09:21:10 +00:00
public void CopyString ( string str )
2015-11-02 17:26:49 +00:00
{
2017-03-06 09:21:10 +00:00
fixed ( char * cp = str )
CopyBuffer ( 0 , cp , str . Length + 1 ) ;
2015-11-02 17:26:49 +00:00
}
2017-03-06 09:21:10 +00:00
public void CopyBytes ( byte [ ] bytes )
2015-11-02 17:26:49 +00:00
{
2017-03-06 09:21:10 +00:00
fixed ( byte * bp = bytes )
CopyBuffer ( 0 , bp , bytes . Length ) ;
2015-11-02 17:26:49 +00:00
}
2017-03-06 09:21:10 +00:00
public void SetAscii ( string str )
2012-12-25 20:36:04 +00:00
{
2017-03-06 09:21:10 +00:00
fixed ( char * cp = str )
SetBuffer ( 0 , cp , str . Length + 1 ) ;
2012-12-25 20:36:04 +00:00
}
2017-03-06 09:21:10 +00:00
public void SetBytes ( byte [ ] bytes )
2012-12-25 20:36:04 +00:00
{
2017-03-06 09:21:10 +00:00
fixed ( byte * bp = bytes )
SetBuffer ( 0 , bp , bytes . Length ) ;
2012-12-25 20:36:04 +00:00
}
2017-03-06 09:21:10 +00:00
public void SetBytes2 ( byte [ ] bytes )
2012-12-25 20:36:04 +00:00
{
2017-03-06 09:21:10 +00:00
fixed ( byte * bp = bytes )
SetBuffer ( 1 , bp , bytes . Length ) ;
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 ;
2015-11-02 17:26:49 +00:00
public enum eCDLog_AddrType
{
CARTROM , CARTRAM , WRAM , APURAM ,
NUM
} ;
public enum eCDLog_Flags
{
ExecFirst = 0x01 ,
ExecOperand = 0x02 ,
CPUData = 0x04 ,
DMAData = 0x08 , //not supported yet
BRR = 0x80 ,
} ;
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 ) ;
2017-03-06 09:21:10 +00:00
[StructLayout(LayoutKind.Sequential)]
public struct CPURegs
{
public uint pc ;
public ushort a , x , y , z , s , d , vector ; //7x
public byte p , nothing ;
public uint aa , rd ;
public byte sp , dp , db , mdr ;
}
[StructLayout(LayoutKind.Sequential)]
public struct LayerEnables
{
byte _BG1_Prio0 , _BG1_Prio1 ;
byte _BG2_Prio0 , _BG2_Prio1 ;
byte _BG3_Prio0 , _BG3_Prio1 ;
byte _BG4_Prio0 , _BG4_Prio1 ;
byte _Obj_Prio0 , _Obj_Prio1 , _Obj_Prio2 , _Obj_Prio3 ;
public bool BG1_Prio0 { get { return _BG1_Prio0 ! = 0 ; } set { _BG1_Prio0 = ( byte ) ( value ? 1 : 0 ) ; } }
public bool BG1_Prio1 { get { return _BG1_Prio1 ! = 0 ; } set { _BG1_Prio1 = ( byte ) ( value ? 1 : 0 ) ; } }
public bool BG2_Prio0 { get { return _BG2_Prio0 ! = 0 ; } set { _BG2_Prio0 = ( byte ) ( value ? 1 : 0 ) ; } }
public bool BG2_Prio1 { get { return _BG2_Prio1 ! = 0 ; } set { _BG2_Prio1 = ( byte ) ( value ? 1 : 0 ) ; } }
public bool BG3_Prio0 { get { return _BG3_Prio0 ! = 0 ; } set { _BG3_Prio0 = ( byte ) ( value ? 1 : 0 ) ; } }
public bool BG3_Prio1 { get { return _BG3_Prio1 ! = 0 ; } set { _BG3_Prio1 = ( byte ) ( value ? 1 : 0 ) ; } }
public bool BG4_Prio0 { get { return _BG4_Prio0 ! = 0 ; } set { _BG4_Prio0 = ( byte ) ( value ? 1 : 0 ) ; } }
public bool BG4_Prio1 { get { return _BG4_Prio1 ! = 0 ; } set { _BG4_Prio1 = ( byte ) ( value ? 1 : 0 ) ; } }
public bool Obj_Prio0 { get { return _Obj_Prio0 ! = 0 ; } set { _Obj_Prio0 = ( byte ) ( value ? 1 : 0 ) ; } }
public bool Obj_Prio1 { get { return _Obj_Prio1 ! = 0 ; } set { _Obj_Prio1 = ( byte ) ( value ? 1 : 0 ) ; } }
public bool Obj_Prio2 { get { return _Obj_Prio2 ! = 0 ; } set { _Obj_Prio2 = ( byte ) ( value ? 1 : 0 ) ; } }
public bool Obj_Prio3 { get { return _Obj_Prio3 ! = 0 ; } set { _Obj_Prio3 = ( byte ) ( value ? 1 : 0 ) ; } }
}
[StructLayout(LayoutKind.Sequential)]
struct CommStruct
{
//the cmd being executed
public eMessage cmd ;
//the status of the core
public eStatus status ;
//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
public sbyte * str ;
public void * ptr ;
public uint id , addr , value , size ;
public int port , device , index , slot ;
2017-03-06 12:00:17 +00:00
public int width , height ;
2017-03-06 09:21:10 +00:00
public int scanline ;
//this should always be used in pairs
public void * buf0 , buf1 ;
public int buf_size0 , buf_size1 ;
//bleck. this is a long so that it can be a 32/64bit pointer
public fixed long cdl_ptr [ 4 ] ;
2017-03-06 11:32:09 +00:00
public fixed int cdl_size [ 4 ] ;
2017-03-06 09:21:10 +00:00
public CPURegs cpuregs ;
public LayerEnables layerEnables ;
//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_MAPPER mapper ;
//utilities
//TODO: make internal, wrap on the API instead of the comm
public unsafe string GetAscii ( ) { return _getAscii ( str ) ; }
public bool GetBool ( ) { return value ! = 0 ; }
private unsafe string _getAscii ( sbyte * ptr ) {
int len = 0 ;
sbyte * junko = ( sbyte * ) ptr ;
while ( junko [ len ] ! = 0 ) len + + ;
return new string ( ( sbyte * ) str , 0 , len , System . Text . Encoding . ASCII ) ;
}
}
public SNES_REGION Region { get { return comm - > region ; } }
public SNES_MAPPER Mapper { get { return comm - > mapper ; } }
public void SetLayerEnables ( ref LayerEnables enables )
2012-12-25 20:36:04 +00:00
{
2017-03-06 09:21:10 +00:00
comm - > layerEnables = enables ;
QUERY_set_layer_enable ( ) ;
2012-12-25 20:36:04 +00:00
}
}
}