2013-04-29 01:57:41 +00:00
using System ;
2014-04-20 01:44:06 +00:00
using System.Threading ;
2013-04-29 01:57:41 +00:00
using System.Collections.Generic ;
using System.IO ;
using System.Runtime.InteropServices ;
2013-10-27 22:07:40 +00:00
using BizHawk.Common ;
2013-11-04 01:06:36 +00:00
using BizHawk.Emulation.Common ;
2014-01-24 17:46:35 +00:00
using BizHawk.Emulation.Cores.Nintendo.N64.NativeApi ;
2013-10-27 22:07:40 +00:00
2013-11-13 03:32:25 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.N64
2013-04-29 01:57:41 +00:00
{
2014-04-25 01:19:57 +00:00
[ CoreAttributes (
"Mupen64Plus" ,
"Richard Goedeken" ,
isPorted : true ,
isReleased : true
) ]
2014-01-15 11:24:47 +00:00
public class N64 : IEmulator
2013-11-11 03:20:33 +00:00
{
2014-04-19 22:23:13 +00:00
public Dictionary < string , int > GetCpuFlagsAndRegisters ( )
2013-11-11 03:20:33 +00:00
{
2013-11-23 01:17:31 +00:00
//note: the approach this code takes is highly bug-prone
2014-04-19 22:23:13 +00:00
var ret = new Dictionary < string , int > ( ) ;
2013-11-18 03:29:47 +00:00
byte [ ] data = new byte [ 32 * 8 + 4 + 4 + 8 + 8 + 4 + 4 + 32 * 4 + 32 * 8 ] ;
api . getRegisters ( data ) ;
for ( int i = 0 ; i < 32 ; i + + )
{
2013-11-23 01:17:31 +00:00
long reg = BitConverter . ToInt64 ( data , i * 8 ) ;
2014-04-19 22:23:13 +00:00
ret . Add ( "REG" + i + "_lo" , ( int ) ( reg ) ) ;
ret . Add ( "REG" + i + "_hi" , ( int ) ( reg > > 32 ) ) ;
2013-11-18 03:29:47 +00:00
}
UInt32 PC = BitConverter . ToUInt32 ( data , 32 * 8 ) ;
2014-04-19 22:23:13 +00:00
ret . Add ( "PC" , ( int ) PC ) ;
2013-11-18 03:29:47 +00:00
2014-04-19 22:23:13 +00:00
ret . Add ( "LL" , BitConverter . ToInt32 ( data , 32 * 8 + 4 ) ) ;
2013-11-18 03:29:47 +00:00
2013-11-23 01:17:31 +00:00
long Lo = BitConverter . ToInt64 ( data , 32 * 8 + 4 + 4 ) ;
2014-04-19 22:23:13 +00:00
ret . Add ( "LO_lo" , ( int ) Lo ) ;
ret . Add ( "LO_hi" , ( int ) ( Lo > > 32 ) ) ;
2013-11-18 03:29:47 +00:00
2013-11-23 01:17:31 +00:00
long Hi = BitConverter . ToInt64 ( data , 32 * 8 + 4 + 4 + 8 ) ;
2014-04-19 22:23:13 +00:00
ret . Add ( "HI_lo" , ( int ) Hi ) ;
ret . Add ( "HI_hi" , ( int ) ( Hi > > 32 ) ) ;
2013-11-18 03:29:47 +00:00
2014-04-19 22:23:13 +00:00
ret . Add ( "FCR0" , BitConverter . ToInt32 ( data , 32 * 8 + 4 + 4 + 8 + 8 ) ) ;
ret . Add ( "FCR31" , BitConverter . ToInt32 ( data , 32 * 8 + 4 + 4 + 8 + 8 + 4 ) ) ;
2013-11-18 03:29:47 +00:00
for ( int i = 0 ; i < 32 ; i + + )
{
2013-11-23 01:17:31 +00:00
uint reg_cop0 = BitConverter . ToUInt32 ( data , 32 * 8 + 4 + 4 + 8 + 8 + 4 + 4 + i * 4 ) ;
2014-04-19 22:23:13 +00:00
ret . Add ( "CP0 REG" + i , ( int ) reg_cop0 ) ;
2013-11-18 03:29:47 +00:00
}
for ( int i = 0 ; i < 32 ; i + + )
{
2013-11-23 01:17:31 +00:00
long reg_cop1_fgr_64 = BitConverter . ToInt64 ( data , 32 * 8 + 4 + 4 + 8 + 8 + 4 + 4 + 32 * 4 + i * 8 ) ;
2014-04-19 22:23:13 +00:00
ret . Add ( "CP1 FGR REG" + i + "_lo" , ( int ) reg_cop1_fgr_64 ) ;
ret . Add ( "CP1 FGR REG" + i + "_hi" , ( int ) ( reg_cop1_fgr_64 > > 32 ) ) ;
2013-11-18 03:29:47 +00:00
}
return ret ;
2013-11-11 03:20:33 +00:00
}
2013-04-29 01:57:41 +00:00
public string SystemId { get { return "N64" ; } }
2013-08-24 16:54:22 +00:00
public string BoardName { get { return null ; } }
2013-04-29 01:57:41 +00:00
public CoreComm CoreComm { get ; private set ; }
2014-01-15 11:24:47 +00:00
private N64VideoProvider videoProvider ;
public IVideoProvider VideoProvider { get { return videoProvider ; } }
2013-05-06 03:22:27 +00:00
2013-11-16 21:29:42 +00:00
private DisplayType _display_type = DisplayType . NTSC ;
public DisplayType DisplayType { get { return _display_type ; } }
2013-05-04 02:46:37 +00:00
2014-01-15 11:24:47 +00:00
private N64Audio audioProvider ;
2013-05-01 14:38:47 +00:00
public ISoundProvider SoundProvider { get { return null ; } }
2014-01-15 11:24:47 +00:00
public ISyncSoundProvider SyncSoundProvider { get { return audioProvider . Resampler ; } }
2013-05-01 14:38:47 +00:00
public bool StartAsyncSound ( ) { return false ; }
2013-04-29 01:57:41 +00:00
public void EndAsyncSound ( ) { }
2014-01-24 17:46:35 +00:00
private N64Input inputProvider ;
public ControllerDefinition ControllerDefinition { get { return inputProvider . ControllerDefinition ; } }
public IController Controller
2013-04-29 01:57:41 +00:00
{
2014-01-24 17:46:35 +00:00
get { return inputProvider . Controller ; }
set { inputProvider . Controller = value ; }
}
2013-04-29 01:57:41 +00:00
2014-01-15 11:24:47 +00:00
public int Frame { get ; private set ; }
2013-04-29 01:57:41 +00:00
public int LagCount { get ; set ; }
2014-01-24 17:46:35 +00:00
public bool IsLagFrame {
get { return ! inputProvider . LastFrameInputPolled ; }
set { inputProvider . LastFrameInputPolled = ! value ; }
}
2013-11-03 16:29:51 +00:00
public void ResetCounters ( )
2013-05-05 22:53:22 +00:00
{
Frame = 0 ;
LagCount = 0 ;
IsLagFrame = false ;
}
2014-01-15 11:24:47 +00:00
2014-04-20 01:44:06 +00:00
bool PendingThreadTerminate ;
Action PendingThreadAction ;
EventWaitHandle PendingThreadEvent = new EventWaitHandle ( false , EventResetMode . AutoReset ) ;
EventWaitHandle CompleteThreadEvent = new EventWaitHandle ( false , EventResetMode . AutoReset ) ;
void ThreadLoop ( )
{
for ( ; ; )
{
PendingThreadEvent . WaitOne ( ) ;
PendingThreadAction ( ) ;
if ( PendingThreadTerminate )
break ;
CompleteThreadEvent . Set ( ) ;
}
PendingThreadTerminate = false ;
CompleteThreadEvent . Set ( ) ;
}
void RunThreadAction ( Action action )
{
PendingThreadAction = action ;
PendingThreadEvent . Set ( ) ;
CompleteThreadEvent . WaitOne ( ) ;
}
void StartThreadLoop ( )
{
new Thread ( ThreadLoop ) . Start ( ) ;
}
void EndThreadLoop ( )
{
RunThreadAction ( ( ) = > { PendingThreadTerminate = true ; } ) ;
}
2013-04-30 00:36:54 +00:00
public void FrameAdvance ( bool render , bool rendersound )
{
2014-01-15 11:24:47 +00:00
audioProvider . RenderSound = rendersound ;
2013-11-17 03:42:06 +00:00
2014-04-20 01:44:06 +00:00
//RunThreadAction(() =>
2013-05-03 21:13:23 +00:00
{
2014-04-20 01:44:06 +00:00
if ( Controller [ "Reset" ] )
{
api . soft_reset ( ) ;
}
if ( Controller [ "Power" ] )
{
api . hard_reset ( ) ;
}
2013-05-11 23:44:20 +00:00
2014-04-20 01:44:06 +00:00
api . frame_advance ( ) ;
}
//);
2014-01-15 11:24:47 +00:00
2013-05-11 23:44:20 +00:00
if ( IsLagFrame ) LagCount + + ;
2014-01-15 11:24:47 +00:00
Frame + + ;
2013-05-11 23:44:20 +00:00
}
2014-01-15 11:24:47 +00:00
public bool DeterministicEmulation { get { return false ; } }
2013-04-29 01:57:41 +00:00
2013-05-07 22:37:26 +00:00
public byte [ ] ReadSaveRam ( )
{
2013-05-09 00:36:01 +00:00
return api . SaveSaveram ( ) ;
2013-05-07 22:37:26 +00:00
}
public void StoreSaveRam ( byte [ ] data )
{
api . LoadSaveram ( data ) ;
}
public void ClearSaveRam ( )
{
api . InitSaveram ( ) ;
}
public bool SaveRamModified { get { return true ; } set { } }
2013-04-29 01:57:41 +00:00
2013-05-04 04:07:04 +00:00
// these next 5 functions are all exact copy paste from gambatte.
// if something's wrong here, it's probably wrong there too
public void SaveStateText ( TextWriter writer )
{
var temp = SaveStateBinary ( ) ;
2013-06-06 00:59:09 +00:00
temp . SaveAsHexFast ( writer ) ;
2013-05-04 04:07:04 +00:00
// write extra copy of stuff we don't use
writer . WriteLine ( "Frame {0}" , Frame ) ;
}
public void LoadStateText ( TextReader reader )
{
string hex = reader . ReadLine ( ) ;
byte [ ] state = new byte [ hex . Length / 2 ] ;
2013-06-06 00:59:09 +00:00
state . ReadFromHexFast ( hex ) ;
2013-05-04 04:07:04 +00:00
LoadStateBinary ( new BinaryReader ( new MemoryStream ( state ) ) ) ;
}
2013-07-30 00:01:32 +00:00
byte [ ] SaveStatePrivateBuff = new byte [ 16788288 + 1024 ] ;
2013-05-04 04:07:04 +00:00
public void SaveStateBinary ( BinaryWriter writer )
{
2013-07-30 00:01:32 +00:00
byte [ ] data = SaveStatePrivateBuff ;
2013-05-06 03:22:27 +00:00
int bytes_used = api . SaveState ( data ) ;
2013-05-04 04:07:04 +00:00
writer . Write ( data . Length ) ;
writer . Write ( data ) ;
2013-05-11 03:44:01 +00:00
byte [ ] saveram = api . SaveSaveram ( ) ;
writer . Write ( saveram ) ;
2013-11-22 19:34:24 +00:00
if ( saveram . Length ! = mupen64plusApi . kSaveramSize )
throw new InvalidOperationException ( "Unexpected N64 SaveRam size" ) ;
2013-05-11 03:44:01 +00:00
2013-05-04 04:07:04 +00:00
// other variables
writer . Write ( IsLagFrame ) ;
writer . Write ( LagCount ) ;
writer . Write ( Frame ) ;
}
public void LoadStateBinary ( BinaryReader reader )
{
int length = reader . ReadInt32 ( ) ;
2013-08-07 23:09:10 +00:00
reader . Read ( SaveStatePrivateBuff , 0 , length ) ;
byte [ ] data = SaveStatePrivateBuff ;
2013-05-04 04:07:04 +00:00
2013-05-06 03:22:27 +00:00
api . LoadState ( data ) ;
2013-05-04 04:07:04 +00:00
2013-11-22 19:34:24 +00:00
reader . Read ( SaveStatePrivateBuff , 0 , mupen64plusApi . kSaveramSize ) ;
2013-08-07 23:09:10 +00:00
api . LoadSaveram ( SaveStatePrivateBuff ) ;
2013-05-11 03:44:01 +00:00
2013-05-04 04:07:04 +00:00
// other variables
IsLagFrame = reader . ReadBoolean ( ) ;
LagCount = reader . ReadInt32 ( ) ;
Frame = reader . ReadInt32 ( ) ;
}
2013-07-30 00:12:24 +00:00
byte [ ] SaveStateBinaryPrivateBuff = new byte [ 0 ] ;
2013-04-29 01:57:41 +00:00
public byte [ ] SaveStateBinary ( )
{
2013-07-30 00:12:24 +00:00
// WELCOME TO THE HACK ZONE
byte [ ] saveram = api . SaveSaveram ( ) ;
int lenwant = 4 + SaveStatePrivateBuff . Length + saveram . Length + 1 + 4 + 4 ;
if ( SaveStateBinaryPrivateBuff . Length ! = lenwant )
{
Console . WriteLine ( "Allocating new N64 private buffer size {0}" , lenwant ) ;
SaveStateBinaryPrivateBuff = new byte [ lenwant ] ;
}
MemoryStream ms = new MemoryStream ( SaveStateBinaryPrivateBuff ) ;
2013-04-29 01:57:41 +00:00
BinaryWriter bw = new BinaryWriter ( ms ) ;
SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
2013-07-30 00:12:24 +00:00
if ( ms . Length ! = SaveStateBinaryPrivateBuff . Length )
throw new Exception ( "Unexpected Length" ) ;
return SaveStateBinaryPrivateBuff ; // ms.ToArray();
2013-04-29 01:57:41 +00:00
}
2013-05-06 20:51:28 +00:00
public bool BinarySaveStatesPreferred { get { return true ; } }
2013-11-17 03:42:06 +00:00
#region memorycallback
mupen64plusApi . MemoryCallback readcb ;
mupen64plusApi . MemoryCallback writecb ;
void RefreshMemoryCallbacks ( )
{
var mcs = CoreComm . MemoryCallbackSystem ;
// we RefreshMemoryCallbacks() after the triggers in case the trigger turns itself off at that point
if ( mcs . HasReads )
2014-01-15 11:24:47 +00:00
readcb = delegate ( uint addr ) { mcs . CallRead ( addr ) ; } ;
2013-11-17 03:42:06 +00:00
else
readcb = null ;
if ( mcs . HasWrites )
2014-01-15 11:24:47 +00:00
writecb = delegate ( uint addr ) { mcs . CallWrite ( addr ) ; } ;
2013-11-17 03:42:06 +00:00
else
writecb = null ;
api . setReadCallback ( readcb ) ;
api . setWriteCallback ( writecb ) ;
}
#endregion
2013-05-19 20:14:34 +00:00
#region memorydomains
2013-11-04 02:11:40 +00:00
private MemoryDomain MakeMemoryDomain ( string name , mupen64plusApi . N64_MEMORY id , MemoryDomain . Endian endian )
2013-05-19 20:14:34 +00:00
{
int size = api . get_memory_size ( id ) ;
//if this type of memory isnt available, dont make the memory domain
if ( size = = 0 )
return null ;
IntPtr memPtr = api . get_memory_ptr ( id ) ;
MemoryDomain md = new MemoryDomain (
name ,
size ,
endian ,
delegate ( int addr )
{
if ( addr < 0 | | addr > = size )
throw new ArgumentOutOfRangeException ( ) ;
return Marshal . ReadByte ( memPtr , addr ) ;
} ,
delegate ( int addr , byte val )
{
if ( addr < 0 | | addr > = size )
throw new ArgumentOutOfRangeException ( ) ;
Marshal . WriteByte ( memPtr + addr , val ) ;
} ) ;
2013-11-06 02:15:29 +00:00
memoryDomains . Add ( md ) ;
2013-05-19 20:14:34 +00:00
return md ;
}
void InitMemoryDomains ( )
{
2013-11-04 02:11:40 +00:00
MakeMemoryDomain ( "RDRAM" , mupen64plusApi . N64_MEMORY . RDRAM , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "PI Register" , mupen64plusApi . N64_MEMORY . PI_REG , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "SI Register" , mupen64plusApi . N64_MEMORY . SI_REG , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "VI Register" , mupen64plusApi . N64_MEMORY . VI_REG , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "RI Register" , mupen64plusApi . N64_MEMORY . RI_REG , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "AI Register" , mupen64plusApi . N64_MEMORY . AI_REG , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "EEPROM" , mupen64plusApi . N64_MEMORY . EEPROM , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "Mempak 1" , mupen64plusApi . N64_MEMORY . MEMPAK1 , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "Mempak 2" , mupen64plusApi . N64_MEMORY . MEMPAK2 , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "Mempak 3" , mupen64plusApi . N64_MEMORY . MEMPAK3 , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "Mempak 4" , mupen64plusApi . N64_MEMORY . MEMPAK4 , MemoryDomain . Endian . Little ) ;
2013-11-06 02:15:29 +00:00
MemoryDomains = new MemoryDomainList ( memoryDomains ) ;
2013-05-19 20:14:34 +00:00
}
2013-11-06 02:15:29 +00:00
private List < MemoryDomain > memoryDomains = new List < MemoryDomain > ( ) ;
public MemoryDomainList MemoryDomains { get ; private set ; }
2013-05-19 20:14:34 +00:00
#endregion
2013-04-29 01:57:41 +00:00
2013-05-01 14:38:47 +00:00
public void Dispose ( )
{
2014-04-20 01:44:06 +00:00
RunThreadAction ( ( ) = >
{
videoProvider . Dispose ( ) ;
audioProvider . Dispose ( ) ;
api . Dispose ( ) ;
} ) ;
EndThreadLoop ( ) ;
2013-05-01 14:38:47 +00:00
}
2013-04-29 01:57:41 +00:00
2014-01-15 11:24:47 +00:00
// mupen64plus DLL Api
private mupen64plusApi api ;
2013-05-04 01:16:27 +00:00
2014-01-15 11:24:47 +00:00
/// <summary>
/// Create mupen64plus Emulator
/// </summary>
/// <param name="comm">Core communication object</param>
/// <param name="game">Game information of game to load</param>
/// <param name="rom">Rom that should be loaded</param>
/// <param name="SyncSettings">N64SyncSettings object</param>
2013-12-26 23:04:22 +00:00
public N64 ( CoreComm comm , GameInfo game , byte [ ] rom , object SyncSettings )
2013-04-29 01:57:41 +00:00
{
2013-12-26 23:04:22 +00:00
int SaveType = 0 ;
if ( game . OptionValue ( "SaveType" ) = = "EEPROM_16K" )
SaveType = 1 ;
2013-04-29 01:57:41 +00:00
CoreComm = comm ;
2013-04-30 00:08:21 +00:00
2013-12-26 23:04:22 +00:00
this . SyncSettings = ( N64SyncSettings ) SyncSettings ? ? new N64SyncSettings ( ) ;
2013-11-16 21:29:42 +00:00
byte country_code = rom [ 0x3E ] ;
switch ( country_code )
{
// PAL codes
case 0x44 :
case 0x46 :
case 0x49 :
case 0x50 :
case 0x53 :
case 0x55 :
case 0x58 :
case 0x59 :
_display_type = DisplayType . PAL ;
break ;
// NTSC codes
case 0x37 :
case 0x41 :
case 0x45 :
case 0x4a :
default : // Fallback for unknown codes
_display_type = DisplayType . NTSC ;
break ;
}
2014-01-20 09:56:52 +00:00
switch ( DisplayType )
{
case DisplayType . NTSC :
comm . VsyncNum = 60000 ;
comm . VsyncDen = 1001 ;
break ;
case DisplayType . PAL :
case DisplayType . DENDY :
default :
comm . VsyncNum = 50 ;
comm . VsyncDen = 1 ;
break ;
}
2013-11-16 21:29:42 +00:00
2014-04-20 01:44:06 +00:00
StartThreadLoop ( ) ;
2014-01-24 17:46:35 +00:00
var videosettings = this . SyncSettings . GetVPS ( game ) ;
2014-04-20 01:44:06 +00:00
//zero 19-apr-2014 - added this to solve problem with SDL initialization corrupting the main thread (I think) and breaking subsequent emulators (for example, NES)
//not sure why this works... if we put the plugin initializations in here, we get deadlocks in some SDL initialization. doesnt make sense to me...
RunThreadAction ( ( ) = >
{
api = new mupen64plusApi ( this , rom , videosettings , SaveType ) ;
} ) ;
2014-01-24 17:46:35 +00:00
// Order is important because the register with the mupen core
videoProvider = new N64VideoProvider ( api , videosettings ) ;
2014-01-15 11:24:47 +00:00
audioProvider = new N64Audio ( api ) ;
2014-05-08 20:03:00 +00:00
inputProvider = new N64Input ( api , comm , this . SyncSettings . Controllers ) ;
2014-01-24 17:46:35 +00:00
api . AttachPlugin ( mupen64plusApi . m64p_plugin_type . M64PLUGIN_RSP ,
"mupen64plus-rsp-hle.dll" ) ;
2014-01-15 11:24:47 +00:00
2013-05-19 20:14:34 +00:00
InitMemoryDomains ( ) ;
2014-01-15 11:24:47 +00:00
RefreshMemoryCallbacks ( ) ;
2014-01-24 17:46:35 +00:00
api . AsyncExecuteEmulator ( ) ;
2013-04-30 00:08:21 +00:00
}
2013-12-22 00:44:39 +00:00
2013-12-26 23:04:22 +00:00
N64SyncSettings SyncSettings ;
2013-12-22 00:44:39 +00:00
public object GetSettings ( ) { return null ; }
2013-12-26 23:04:22 +00:00
public object GetSyncSettings ( ) { return SyncSettings . Clone ( ) ; }
2013-12-22 00:44:39 +00:00
public bool PutSettings ( object o ) { return false ; }
2013-12-26 23:04:22 +00:00
public bool PutSyncSettings ( object o ) { SyncSettings = ( N64SyncSettings ) o ; return true ; }
2013-04-29 01:57:41 +00:00
}
}