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.Collections.Generic ;
using System.Runtime.InteropServices ;
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
{
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-04-18 03:49:17 +00:00
delegate IntPtr DllInit ( ) ;
2017-03-06 09:21:10 +00:00
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void MessageApi ( eMessage msg ) ;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void BufferApi ( int id , void * ptr , int size ) ;
CommStruct * comm ;
MessageApi Message ;
2017-04-19 03:09:04 +00:00
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 ( ) ;
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 ) ) ;
2017-04-19 03:09:04 +00:00
_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-04-18 03:49:17 +00:00
comm = ( CommStruct * ) dllinit ( ) . ToPointer ( ) ;
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 ( ) ;
2017-03-10 14:22:45 +00:00
foreach ( var smb in DeallocatedMemoryBlocks . Values ) smb . Dispose ( ) ;
foreach ( var smb in SharedMemoryBlocks . Values ) smb . Dispose ( ) ;
SharedMemoryBlocks . Clear ( ) ;
2013-01-18 04:46:17 +00:00
DeallocatedMemoryBlocks . Clear ( ) ;
2012-12-27 07:59:19 +00:00
}
2017-04-19 03:09:04 +00:00
/// <summary>
/// Copy an ascii string into libretro. It keeps the copy.
/// </summary>
public void CopyAscii ( int id , string str )
2015-11-02 17:26:49 +00:00
{
2017-04-19 03:09:04 +00:00
fixed ( byte * cp = System . Text . Encoding . ASCII . GetBytes ( str + "\0" ) )
_copyBuffer ( id , cp , str . Length + 1 ) ;
2015-11-02 17:26:49 +00:00
}
2017-04-19 03:09:04 +00:00
/// <summary>
/// Copy a buffer into libretro. It keeps the copy.
/// </summary>
public void CopyBytes ( int id , byte [ ] bytes )
2015-11-02 17:26:49 +00:00
{
2017-03-06 09:21:10 +00:00
fixed ( byte * bp = bytes )
2017-04-19 03:09:04 +00:00
_copyBuffer ( id , bp , bytes . Length ) ;
2015-11-02 17:26:49 +00:00
}
2017-04-19 03:09:04 +00:00
/// <summary>
/// Locks a buffer and sets it into libretro. You must pass a delegate to be executed while that buffer is locked.
/// This is meant to be used for avoiding a memcpy for large roms (which the core is then just going to memcpy again on its own)
/// The memcpy has to happen at some point (libretro semantics specify [not literally, the docs dont say] that the core should finish using the buffer before its init returns)
/// but this limits it to once.
/// Moreover, this keeps the c++ side from having to free strings when they're no longer used (and memory management is trickier there, so we try to avoid it)
/// </summary>
public void SetBytes ( int id , byte [ ] bytes , Action andThen )
2012-12-25 20:36:04 +00:00
{
2017-03-06 09:21:10 +00:00
fixed ( byte * bp = bytes )
2017-04-19 03:09:04 +00:00
{
_setBuffer ( id , bp , bytes . Length ) ;
andThen ( ) ;
}
2012-12-25 20:36:04 +00:00
}
2017-04-19 03:09:04 +00:00
/// <summary>
/// see SetBytes
/// </summary>
public void SetAscii ( int id , string str , Action andThen )
2012-12-25 20:36:04 +00:00
{
2017-04-19 03:09:04 +00:00
fixed ( byte * cp = System . Text . Encoding . ASCII . GetBytes ( str + "\0" ) )
{
_setBuffer ( id , cp , str . Length + 1 ) ;
andThen ( ) ;
}
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 ,
2017-05-06 21:23:26 +00:00
SGB_CARTROM , SGB_CARTRAM , SGB_WRAM , SGB_HRAM ,
2015-11-02 17:26:49 +00:00
NUM
} ;
2017-05-14 18:51:02 +00:00
public enum eTRACE : uint
{
CPU = 0 ,
SMP = 1 ,
GB = 2
}
2015-11-02 17:26:49 +00:00
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 ( ) ;
2017-04-16 22:08:57 +00:00
public delegate short snes_input_state_t ( int port , int device , int index , int id ) ;
2012-12-25 20:36:04 +00:00
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 ) ;
2017-05-14 18:51:02 +00:00
public delegate void snes_trace_t ( uint which , string msg ) ;
2012-12-25 20:36:04 +00:00
2017-03-06 09:21:10 +00:00
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 ;
}
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 ) ; } }
}
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 ;
2017-04-09 21:45:05 +00:00
public fixed int inports [ 2 ] ;
2017-03-06 09:21:10 +00:00
//this should always be used in pairs
2017-04-19 03:09:04 +00:00
public fixed uint buf [ 3 ] ; //ACTUALLY A POINTER but can't marshal it :(
public fixed int buf_size [ 3 ] ;
2017-03-06 09:21:10 +00:00
//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
}
2017-04-09 21:45:05 +00:00
public void SetInputPortBeforeInit ( int port , SNES_INPUT_PORT type )
{
comm - > inports [ port ] = ( int ) type ;
}
2012-12-25 20:36:04 +00:00
}
}