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 ;
2017-06-10 13:46:38 +00:00
using BizHawk.Emulation.Cores.Waterbox ;
using BizHawk.Common.BizInvoke ;
2017-06-10 19:20:06 +00:00
using BizHawk.Emulation.Common ;
2013-11-22 09:33:56 +00:00
2013-11-13 23:36:21 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.SNES
2012-12-25 20:36:04 +00:00
{
2017-06-10 17:43:03 +00:00
public abstract unsafe class CoreImpl
2012-12-25 20:36:04 +00:00
{
2017-06-10 17:43:03 +00:00
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
public abstract IntPtr DllInit ( ) ;
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
public abstract void Message ( LibsnesApi . eMessage msg ) ;
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
public abstract void CopyBuffer ( int id , void * ptr , int size ) ;
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
public abstract void SetBuffer ( int id , void * ptr , int size ) ;
2017-06-11 15:28:29 +00:00
[BizImport(CallingConvention.Cdecl)]
public abstract void PostLoadState ( ) ;
2017-06-10 17:43:03 +00:00
}
2017-06-10 19:20:06 +00:00
public unsafe partial class LibsnesApi : IDisposable , IMonitor , IBinaryStateable
2017-06-10 17:43:03 +00:00
{
static LibsnesApi ( )
{
if ( sizeof ( CommStruct ) ! = 232 )
{
throw new InvalidOperationException ( "sizeof(comm)" ) ;
}
}
2017-06-10 13:46:38 +00:00
private PeRunner _exe ;
private CoreImpl _core ;
private bool _disposed ;
private CommStruct * _comm ;
2017-06-10 17:43:03 +00:00
private readonly Dictionary < string , IntPtr > _sharedMemoryBlocks = new Dictionary < string , IntPtr > ( ) ;
2017-06-10 21:19:18 +00:00
private bool _sealed = false ;
2012-12-25 20:36:04 +00:00
2017-06-10 17:43:03 +00:00
public void Enter ( )
2017-06-10 13:46:38 +00:00
{
2017-06-10 17:43:03 +00:00
_exe . Enter ( ) ;
2017-06-10 13:46:38 +00:00
}
2017-03-06 09:21:10 +00:00
2017-06-10 17:43:03 +00:00
public void Exit ( )
{
_exe . Exit ( ) ;
}
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
{
2017-06-10 13:46:38 +00:00
_exe = new PeRunner ( new PeRunnerOptions
{
Filename = "libsnes.wbx" ,
Path = dllPath ,
2017-06-10 19:53:38 +00:00
SbrkHeapSizeKB = 4 * 1024 ,
2017-06-11 19:30:54 +00:00
InvisibleHeapSizeKB = 8 * 1024 ,
2017-06-10 19:53:38 +00:00
MmapHeapSizeKB = 32 * 1024 , // TODO: see if we can safely make libco stacks smaller
PlainHeapSizeKB = 2 * 1024 , // TODO: wasn't there more in here?
SealedHeapSizeKB = 128 * 1024
2017-06-10 13:46:38 +00:00
} ) ;
2017-06-11 11:47:16 +00:00
using ( _exe . EnterExit ( ) )
{
2017-06-11 11:50:41 +00:00
// Marshal checks that function pointers passed to GetDelegateForFunctionPointer are
// _currently_ valid when created, even though they don't need to be valid until
// the delegate is later invoked. so GetInvoker needs to be acquired within a lock.
2017-06-18 12:49:55 +00:00
_core = BizInvoker . GetInvoker < CoreImpl > ( _exe , _exe , CallingConventionAdapters . Waterbox ) ;
2017-06-11 11:47:16 +00:00
_comm = ( CommStruct * ) _core . DllInit ( ) . ToPointer ( ) ;
}
2012-12-27 07:59:19 +00:00
}
2012-12-25 20:36:04 +00:00
public void Dispose ( )
{
2017-06-10 13:46:38 +00:00
if ( ! _disposed )
{
2017-06-10 17:43:03 +00:00
_disposed = true ;
2017-06-10 13:46:38 +00:00
_exe . Dispose ( ) ;
_exe = null ;
_core = null ;
_comm = null ;
}
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-06-10 13:46:38 +00:00
fixed ( byte * cp = System . Text . Encoding . ASCII . GetBytes ( str + "\0" ) )
{
_core . 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-06-10 13:46:38 +00:00
{
_core . 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-06-10 21:19:18 +00:00
if ( _sealed )
throw new InvalidOperationException ( "Init period is over" ) ;
2017-03-06 09:21:10 +00:00
fixed ( byte * bp = bytes )
2017-04-19 03:09:04 +00:00
{
2017-06-10 13:46:38 +00:00
_core . SetBuffer ( id , bp , bytes . Length ) ;
2017-04-19 03:09:04 +00:00
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-06-10 21:19:18 +00:00
if ( _sealed )
throw new InvalidOperationException ( "Init period is over" ) ;
2017-06-10 13:46:38 +00:00
fixed ( byte * cp = System . Text . Encoding . ASCII . GetBytes ( str + "\0" ) )
2017-04-19 03:09:04 +00:00
{
2017-06-10 13:46:38 +00:00
_core . SetBuffer ( id , cp , str . Length + 1 ) ;
2017-04-19 03:09:04 +00:00
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
2017-06-10 19:20:06 +00:00
{
CPU = 0 ,
SMP = 1 ,
2017-05-14 18:51:02 +00:00
GB = 2
2017-06-10 19:20:06 +00:00
}
2017-05-14 18:51:02 +00:00
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
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
2017-06-10 17:43:03 +00:00
[StructLayout(LayoutKind.Explicit)]
2017-03-06 09:21:10 +00:00
public struct CPURegs
{
2017-06-10 17:43:03 +00:00
[FieldOffset(0)]
2017-03-06 09:21:10 +00:00
public uint pc ;
2017-06-10 17:43:03 +00:00
[FieldOffset(4)]
2017-03-06 09:21:10 +00:00
public ushort a , x , y , z , s , d , vector ; //7x
2017-06-10 17:43:03 +00:00
[FieldOffset(18)]
2017-03-06 09:21:10 +00:00
public byte p , nothing ;
2017-06-10 17:43:03 +00:00
[FieldOffset(20)]
2017-03-06 09:21:10 +00:00
public uint aa , rd ;
2017-06-10 17:43:03 +00:00
[FieldOffset(28)]
2017-03-06 09:21:10 +00:00
public byte sp , dp , db , mdr ;
}
2017-06-10 17:43:03 +00:00
[StructLayout(LayoutKind.Sequential)]
2017-03-06 09:21:10 +00:00
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 ) ; } }
2017-06-10 13:46:38 +00:00
2017-03-06 09:21:10 +00:00
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 ) ; } }
}
2017-06-10 17:43:03 +00:00
[StructLayout(LayoutKind.Explicit)]
2017-03-06 09:21:10 +00:00
struct CommStruct
{
2017-06-10 17:43:03 +00:00
[FieldOffset(0)]
2017-03-06 09:21:10 +00:00
//the cmd being executed
public eMessage cmd ;
2017-06-10 17:43:03 +00:00
[FieldOffset(4)]
2017-03-06 09:21:10 +00:00
//the status of the core
public eStatus status ;
2017-06-10 17:43:03 +00:00
[FieldOffset(8)]
2017-03-06 09:21:10 +00:00
//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
2017-06-10 17:43:03 +00:00
[FieldOffset(16)]
2017-03-06 09:21:10 +00:00
public sbyte * str ;
2017-06-10 17:43:03 +00:00
[FieldOffset(24)]
2017-03-06 09:21:10 +00:00
public void * ptr ;
2017-06-10 17:43:03 +00:00
[FieldOffset(32)]
2017-06-10 18:14:50 +00:00
public uint id ;
[FieldOffset(36)]
public uint addr ;
[FieldOffset(40)]
public uint value ;
[FieldOffset(44)]
public uint size ;
2017-06-10 17:43:03 +00:00
[FieldOffset(48)]
2017-06-10 18:14:50 +00:00
public int port ;
[FieldOffset(52)]
public int device ;
[FieldOffset(56)]
public int index ;
[FieldOffset(60)]
public int slot ;
2017-06-10 17:43:03 +00:00
[FieldOffset(64)]
2017-06-10 18:14:50 +00:00
public int width ;
[FieldOffset(68)]
public int height ;
2017-06-10 17:43:03 +00:00
[FieldOffset(72)]
2017-03-06 09:21:10 +00:00
public int scanline ;
2017-06-10 17:43:03 +00:00
[FieldOffset(76)]
2017-04-09 21:45:05 +00:00
public fixed int inports [ 2 ] ;
2017-03-06 09:21:10 +00:00
2017-06-10 17:43:03 +00:00
[FieldOffset(88)]
2017-03-06 09:21:10 +00:00
//this should always be used in pairs
2017-06-10 17:43:03 +00:00
public fixed long buf [ 3 ] ; //ACTUALLY A POINTER but can't marshal it :(
[FieldOffset(112)]
2017-04-19 03:09:04 +00:00
public fixed int buf_size [ 3 ] ;
2017-03-06 09:21:10 +00:00
2017-06-10 17:43:03 +00:00
[FieldOffset(128)]
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-06-10 17:43:03 +00:00
[FieldOffset(160)]
2017-03-06 11:32:09 +00:00
public fixed int cdl_size [ 4 ] ;
2017-03-06 09:21:10 +00:00
2017-06-10 17:43:03 +00:00
[FieldOffset(176)]
2017-03-06 09:21:10 +00:00
public CPURegs cpuregs ;
2017-06-10 17:43:03 +00:00
[FieldOffset(208)]
2017-03-06 09:21:10 +00:00
public LayerEnables layerEnables ;
2017-06-10 17:43:03 +00:00
[FieldOffset(220)]
2017-03-06 09:21:10 +00:00
//static configuration-type information which can be grabbed off the core at any time without even needing a QUERY command
public SNES_REGION region ;
2017-06-10 17:43:03 +00:00
[FieldOffset(224)]
2017-03-06 09:21:10 +00:00
public SNES_MAPPER mapper ;
2017-06-10 17:43:03 +00:00
[FieldOffset(228)]
public int unused ;
2017-03-06 09:21:10 +00:00
//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 ; }
2017-06-10 13:46:38 +00:00
private unsafe string _getAscii ( sbyte * ptr )
{
2017-03-06 09:21:10 +00:00
int len = 0 ;
sbyte * junko = ( sbyte * ) ptr ;
2017-06-10 13:46:38 +00:00
while ( junko [ len ] ! = 0 ) len + + ;
2017-03-06 09:21:10 +00:00
return new string ( ( sbyte * ) str , 0 , len , System . Text . Encoding . ASCII ) ;
}
}
2017-06-10 13:46:38 +00:00
public SNES_REGION Region
{
get
{
using ( _exe . EnterExit ( ) )
{
return _comm - > region ;
}
}
}
public SNES_MAPPER Mapper
{
get
{
using ( _exe . EnterExit ( ) )
{
return _comm - > mapper ;
}
}
}
2017-03-06 09:21:10 +00:00
public void SetLayerEnables ( ref LayerEnables enables )
2012-12-25 20:36:04 +00:00
{
2017-06-10 13:46:38 +00:00
using ( _exe . EnterExit ( ) )
{
_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 )
{
2017-06-10 13:46:38 +00:00
using ( _exe . EnterExit ( ) )
{
_comm - > inports [ port ] = ( int ) type ;
}
2017-04-09 21:45:05 +00:00
}
2017-06-10 19:20:06 +00:00
public void Seal ( )
{
2017-06-10 23:51:59 +00:00
/ * Cothreads can very easily acquire "pointer poison" ; because their stack and even registers
* are part of state , any poisoned pointer that ' s used even temporarily might be persisted longer
* than needed . Most of the libsnes core cothreads handle internal matters only and aren ' t very
* vulnerable to pointer poison , but the main boss cothread is used heavily during init , when
* many syscalls happen and many kinds of poison can end up on the stack . so here , we call
* _core . DllInit ( ) again , which recreates that cothread , zeroing out all of the memory first ,
* as well as zeroing out the comm struct . * /
_core . DllInit ( ) ;
2017-06-10 19:20:06 +00:00
_exe . Seal ( ) ;
2017-06-10 21:19:18 +00:00
_sealed = true ;
2017-06-10 19:20:06 +00:00
}
public void SaveStateBinary ( BinaryWriter writer )
{
_exe . SaveStateBinary ( writer ) ;
}
public void LoadStateBinary ( BinaryReader reader )
{
_exe . LoadStateBinary ( reader ) ;
2017-06-11 15:28:29 +00:00
_core . PostLoadState ( ) ;
2017-06-10 19:20:06 +00:00
}
2012-12-25 20:36:04 +00:00
}
}