2015-07-03 09:11:07 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using BizHawk.Common.BufferExtensions ;
using BizHawk.Emulation.Common ;
using BizHawk.Common ;
using System.Runtime.InteropServices ;
using System.IO ;
using System.ComponentModel ;
namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
{
[ CoreAttributes (
"Genplus-gx" ,
"" ,
isPorted : true ,
isReleased : true ,
portedVersion : "r874" ,
portedUrl : "https://code.google.com/p/genplus-gx/" ,
singleInstance : false
) ]
public class GPGXDynamic : IEmulator , ISyncSoundProvider , IVideoProvider , ISaveRam , IStatable ,
IInputPollable , IDebuggable , ISettable < GPGXDynamic . GPGXSettings , GPGXDynamic . GPGXSyncSettings > , IDriveLight
{
DiscSystem . Disc CD ;
DiscSystem . DiscSectorReader CDReader ;
byte [ ] romfile ;
bool drivelight ;
bool disposed = false ;
LibGPGXDynamic gpgx ;
ElfRunner elf ;
LibGPGXDynamic . load_archive_cb LoadCallback = null ;
LibGPGXDynamic . input_cb InputCallback = null ;
LibGPGXDynamic . InputData input = new LibGPGXDynamic . InputData ( ) ;
public enum ControlType
{
None ,
OnePlayer ,
Normal ,
Xea1p ,
Activator ,
Teamplayer ,
Wayplay ,
Mouse
} ;
//[CoreConstructor("GEN")]
public GPGXDynamic ( CoreComm comm , byte [ ] file , object Settings , object SyncSettings )
: this ( comm , file , null , Settings , SyncSettings )
{
}
public GPGXDynamic ( CoreComm comm , byte [ ] rom , DiscSystem . Disc CD , object Settings , object SyncSettings )
{
ServiceProvider = new BasicServiceProvider ( this ) ;
( ServiceProvider as BasicServiceProvider ) . Register < ITraceable > ( _tracer ) ;
// this can influence some things internally
string romextension = "GEN" ;
// three or six button?
// http://www.sega-16.com/forum/showthread.php?4398-Forgotten-Worlds-giving-you-GAME-OVER-immediately-Fix-inside&highlight=forgotten%20worlds
//hack, don't use
//romfile = File.ReadAllBytes(@"D:\encodes\bizhawksrc\output\SANIC CD\PierSolar (E).bin");
if ( rom ! = null & & rom . Length > 16 * 1024 * 1024 )
{
throw new InvalidOperationException ( "ROM too big! Did you try to load a CD as a ROM?" ) ;
}
elf = new ElfRunner ( Path . Combine ( comm . CoreFileProvider . DllPath ( ) , "gpgx.elf" ) ) ;
try
{
gpgx = new LibGPGXDynamic ( ) ;
elf . PopulateInterface ( gpgx ) ;
_SyncSettings = ( GPGXSyncSettings ) SyncSettings ? ? new GPGXSyncSettings ( ) ;
CoreComm = comm ;
LoadCallback = new LibGPGXDynamic . load_archive_cb ( load_archive ) ;
this . romfile = rom ;
this . CD = CD ;
CDReader = new DiscSystem . DiscSectorReader ( CD ) ;
LibGPGXDynamic . INPUT_SYSTEM system_a = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_NONE ;
LibGPGXDynamic . INPUT_SYSTEM system_b = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_NONE ;
switch ( this . _SyncSettings . ControlType )
{
case ControlType . None :
default :
break ;
case ControlType . Activator :
system_a = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_ACTIVATOR ;
system_b = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_ACTIVATOR ;
break ;
case ControlType . Normal :
system_a = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_MD_GAMEPAD ;
system_b = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_MD_GAMEPAD ;
break ;
case ControlType . OnePlayer :
system_a = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_MD_GAMEPAD ;
break ;
case ControlType . Xea1p :
system_a = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_XE_A1P ;
break ;
case ControlType . Teamplayer :
system_a = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_TEAMPLAYER ;
system_b = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_TEAMPLAYER ;
break ;
case ControlType . Wayplay :
system_a = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_WAYPLAY ;
system_b = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_WAYPLAY ;
break ;
case ControlType . Mouse :
system_a = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_MD_GAMEPAD ;
// seems like mouse in port 1 would be supported, but not both at the same time
system_b = LibGPGXDynamic . INPUT_SYSTEM . SYSTEM_MOUSE ;
break ;
}
if ( ! gpgx . gpgx_init ( romextension , LoadCallback , this . _SyncSettings . UseSixButton , system_a , system_b , this . _SyncSettings . Region ) )
throw new Exception ( "gpgx_init() failed" ) ;
{
int fpsnum = 60 ;
int fpsden = 1 ;
gpgx . gpgx_get_fps ( ref fpsnum , ref fpsden ) ;
CoreComm . VsyncNum = fpsnum ;
CoreComm . VsyncDen = fpsden ;
DisplayType = CoreComm . VsyncRate > 55 ? DisplayType . NTSC : DisplayType . PAL ;
}
// compute state size
{
byte [ ] tmp = new byte [ gpgx . gpgx_state_max_size ( ) ] ;
int size = gpgx . gpgx_state_size ( tmp , tmp . Length ) ;
if ( size < = 0 )
throw new Exception ( "Couldn't Determine GPGX internal state size!" ) ;
savebuff = new byte [ size ] ;
savebuff2 = new byte [ savebuff . Length + 13 ] ;
Console . WriteLine ( "GPGX Internal State Size: {0}" , size ) ;
}
SetControllerDefinition ( ) ;
// pull the default video size from the core
update_video_initial ( ) ;
SetMemoryDomains ( ) ;
InputCallback = new LibGPGXDynamic . input_cb ( input_callback ) ;
gpgx . gpgx_set_input_callback ( InputCallback ) ;
if ( CD ! = null )
DriveLightEnabled = true ;
PutSettings ( ( GPGXSettings ) Settings ? ? new GPGXSettings ( ) ) ;
InitMemCallbacks ( ) ;
KillMemCallbacks ( ) ;
}
catch
{
Dispose ( ) ;
throw ;
}
}
public IEmulatorServiceProvider ServiceProvider { get ; private set ; }
public bool DriveLightEnabled { get ; private set ; }
public bool DriveLightOn { get ; private set ; }
/// <summary>
/// core callback for file loading
/// </summary>
/// <param name="filename">string identifying file to be loaded</param>
/// <param name="buffer">buffer to load file to</param>
/// <param name="maxsize">maximum length buffer can hold</param>
/// <returns>actual size loaded, or 0 on failure</returns>
int load_archive ( string filename , IntPtr buffer , int maxsize )
{
byte [ ] srcdata = null ;
if ( buffer = = IntPtr . Zero )
{
Console . WriteLine ( "Couldn't satisfy firmware request {0} because buffer == NULL" , filename ) ;
return 0 ;
}
if ( filename = = "PRIMARY_ROM" )
{
if ( romfile = = null )
{
Console . WriteLine ( "Couldn't satisfy firmware request PRIMARY_ROM because none was provided." ) ;
return 0 ;
}
srcdata = romfile ;
}
else if ( filename = = "PRIMARY_CD" | | filename = = "SECONDARY_CD" )
{
if ( filename = = "PRIMARY_CD" & & romfile ! = null )
{
Console . WriteLine ( "Declined to satisfy firmware request PRIMARY_CD because PRIMARY_ROM was provided." ) ;
return 0 ;
}
else
{
if ( CD = = null )
{
Console . WriteLine ( "Couldn't satisfy firmware request {0} because none was provided." , filename ) ;
return 0 ;
}
srcdata = GetCDData ( ) ;
if ( srcdata . Length ! = maxsize )
{
Console . WriteLine ( "Couldn't satisfy firmware request {0} because of struct size." , filename ) ;
return 0 ;
}
}
}
else
{
// use fromtend firmware interface
string firmwareID = null ;
switch ( filename )
{
case "CD_BIOS_EU" : firmwareID = "CD_BIOS_EU" ; break ;
case "CD_BIOS_JP" : firmwareID = "CD_BIOS_JP" ; break ;
case "CD_BIOS_US" : firmwareID = "CD_BIOS_US" ; break ;
default :
break ;
}
if ( firmwareID ! = null )
{
// this path will be the most common PEBKAC error, so be a bit more vocal about the problem
srcdata = CoreComm . CoreFileProvider . GetFirmware ( "GEN" , firmwareID , false , "GPGX firmwares are usually required." ) ;
if ( srcdata = = null )
{
Console . WriteLine ( "Frontend couldn't satisfy firmware request GEN:{0}" , firmwareID ) ;
return 0 ;
}
}
else
{
Console . WriteLine ( "Unrecognized firmware request {0}" , filename ) ;
return 0 ;
}
}
if ( srcdata ! = null )
{
if ( srcdata . Length > maxsize )
{
Console . WriteLine ( "Couldn't satisfy firmware request {0} because {1} > {2}" , filename , srcdata . Length , maxsize ) ;
return 0 ;
}
else
{
Marshal . Copy ( srcdata , 0 , buffer , srcdata . Length ) ;
Console . WriteLine ( "Firmware request {0} satisfied at size {1}" , filename , srcdata . Length ) ;
return srcdata . Length ;
}
}
else
{
throw new Exception ( ) ;
//Console.WriteLine("Couldn't satisfy firmware request {0} for unknown reasons", filename);
//return 0;
}
}
void CDRead ( int lba , IntPtr dest , bool audio )
{
if ( audio )
{
byte [ ] data = new byte [ 2352 ] ;
2015-07-08 03:29:11 +00:00
if ( lba < CD . Session1 . LeadoutLBA )
2015-07-03 09:11:07 +00:00
{
CDReader . ReadLBA_2352 ( lba , data , 0 ) ;
}
else
{
// audio seems to read slightly past the end of disks; probably innoculous
// just send back 0s.
// Console.WriteLine("!!{0} >= {1}", lba, CD.LBACount);
}
Marshal . Copy ( data , 0 , dest , 2352 ) ;
}
else
{
byte [ ] data = new byte [ 2048 ] ;
CDReader . ReadLBA_2048 ( lba , data , 0 ) ;
Marshal . Copy ( data , 0 , dest , 2048 ) ;
drivelight = true ;
}
}
LibGPGXDynamic . cd_read_cb cd_callback_handle ;
unsafe byte [ ] GetCDData ( )
{
LibGPGXDynamic . CDData ret = new LibGPGXDynamic . CDData ( ) ;
int size = Marshal . SizeOf ( ret ) ;
ret . readcallback = cd_callback_handle = new LibGPGXDynamic . cd_read_cb ( CDRead ) ;
2015-07-08 03:29:11 +00:00
var ses = CD . Session1 ;
2015-07-03 09:11:07 +00:00
int ntrack = ses . Tracks . Count ;
// bet you a dollar this is all wrong
2015-07-08 03:29:11 +00:00
//zero 07-jul-2015 - throws a dollar in the pile, since he probably messed it up worse
2015-07-03 09:11:07 +00:00
for ( int i = 0 ; i < LibGPGXDynamic . CD_MAX_TRACKS ; i + + )
{
if ( i < ntrack )
{
ret . tracks [ i ] . start = ses . Tracks [ i ] . LBA ;
2015-07-08 03:29:11 +00:00
ret . tracks [ i ] . end = ses . Tracks [ i + 1 ] . LBA ;
2015-07-03 09:11:07 +00:00
if ( i = = ntrack - 1 )
{
ret . end = ret . tracks [ i ] . end ;
ret . last = ntrack ;
}
}
else
{
ret . tracks [ i ] . start = 0 ;
ret . tracks [ i ] . end = 0 ;
}
}
byte [ ] retdata = new byte [ size ] ;
fixed ( byte * p = & retdata [ 0 ] )
{
Marshal . StructureToPtr ( ret , ( IntPtr ) p , false ) ;
}
return retdata ;
}
#region controller
/// <summary>
/// size of native input struct
/// </summary>
int inputsize ;
GPGXControlConverterDynamic ControlConverter ;
public ControllerDefinition ControllerDefinition { get ; private set ; }
public IController Controller { get ; set ; }
void SetControllerDefinition ( )
{
inputsize = Marshal . SizeOf ( typeof ( LibGPGXDynamic . InputData ) ) ;
if ( ! gpgx . gpgx_get_control ( input , inputsize ) )
throw new Exception ( "gpgx_get_control() failed" ) ;
ControlConverter = new GPGXControlConverterDynamic ( input ) ;
ControllerDefinition = ControlConverter . ControllerDef ;
}
public LibGPGXDynamic . INPUT_DEVICE [ ] GetDevices ( )
{
return ( LibGPGXDynamic . INPUT_DEVICE [ ] ) input . dev . Clone ( ) ;
}
// core callback for input
void input_callback ( )
{
InputCallbacks . Call ( ) ;
IsLagFrame = false ;
}
private readonly InputCallbackSystem _inputCallbacks = new InputCallbackSystem ( ) ;
public IInputCallbackSystem InputCallbacks { get { return _inputCallbacks ; } }
private readonly TraceBuffer _tracer = new TraceBuffer ( ) ;
#endregion
// TODO: use render and rendersound
public void FrameAdvance ( bool render , bool rendersound = true )
{
if ( Controller [ "Reset" ] )
gpgx . gpgx_reset ( false ) ;
if ( Controller [ "Power" ] )
gpgx . gpgx_reset ( true ) ;
// do we really have to get each time? nothing has changed
if ( ! gpgx . gpgx_get_control ( input , inputsize ) )
throw new Exception ( "gpgx_get_control() failed!" ) ;
ControlConverter . ScreenWidth = vwidth ;
ControlConverter . ScreenHeight = vheight ;
ControlConverter . Convert ( Controller , input ) ;
if ( ! gpgx . gpgx_put_control ( input , inputsize ) )
throw new Exception ( "gpgx_put_control() failed!" ) ;
IsLagFrame = true ;
Frame + + ;
drivelight = false ;
gpgx . gpgx_advance ( ) ;
update_video ( ) ;
update_audio ( ) ;
if ( IsLagFrame )
LagCount + + ;
if ( CD ! = null )
DriveLightOn = drivelight ;
}
public int Frame { get ; private set ; }
public int LagCount { get ; private set ; }
public bool IsLagFrame { get ; private set ; }
public string SystemId { get { return "GEN" ; } }
public bool DeterministicEmulation { get { return true ; } }
public string BoardName { get { return null ; } }
public CoreComm CoreComm { get ; private set ; }
#region saveram
byte [ ] DisposedSaveRam = null ;
public byte [ ] CloneSaveRam ( )
{
if ( disposed )
{
if ( DisposedSaveRam ! = null )
{
return ( byte [ ] ) DisposedSaveRam . Clone ( ) ;
}
else
{
return new byte [ 0 ] ;
}
}
else
{
int size = 0 ;
IntPtr area = IntPtr . Zero ;
gpgx . gpgx_get_sram ( ref area , ref size ) ;
if ( size < = 0 | | area = = IntPtr . Zero )
return new byte [ 0 ] ;
gpgx . gpgx_sram_prepread ( ) ;
byte [ ] ret = new byte [ size ] ;
Marshal . Copy ( area , ret , 0 , size ) ;
return ret ;
}
}
public void StoreSaveRam ( byte [ ] data )
{
if ( disposed )
{
throw new ObjectDisposedException ( typeof ( GPGX ) . ToString ( ) ) ;
}
else
{
int size = 0 ;
IntPtr area = IntPtr . Zero ;
gpgx . gpgx_get_sram ( ref area , ref size ) ;
if ( size < = 0 | | area = = IntPtr . Zero )
return ;
if ( size ! = data . Length )
throw new Exception ( "Unexpected saveram size" ) ;
Marshal . Copy ( data , 0 , area , size ) ;
gpgx . gpgx_sram_commitwrite ( ) ;
}
}
public bool SaveRamModified
{
get
{
if ( disposed )
{
return DisposedSaveRam ! = null ;
}
else
{
int size = 0 ;
IntPtr area = IntPtr . Zero ;
gpgx . gpgx_get_sram ( ref area , ref size ) ;
return size > 0 & & area ! = IntPtr . Zero ;
}
}
}
#endregion
public void ResetCounters ( )
{
Frame = 0 ;
IsLagFrame = false ;
LagCount = 0 ;
}
#region savestates
private byte [ ] savebuff ;
private byte [ ] savebuff2 ;
public void SaveStateText ( System . IO . TextWriter writer )
{
var temp = SaveStateBinary ( ) ;
temp . SaveAsHexFast ( writer ) ;
// write extra copy of stuff we don't use
writer . WriteLine ( "Frame {0}" , Frame ) ;
}
public void LoadStateText ( System . IO . TextReader reader )
{
string hex = reader . ReadLine ( ) ;
byte [ ] state = new byte [ hex . Length / 2 ] ;
state . ReadFromHexFast ( hex ) ;
LoadStateBinary ( new System . IO . BinaryReader ( new System . IO . MemoryStream ( state ) ) ) ;
}
public void SaveStateBinary ( System . IO . BinaryWriter writer )
{
if ( ! gpgx . gpgx_state_save ( savebuff , savebuff . Length ) )
throw new Exception ( "gpgx_state_save() returned false" ) ;
writer . Write ( savebuff . Length ) ;
writer . Write ( savebuff ) ;
// other variables
writer . Write ( Frame ) ;
writer . Write ( LagCount ) ;
writer . Write ( IsLagFrame ) ;
}
public void LoadStateBinary ( System . IO . BinaryReader reader )
{
int newlen = reader . ReadInt32 ( ) ;
if ( newlen ! = savebuff . Length )
throw new Exception ( "Unexpected state size" ) ;
reader . Read ( savebuff , 0 , savebuff . Length ) ;
if ( ! gpgx . gpgx_state_load ( savebuff , savebuff . Length ) )
throw new Exception ( "gpgx_state_load() returned false" ) ;
// other variables
Frame = reader . ReadInt32 ( ) ;
LagCount = reader . ReadInt32 ( ) ;
IsLagFrame = reader . ReadBoolean ( ) ;
update_video ( ) ;
}
public byte [ ] SaveStateBinary ( )
{
var ms = new System . IO . MemoryStream ( savebuff2 , true ) ;
var bw = new System . IO . BinaryWriter ( ms ) ;
SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
ms . Close ( ) ;
return savebuff2 ;
}
public bool BinarySaveStatesPreferred { get { return true ; } }
#endregion
#region debugging tools
private IMemoryDomains MemoryDomains ;
unsafe void SetMemoryDomains ( )
{
var mm = new List < MemoryDomain > ( ) ;
for ( int i = LibGPGXDynamic . MIN_MEM_DOMAIN ; i < = LibGPGXDynamic . MAX_MEM_DOMAIN ; i + + )
{
IntPtr area = IntPtr . Zero ;
int size = 0 ;
IntPtr pname = gpgx . gpgx_get_memdom ( i , ref area , ref size ) ;
if ( area = = IntPtr . Zero | | pname = = IntPtr . Zero | | size = = 0 )
continue ;
string name = Marshal . PtrToStringAnsi ( pname ) ;
if ( name = = "VRAM" )
{
// vram pokes need to go through hook which invalidates cached tiles
byte * p = ( byte * ) area ;
mm . Add ( new MemoryDomain ( name , size , MemoryDomain . Endian . Unknown ,
delegate ( long addr )
{
if ( addr < 0 | | addr > = 65536 )
throw new ArgumentOutOfRangeException ( ) ;
return p [ addr ^ 1 ] ;
} ,
delegate ( long addr , byte val )
{
if ( addr < 0 | | addr > = 65536 )
throw new ArgumentOutOfRangeException ( ) ;
gpgx . gpgx_poke_vram ( ( ( int ) addr ) ^ 1 , val ) ;
} ,
byteSize : 2 ) ) ;
}
else
{
var byteSize = name . Contains ( "Z80" ) ? 1 : 2 ;
mm . Add ( MemoryDomain . FromIntPtrSwap16 ( name , size , MemoryDomain . Endian . Big , area , writable : true , byteSize : byteSize ) ) ;
}
}
MemoryDomains = new MemoryDomainList ( mm ) ;
( ServiceProvider as BasicServiceProvider ) . Register < IMemoryDomains > ( MemoryDomains ) ;
}
public IDictionary < string , RegisterValue > GetCpuFlagsAndRegisters ( )
{
LibGPGXDynamic . RegisterInfo [ ] regs = new LibGPGXDynamic . RegisterInfo [ gpgx . gpgx_getmaxnumregs ( ) ] ;
int n = gpgx . gpgx_getregs ( regs ) ;
if ( n > regs . Length )
throw new InvalidOperationException ( "A buffer overrun has occured!" ) ;
var ret = new Dictionary < string , RegisterValue > ( ) ;
for ( int i = 0 ; i < n ; i + + )
{
// el hacko
string name = Marshal . PtrToStringAnsi ( regs [ i ] . Name ) ;
byte size = 32 ;
if ( name . Contains ( "68K SR" ) | | name . StartsWith ( "Z80" ) )
size = 16 ;
ret [ Marshal . PtrToStringAnsi ( regs [ i ] . Name ) ] =
new RegisterValue { BitSize = size , Value = ( ulong ) regs [ i ] . Value } ;
}
return ret ;
}
public bool CanStep ( StepType type ) { return false ; }
[FeatureNotImplemented]
public void Step ( StepType type ) { throw new NotImplementedException ( ) ; }
[FeatureNotImplemented]
public void SetCpuRegister ( string register , int value )
{
throw new NotImplementedException ( ) ;
}
public void UpdateVDPViewContext ( LibGPGXDynamic . VDPView view )
{
gpgx . gpgx_get_vdp_view ( view ) ;
gpgx . gpgx_flush_vram ( ) ; // fully regenerate internal caches as needed
}
private readonly MemoryCallbackSystem _memoryCallbacks = new MemoryCallbackSystem ( ) ;
public IMemoryCallbackSystem MemoryCallbacks { get { return _memoryCallbacks ; } }
LibGPGXDynamic . mem_cb ExecCallback ;
LibGPGXDynamic . mem_cb ReadCallback ;
LibGPGXDynamic . mem_cb WriteCallback ;
void InitMemCallbacks ( )
{
ExecCallback = new LibGPGXDynamic . mem_cb ( a = > MemoryCallbacks . CallExecutes ( a ) ) ;
ReadCallback = new LibGPGXDynamic . mem_cb ( a = > MemoryCallbacks . CallReads ( a ) ) ;
WriteCallback = new LibGPGXDynamic . mem_cb ( a = > MemoryCallbacks . CallWrites ( a ) ) ;
_memoryCallbacks . ActiveChanged + = RefreshMemCallbacks ;
}
void RefreshMemCallbacks ( )
{
gpgx . gpgx_set_mem_callback (
MemoryCallbacks . HasReads ? ReadCallback : null ,
MemoryCallbacks . HasWrites ? WriteCallback : null ,
MemoryCallbacks . HasExecutes ? ExecCallback : null ) ;
}
void KillMemCallbacks ( )
{
gpgx . gpgx_set_mem_callback ( null , null , null ) ;
}
#endregion
public void Dispose ( )
{
if ( ! disposed )
{
// if (SaveRamModified)
// DisposedSaveRam = CloneSaveRam();
// KillMemCallbacks();
if ( CD ! = null )
{
CD . Dispose ( ) ;
}
if ( elf ! = null )
{
elf . Dispose ( ) ;
elf = null ;
gpgx = null ;
}
disposed = true ;
}
}
#region SoundProvider
short [ ] samples = new short [ 4096 ] ;
int nsamp = 0 ;
public ISoundProvider SoundProvider { get { return null ; } }
public ISyncSoundProvider SyncSoundProvider { get { return this ; } }
public bool StartAsyncSound ( ) { return false ; }
public void EndAsyncSound ( ) { }
public void GetSamples ( out short [ ] samples , out int nsamp )
{
nsamp = this . nsamp ;
samples = this . samples ;
this . nsamp = 0 ;
}
public void DiscardSamples ( )
{
this . nsamp = 0 ;
}
void update_audio ( )
{
IntPtr src = IntPtr . Zero ;
gpgx . gpgx_get_audio ( ref nsamp , ref src ) ;
if ( src ! = IntPtr . Zero )
{
Marshal . Copy ( src , samples , 0 , nsamp * 2 ) ;
}
}
#endregion
#region VideoProvider
public DisplayType DisplayType { get ; private set ; }
int [ ] vidbuff = new int [ 0 ] ;
int vwidth ;
int vheight ;
public int [ ] GetVideoBuffer ( ) { return vidbuff ; }
public int VirtualWidth { get { return BufferWidth ; } } // TODO
public int VirtualHeight { get { return BufferHeight ; } } // TODO
public int BufferWidth { get { return vwidth ; } }
public int BufferHeight { get { return vheight ; } }
public int BackgroundColor { get { return unchecked ( ( int ) 0xff000000 ) ; } }
void update_video_initial ( )
{
// hack: you should call update_video() here, but that gives you 256x192 on frame 0
// and we know that we only use GPGX to emulate genesis games that will always be 320x224 immediately afterwards
// so instead, just assume a 320x224 size now; if that happens to be wrong, it'll be fixed soon enough.
vwidth = 320 ;
vheight = 224 ;
vidbuff = new int [ vwidth * vheight ] ;
for ( int i = 0 ; i < vidbuff . Length ; i + + )
vidbuff [ i ] = unchecked ( ( int ) 0xff000000 ) ;
}
unsafe void update_video ( )
{
int pitch = 0 ;
IntPtr src = IntPtr . Zero ;
gpgx . gpgx_get_video ( ref vwidth , ref vheight , ref pitch , ref src ) ;
if ( vidbuff . Length < vwidth * vheight )
vidbuff = new int [ vwidth * vheight ] ;
int rinc = ( pitch / 4 ) - vwidth ;
fixed ( int * pdst_ = & vidbuff [ 0 ] )
{
int * pdst = pdst_ ;
int * psrc = ( int * ) src ;
for ( int j = 0 ; j < vheight ; j + + )
{
for ( int i = 0 ; i < vwidth ; i + + )
* pdst + + = * psrc + + ; // | unchecked((int)0xff000000);
psrc + = rinc ;
}
}
}
#endregion
#region Settings
GPGXSyncSettings _SyncSettings ;
GPGXSettings _Settings ;
public GPGXSettings GetSettings ( ) { return _Settings . Clone ( ) ; }
public GPGXSyncSettings GetSyncSettings ( ) { return _SyncSettings . Clone ( ) ; }
public bool PutSettings ( GPGXSettings o )
{
_Settings = o ;
gpgx . gpgx_set_draw_mask ( _Settings . GetDrawMask ( ) ) ;
return false ;
}
public bool PutSyncSettings ( GPGXSyncSettings o )
{
bool ret = GPGXSyncSettings . NeedsReboot ( _SyncSettings , o ) ;
_SyncSettings = o ;
return ret ;
}
public class GPGXSettings
{
[DisplayName("Background Layer A")]
[Description("True to draw BG layer A")]
[DefaultValue(true)]
public bool DrawBGA { get ; set ; }
[DisplayName("Background Layer B")]
[Description("True to draw BG layer B")]
[DefaultValue(true)]
public bool DrawBGB { get ; set ; }
[DisplayName("Background Layer W")]
[Description("True to draw BG layer W")]
[DefaultValue(true)]
public bool DrawBGW { get ; set ; }
public GPGXSettings ( )
{
SettingsUtil . SetDefaultValues ( this ) ;
}
public GPGXSettings Clone ( )
{
return ( GPGXSettings ) MemberwiseClone ( ) ;
}
public LibGPGXDynamic . DrawMask GetDrawMask ( )
{
LibGPGXDynamic . DrawMask ret = 0 ;
if ( DrawBGA ) ret | = LibGPGXDynamic . DrawMask . BGA ;
if ( DrawBGB ) ret | = LibGPGXDynamic . DrawMask . BGB ;
if ( DrawBGW ) ret | = LibGPGXDynamic . DrawMask . BGW ;
return ret ;
}
}
public class GPGXSyncSettings
{
[DisplayName("Use Six Button Controllers")]
[Description("Controls the type of any attached normal controllers; six button controllers are used if true, otherwise three button controllers. Some games don't work correctly with six button controllers. Not relevant if other controller types are connected.")]
[DefaultValue(true)]
public bool UseSixButton { get ; set ; }
[DisplayName("Control Type")]
[Description("Sets the type of controls that are plugged into the console. Some games will automatically load with a different control type.")]
[DefaultValue(ControlType.Normal)]
public ControlType ControlType { get ; set ; }
[DisplayName("Autodetect Region")]
[Description("Sets the region of the emulated console. Many games can run on multiple regions and will behave differently on different ones. Some games may require a particular region.")]
[DefaultValue(LibGPGXDynamic.Region.Autodetect)]
public LibGPGXDynamic . Region Region { get ; set ; }
public GPGXSyncSettings ( )
{
SettingsUtil . SetDefaultValues ( this ) ;
}
public GPGXSyncSettings Clone ( )
{
return ( GPGXSyncSettings ) MemberwiseClone ( ) ;
}
public static bool NeedsReboot ( GPGXSyncSettings x , GPGXSyncSettings y )
{
return ! DeepEquality . DeepEquals ( x , y ) ;
}
}
#endregion
}
}