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 ;
2014-07-03 18:54:53 +00:00
using BizHawk.Common.BufferExtensions ;
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" ,
2014-06-01 01:57:22 +00:00
"" ,
2014-04-25 01:19:57 +00:00
isPorted : true ,
2014-06-01 01:57:22 +00:00
isReleased : true ,
portedVersion : "2.0" ,
portedUrl : "https://code.google.com/p/mupen64plus/"
2014-04-25 01:19:57 +00:00
) ]
2014-09-07 21:48:05 +00:00
public partial class N64 : IEmulator , IMemoryDomains
2013-11-11 03:20:33 +00:00
{
2014-05-13 00:31:32 +00:00
private readonly N64Input _inputProvider ;
private readonly N64VideoProvider _videoProvider ;
private readonly N64Audio _audioProvider ;
private readonly EventWaitHandle _pendingThreadEvent = new EventWaitHandle ( false , EventResetMode . AutoReset ) ;
private readonly EventWaitHandle _completeThreadEvent = new EventWaitHandle ( false , EventResetMode . AutoReset ) ;
private mupen64plusApi api ; // mupen64plus DLL Api
2014-06-01 12:06:22 +00:00
2014-05-13 00:31:32 +00:00
private N64SyncSettings _syncSettings ;
2014-06-01 12:06:22 +00:00
private N64Settings _settings ;
2014-05-13 00:31:32 +00:00
private bool _pendingThreadTerminate ;
private DisplayType _display_type = DisplayType . NTSC ;
private Action _pendingThreadAction ;
2014-05-13 00:06:33 +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>
2014-09-07 21:48:05 +00:00
/// <param name="syncSettings">N64SyncSettings object</param>
2014-08-23 19:06:37 +00:00
[CoreConstructor("N64")]
2014-06-01 12:06:22 +00:00
public N64 ( CoreComm comm , GameInfo game , byte [ ] rom , object settings , object syncSettings )
2014-05-13 00:06:33 +00:00
{
int SaveType = 0 ;
if ( game . OptionValue ( "SaveType" ) = = "EEPROM_16K" )
{
SaveType = 1 ;
}
CoreComm = comm ;
2014-06-01 12:06:22 +00:00
_syncSettings = ( N64SyncSettings ) syncSettings ? ? new N64SyncSettings ( ) ;
_settings = ( N64Settings ) settings ? ? new N64Settings ( ) ;
2014-05-13 00:06:33 +00:00
2014-09-07 00:23:15 +00:00
if ( game . OptionValue ( "expansionpak" ) ! = null & & game . OptionValue ( "expansionpak" ) = = "1" )
{
_syncSettings . DisableExpansionSlot = false ;
}
2014-05-13 00:06:33 +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 ;
}
switch ( DisplayType )
{
case DisplayType . NTSC :
comm . VsyncNum = 60000 ;
comm . VsyncDen = 1001 ;
break ;
default :
comm . VsyncNum = 50 ;
comm . VsyncDen = 1 ;
break ;
}
StartThreadLoop ( ) ;
2014-06-01 12:06:22 +00:00
var videosettings = _syncSettings . GetVPS ( game , _settings . VideoSizeX , _settings . VideoSizeY ) ;
2014-07-20 16:59:03 +00:00
var coreType = _syncSettings . Core ;
2014-05-13 00:06:33 +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 ( ( ) = >
{
2014-09-06 23:03:34 +00:00
api = new mupen64plusApi ( this , rom , videosettings , SaveType , ( int ) coreType , _syncSettings . DisableExpansionSlot ) ;
2014-05-13 00:06:33 +00:00
} ) ;
// Order is important because the register with the mupen core
_videoProvider = new N64VideoProvider ( api , videosettings ) ;
_audioProvider = new N64Audio ( api ) ;
2014-05-13 00:31:32 +00:00
_inputProvider = new N64Input ( api , comm , this . _syncSettings . Controllers ) ;
2014-05-14 01:50:36 +00:00
2014-07-20 16:59:03 +00:00
string rsp = _syncSettings . Rsp = = N64SyncSettings . RspType . Rsp_Hle ?
2014-05-14 01:50:36 +00:00
"mupen64plus-rsp-hle.dll" :
"mupen64plus-rsp-z64-hlevideo.dll" ;
api . AttachPlugin ( mupen64plusApi . m64p_plugin_type . M64PLUGIN_RSP , rsp ) ;
2014-05-13 00:06:33 +00:00
InitMemoryDomains ( ) ;
RefreshMemoryCallbacks ( ) ;
api . AsyncExecuteEmulator ( ) ;
2014-06-28 13:00:53 +00:00
SetControllerButtons ( ) ;
2014-05-13 00:06:33 +00:00
}
2014-05-13 00:31:32 +00:00
public void Dispose ( )
2013-11-11 03:20:33 +00:00
{
2014-05-13 00:31:32 +00:00
RunThreadAction ( ( ) = >
{
_videoProvider . Dispose ( ) ;
_audioProvider . Dispose ( ) ;
api . Dispose ( ) ;
} ) ;
2013-11-18 03:29:47 +00:00
2014-05-13 00:31:32 +00:00
EndThreadLoop ( ) ;
}
private void ThreadLoop ( )
{
for ( ; ; )
2013-11-18 03:29:47 +00:00
{
2014-05-13 00:31:32 +00:00
_pendingThreadEvent . WaitOne ( ) ;
_pendingThreadAction ( ) ;
if ( _pendingThreadTerminate )
{
break ;
}
_completeThreadEvent . Set ( ) ;
2013-11-18 03:29:47 +00:00
}
2014-05-13 00:31:32 +00:00
_pendingThreadTerminate = false ;
_completeThreadEvent . Set ( ) ;
}
2013-11-18 03:29:47 +00:00
2014-05-13 00:31:32 +00:00
private void RunThreadAction ( Action action )
{
_pendingThreadAction = action ;
_pendingThreadEvent . Set ( ) ;
_completeThreadEvent . WaitOne ( ) ;
}
2013-11-18 03:29:47 +00:00
2014-05-13 00:31:32 +00:00
private void StartThreadLoop ( )
{
2014-09-07 21:48:05 +00:00
var thread = new Thread ( ThreadLoop ) { IsBackground = true } ;
thread . Start ( ) ; // will this solve the hanging process problem?
2014-05-13 00:31:32 +00:00
}
2013-11-18 03:29:47 +00:00
2014-05-13 00:31:32 +00:00
private void EndThreadLoop ( )
{
RunThreadAction ( ( ) = > { _pendingThreadTerminate = true ; } ) ;
}
2013-11-18 03:29:47 +00:00
2014-05-13 00:31:32 +00:00
public void FrameAdvance ( bool render , bool rendersound )
{
2014-08-03 00:00:26 +00:00
IsVIFrame = false ;
2014-05-13 00:31:32 +00:00
_audioProvider . RenderSound = rendersound ;
2013-11-18 03:29:47 +00:00
2014-05-13 00:31:32 +00:00
if ( Controller [ "Reset" ] )
2013-11-18 03:29:47 +00:00
{
2014-05-13 00:31:32 +00:00
api . soft_reset ( ) ;
2013-11-18 03:29:47 +00:00
}
2014-05-13 00:31:32 +00:00
if ( Controller [ "Power" ] )
2013-11-18 03:29:47 +00:00
{
2014-05-13 00:31:32 +00:00
api . hard_reset ( ) ;
2013-11-18 03:29:47 +00:00
}
2014-05-13 00:31:32 +00:00
api . frame_advance ( ) ;
if ( IsLagFrame )
{
LagCount + + ;
}
Frame + + ;
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
2014-05-13 00:06:33 +00:00
public IVideoProvider VideoProvider { get { return _videoProvider ; } }
2014-05-13 00:31:32 +00:00
2013-11-16 21:29:42 +00:00
public DisplayType DisplayType { get { return _display_type ; } }
2013-05-04 02:46:37 +00:00
2013-05-01 14:38:47 +00:00
public ISoundProvider SoundProvider { get { return null ; } }
2014-05-13 00:31:32 +00:00
2014-05-13 00:06:33 +00:00
public ISyncSoundProvider SyncSoundProvider { get { return _audioProvider . Resampler ; } }
2014-05-13 00:31:32 +00:00
2013-05-01 14:38:47 +00:00
public bool StartAsyncSound ( ) { return false ; }
2014-05-13 00:31:32 +00:00
2013-04-29 01:57:41 +00:00
public void EndAsyncSound ( ) { }
2014-05-13 00:31:32 +00:00
public ControllerDefinition ControllerDefinition
{
get { return _inputProvider . ControllerDefinition ; }
}
2014-01-24 17:46:35 +00:00
public IController Controller
2013-04-29 01:57:41 +00:00
{
2014-05-13 00:06:33 +00:00
get { return _inputProvider . Controller ; }
set { _inputProvider . Controller = value ; }
2014-01-24 17:46:35 +00:00
}
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-05-13 00:06:33 +00:00
public bool IsLagFrame
{
2014-08-03 01:16:11 +00:00
get
{
2014-08-03 13:06:53 +00:00
if ( _settings . UseMupenStyleLag )
2014-08-03 01:16:11 +00:00
{
return ! IsVIFrame ;
}
return ! _inputProvider . LastFrameInputPolled ;
}
2014-08-03 18:13:33 +00:00
internal set
2014-08-03 01:16:11 +00:00
{
2014-08-03 13:06:53 +00:00
if ( _settings . UseMupenStyleLag )
2014-08-03 01:16:11 +00:00
{
IsVIFrame = ! value ;
}
else
{
_inputProvider . LastFrameInputPolled = ! value ;
}
}
2014-01-24 17:46:35 +00:00
}
2014-05-13 00:06:33 +00:00
2014-08-03 00:00:26 +00:00
public bool IsVIFrame
{
get { return _videoProvider . IsVIFrame ; }
2014-08-03 18:13:33 +00:00
internal set { _videoProvider . IsVIFrame = value ; }
2014-08-03 00:00:26 +00:00
}
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
public bool DeterministicEmulation { get { return false ; } }
2013-04-29 01:57:41 +00:00
2014-08-13 17:52:13 +00:00
public byte [ ] CloneSaveRam ( )
2013-05-07 22:37:26 +00:00
{
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
2014-05-13 00:31:32 +00:00
#region Savestates
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 ) ;
2014-05-13 00:06:33 +00:00
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 )
{
2014-05-13 00:06:33 +00:00
var hex = reader . ReadLine ( ) ;
var 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 ) ) ) ;
}
2014-05-13 00:06:33 +00:00
private 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
2014-09-07 00:43:19 +00:00
writer . Write ( bytes_used ) ;
writer . Write ( data , 0 , bytes_used ) ;
2013-05-04 04:07:04 +00:00
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 )
2014-05-13 00:06:33 +00:00
{
2013-11-22 19:34:24 +00:00
throw new InvalidOperationException ( "Unexpected N64 SaveRam size" ) ;
2014-05-13 00:06:33 +00:00
}
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 ( ) ;
2014-09-07 00:43:19 +00:00
if ( ( _syncSettings . DisableExpansionSlot & & length > = 16788288 ) | | ( ! _syncSettings . DisableExpansionSlot & & length < 16788288 ) )
{
throw new SavestateSizeMismatchException ( "Wrong N64 savestate size" ) ;
}
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 ( ) ;
}
2014-05-13 00:06:33 +00:00
private byte [ ] SaveStateBinaryPrivateBuff = new byte [ 0 ] ;
2013-07-30 00:12:24 +00:00
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 ] ;
}
2014-05-13 00:06:33 +00:00
var ms = new MemoryStream ( SaveStateBinaryPrivateBuff ) ;
var bw = new BinaryWriter ( ms ) ;
2013-04-29 01:57:41 +00:00
SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
2013-07-30 00:12:24 +00:00
if ( ms . Length ! = SaveStateBinaryPrivateBuff . Length )
2014-05-13 00:31:32 +00:00
{
2013-07-30 00:12:24 +00:00
throw new Exception ( "Unexpected Length" ) ;
2014-05-13 00:31:32 +00:00
}
2013-07-30 00:12:24 +00:00
2014-05-13 00:06:33 +00:00
return SaveStateBinaryPrivateBuff ;
2013-04-29 01:57:41 +00:00
}
2013-05-06 20:51:28 +00:00
public bool BinarySaveStatesPreferred { get { return true ; } }
2014-05-13 00:31:32 +00:00
#endregion
#region Debugging Hooks
public Dictionary < string , int > GetCpuFlagsAndRegisters ( )
{
//note: the approach this code takes is highly bug-prone
var ret = new Dictionary < string , int > ( ) ;
var 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 + + )
{
var reg = BitConverter . ToInt64 ( data , i * 8 ) ;
ret . Add ( "REG" + i + "_lo" , ( int ) ( reg ) ) ;
ret . Add ( "REG" + i + "_hi" , ( int ) ( reg > > 32 ) ) ;
}
var PC = BitConverter . ToUInt32 ( data , 32 * 8 ) ;
ret . Add ( "PC" , ( int ) PC ) ;
ret . Add ( "LL" , BitConverter . ToInt32 ( data , 32 * 8 + 4 ) ) ;
var Lo = BitConverter . ToInt64 ( data , 32 * 8 + 4 + 4 ) ;
ret . Add ( "LO_lo" , ( int ) Lo ) ;
ret . Add ( "LO_hi" , ( int ) ( Lo > > 32 ) ) ;
var Hi = BitConverter . ToInt64 ( data , 32 * 8 + 4 + 4 + 8 ) ;
ret . Add ( "HI_lo" , ( int ) Hi ) ;
ret . Add ( "HI_hi" , ( int ) ( Hi > > 32 ) ) ;
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 ) ) ;
for ( int i = 0 ; i < 32 ; i + + )
{
var reg_cop0 = BitConverter . ToUInt32 ( data , 32 * 8 + 4 + 4 + 8 + 8 + 4 + 4 + i * 4 ) ;
ret . Add ( "CP0 REG" + i , ( int ) reg_cop0 ) ;
}
for ( int i = 0 ; i < 32 ; i + + )
{
var reg_cop1_fgr_64 = BitConverter . ToInt64 ( data , 32 * 8 + 4 + 4 + 8 + 8 + 4 + 4 + 32 * 4 + i * 8 ) ;
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 ) ) ;
}
return ret ;
}
2013-11-17 03:42:06 +00:00
2014-05-31 17:03:21 +00:00
public void SetCpuRegister ( string register , int value )
{
throw new NotImplementedException ( ) ;
}
2014-05-13 00:06:33 +00:00
private mupen64plusApi . MemoryCallback readcb ;
private mupen64plusApi . MemoryCallback writecb ;
2013-11-17 03:42:06 +00:00
2014-05-13 00:31:32 +00:00
private void RefreshMemoryCallbacks ( )
2013-11-17 03:42:06 +00:00
{
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
2014-05-13 00:31:32 +00:00
#region Settings
public object GetSettings ( )
2013-05-01 14:38:47 +00:00
{
2014-06-01 12:06:22 +00:00
return _settings . Clone ( ) ;
2014-05-13 00:31:32 +00:00
}
2014-05-13 00:06:33 +00:00
2014-05-13 00:31:32 +00:00
public object GetSyncSettings ( )
{
return _syncSettings . Clone ( ) ;
2013-05-01 14:38:47 +00:00
}
2013-04-29 01:57:41 +00:00
2014-05-13 00:31:32 +00:00
public bool PutSettings ( object o )
{
2014-06-01 12:06:22 +00:00
_settings = ( N64Settings ) o ;
return true ;
2014-05-13 00:31:32 +00:00
}
2013-05-04 01:16:27 +00:00
2014-05-13 00:31:32 +00:00
public bool PutSyncSettings ( object o )
{
2014-06-01 12:06:22 +00:00
_syncSettings = ( N64SyncSettings ) o ;
2014-06-25 02:18:11 +00:00
SetControllerButtons ( ) ;
2014-06-01 12:06:22 +00:00
return true ;
2014-05-13 00:31:32 +00:00
}
2013-12-26 23:04:22 +00:00
2014-06-25 02:18:11 +00:00
private void SetControllerButtons ( )
{
ControllerDefinition . BoolButtons . Clear ( ) ;
ControllerDefinition . FloatControls . Clear ( ) ;
ControllerDefinition . BoolButtons . AddRange ( new [ ]
{
"Reset" ,
"Power"
} ) ;
for ( int i = 0 ; i < 4 ; i + + )
{
if ( _syncSettings . Controllers [ i ] . IsConnected )
{
ControllerDefinition . BoolButtons . AddRange ( new [ ]
{
2014-06-28 12:58:13 +00:00
"P" + ( i + 1 ) + " A Up" ,
"P" + ( i + 1 ) + " A Down" ,
"P" + ( i + 1 ) + " A Left" ,
"P" + ( i + 1 ) + " A Right" ,
"P" + ( i + 1 ) + " DPad U" ,
"P" + ( i + 1 ) + " DPad D" ,
"P" + ( i + 1 ) + " DPad L" ,
"P" + ( i + 1 ) + " DPad R" ,
"P" + ( i + 1 ) + " Start" ,
"P" + ( i + 1 ) + " Z" ,
"P" + ( i + 1 ) + " B" ,
"P" + ( i + 1 ) + " A" ,
"P" + ( i + 1 ) + " C Up" ,
"P" + ( i + 1 ) + " C Down" ,
"P" + ( i + 1 ) + " C Right" ,
"P" + ( i + 1 ) + " C Left" ,
"P" + ( i + 1 ) + " L" ,
"P" + ( i + 1 ) + " R" ,
2014-06-25 02:18:11 +00:00
} ) ;
ControllerDefinition . FloatControls . AddRange ( new [ ]
{
2014-06-25 13:52:38 +00:00
"P" + ( i + 1 ) + " X Axis" ,
"P" + ( i + 1 ) + " Y Axis" ,
2014-06-25 02:18:11 +00:00
} ) ;
}
}
}
2014-05-13 00:31:32 +00:00
#endregion
2013-04-29 01:57:41 +00:00
}
}