2013-12-15 20:51:57 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
2014-07-03 18:54:53 +00:00
using BizHawk.Common.BufferExtensions ;
2013-12-15 20:51:57 +00:00
using BizHawk.Emulation.Common ;
2014-07-11 18:51:26 +00:00
using BizHawk.Common ;
2013-12-15 20:51:57 +00:00
using System.Runtime.InteropServices ;
2013-12-16 21:23:32 +00:00
using System.IO ;
2013-12-23 23:03:12 +00:00
using System.ComponentModel ;
2013-12-15 20:51:57 +00:00
namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
{
2014-04-25 01:19:57 +00:00
[ CoreAttributes (
"Genplus-gx" ,
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 ,
2014-07-11 18:55:43 +00:00
portedVersion : "r874" ,
2015-03-03 00:23:50 +00:00
portedUrl : "https://code.google.com/p/genplus-gx/" ,
singleInstance : true
2014-04-25 01:19:57 +00:00
) ]
2015-01-14 21:55:48 +00:00
public class GPGX : IEmulator , ISyncSoundProvider , IVideoProvider , ISaveRam , IStatable ,
2014-12-12 01:49:54 +00:00
IInputPollable , IDebuggable , ISettable < GPGX . GPGXSettings , GPGX . GPGXSyncSettings > , IDriveLight
2013-12-15 20:51:57 +00:00
{
static GPGX AttachedCore = null ;
2013-12-16 18:04:45 +00:00
DiscSystem . Disc CD ;
2013-12-15 20:51:57 +00:00
byte [ ] romfile ;
2013-12-30 20:36:51 +00:00
bool drivelight ;
2013-12-15 20:51:57 +00:00
bool disposed = false ;
LibGPGX . load_archive_cb LoadCallback = null ;
2013-12-21 17:49:32 +00:00
LibGPGX . input_cb InputCallback = null ;
2013-12-15 20:51:57 +00:00
2013-12-16 01:58:40 +00:00
LibGPGX . InputData input = new LibGPGX . InputData ( ) ;
public enum ControlType
{
None ,
OnePlayer ,
Normal ,
Xea1p ,
Activator ,
Teamplayer ,
2014-06-27 02:22:23 +00:00
Wayplay ,
Mouse
2013-12-16 01:58:40 +00:00
} ;
2015-06-25 19:14:42 +00:00
[CoreConstructor("GEN")]
2014-09-12 15:39:04 +00:00
public GPGX ( CoreComm comm , byte [ ] file , object Settings , object SyncSettings )
: this ( comm , file , null , Settings , SyncSettings )
2014-08-23 19:16:47 +00:00
{
}
2014-08-23 19:06:37 +00:00
public GPGX ( CoreComm comm , byte [ ] rom , DiscSystem . Disc CD , object Settings , object SyncSettings )
2013-12-15 20:51:57 +00:00
{
2014-12-04 03:38:30 +00:00
ServiceProvider = new BasicServiceProvider ( this ) ;
2014-08-23 19:06:37 +00:00
// this can influence some things internally
string romextension = "GEN" ;
2013-12-16 01:58:40 +00:00
// 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
2014-02-04 22:29:40 +00:00
//hack, don't use
//romfile = File.ReadAllBytes(@"D:\encodes\bizhawksrc\output\SANIC CD\PierSolar (E).bin");
2014-08-23 19:06:37 +00:00
if ( rom ! = null & & rom . Length > 16 * 1024 * 1024 )
2014-03-05 18:16:34 +00:00
{
throw new InvalidOperationException ( "ROM too big! Did you try to load a CD as a ROM?" ) ;
}
2014-02-04 22:29:40 +00:00
2013-12-15 20:51:57 +00:00
try
{
2014-07-11 18:51:26 +00:00
_SyncSettings = ( GPGXSyncSettings ) SyncSettings ? ? new GPGXSyncSettings ( ) ;
2013-12-23 23:03:12 +00:00
2014-08-23 19:06:37 +00:00
CoreComm = comm ;
2013-12-15 20:51:57 +00:00
if ( AttachedCore ! = null )
{
AttachedCore . Dispose ( ) ;
AttachedCore = null ;
}
AttachedCore = this ;
LoadCallback = new LibGPGX . load_archive_cb ( load_archive ) ;
2014-08-23 19:06:37 +00:00
this . romfile = rom ;
2013-12-16 19:00:05 +00:00
this . CD = CD ;
2013-12-15 20:51:57 +00:00
2013-12-16 01:58:40 +00:00
LibGPGX . INPUT_SYSTEM system_a = LibGPGX . INPUT_SYSTEM . SYSTEM_NONE ;
LibGPGX . INPUT_SYSTEM system_b = LibGPGX . INPUT_SYSTEM . SYSTEM_NONE ;
2014-06-27 02:22:23 +00:00
switch ( this . _SyncSettings . ControlType )
2013-12-16 01:58:40 +00:00
{
case ControlType . None :
default :
break ;
case ControlType . Activator :
system_a = LibGPGX . INPUT_SYSTEM . SYSTEM_ACTIVATOR ;
system_b = LibGPGX . INPUT_SYSTEM . SYSTEM_ACTIVATOR ;
break ;
case ControlType . Normal :
system_a = LibGPGX . INPUT_SYSTEM . SYSTEM_MD_GAMEPAD ;
system_b = LibGPGX . INPUT_SYSTEM . SYSTEM_MD_GAMEPAD ;
break ;
case ControlType . OnePlayer :
system_a = LibGPGX . INPUT_SYSTEM . SYSTEM_MD_GAMEPAD ;
break ;
case ControlType . Xea1p :
system_a = LibGPGX . INPUT_SYSTEM . SYSTEM_XE_A1P ;
break ;
case ControlType . Teamplayer :
system_a = LibGPGX . INPUT_SYSTEM . SYSTEM_TEAMPLAYER ;
system_b = LibGPGX . INPUT_SYSTEM . SYSTEM_TEAMPLAYER ;
break ;
case ControlType . Wayplay :
system_a = LibGPGX . INPUT_SYSTEM . SYSTEM_WAYPLAY ;
system_b = LibGPGX . INPUT_SYSTEM . SYSTEM_WAYPLAY ;
break ;
2014-06-27 02:22:23 +00:00
case ControlType . Mouse :
system_a = LibGPGX . INPUT_SYSTEM . SYSTEM_MD_GAMEPAD ;
2014-06-27 02:39:24 +00:00
// seems like mouse in port 1 would be supported, but not both at the same time
2014-06-27 02:22:23 +00:00
system_b = LibGPGX . INPUT_SYSTEM . SYSTEM_MOUSE ;
break ;
2013-12-16 01:58:40 +00:00
}
2014-06-27 02:22:23 +00:00
if ( ! LibGPGX . gpgx_init ( romextension , LoadCallback , this . _SyncSettings . UseSixButton , system_a , system_b , this . _SyncSettings . Region ) )
2013-12-15 20:51:57 +00:00
throw new Exception ( "gpgx_init() failed" ) ;
2013-12-16 01:58:40 +00:00
{
int fpsnum = 60 ;
int fpsden = 1 ;
LibGPGX . gpgx_get_fps ( ref fpsnum , ref fpsden ) ;
CoreComm . VsyncNum = fpsnum ;
CoreComm . VsyncDen = fpsden ;
2014-02-21 00:30:52 +00:00
DisplayType = CoreComm . VsyncRate > 55 ? DisplayType . NTSC : DisplayType . PAL ;
2013-12-16 01:58:40 +00:00
}
2014-02-03 18:07:21 +00:00
// compute state size
{
byte [ ] tmp = new byte [ LibGPGX . gpgx_state_max_size ( ) ] ;
int size = LibGPGX . 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 ) ;
}
2013-12-16 01:58:40 +00:00
SetControllerDefinition ( ) ;
2013-12-18 02:16:17 +00:00
// pull the default video size from the core
2014-09-22 19:35:00 +00:00
update_video_initial ( ) ;
2013-12-19 03:33:53 +00:00
SetMemoryDomains ( ) ;
2013-12-21 17:49:32 +00:00
InputCallback = new LibGPGX . input_cb ( input_callback ) ;
LibGPGX . gpgx_set_input_callback ( InputCallback ) ;
2013-12-30 20:36:51 +00:00
if ( CD ! = null )
2014-12-12 01:49:54 +00:00
DriveLightEnabled = true ;
2014-06-21 17:20:18 +00:00
2014-10-19 01:22:47 +00:00
PutSettings ( ( GPGXSettings ) Settings ? ? new GPGXSettings ( ) ) ;
2014-07-11 18:51:26 +00:00
2014-06-21 17:20:18 +00:00
InitMemCallbacks ( ) ;
KillMemCallbacks ( ) ;
2013-12-15 20:51:57 +00:00
}
catch
{
Dispose ( ) ;
throw ;
}
}
2014-12-04 03:38:30 +00:00
public IEmulatorServiceProvider ServiceProvider { get ; private set ; }
2014-12-12 01:49:54 +00:00
public bool DriveLightEnabled { get ; private set ; }
public bool DriveLightOn { get ; private set ; }
2013-12-15 20:51:57 +00:00
/// <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" )
2013-12-16 18:04:45 +00:00
{
if ( romfile = = null )
{
Console . WriteLine ( "Couldn't satisfy firmware request PRIMARY_ROM because none was provided." ) ;
return 0 ;
}
2013-12-15 20:51:57 +00:00
srcdata = romfile ;
2013-12-16 18:04:45 +00:00
}
2014-02-04 22:29:40 +00:00
else if ( filename = = "PRIMARY_CD" | | filename = = "SECONDARY_CD" )
2013-12-16 18:04:45 +00:00
{
2014-02-04 22:29:40 +00:00
if ( filename = = "PRIMARY_CD" & & romfile ! = null )
2013-12-16 18:04:45 +00:00
{
2014-02-04 22:29:40 +00:00
Console . WriteLine ( "Declined to satisfy firmware request PRIMARY_CD because PRIMARY_ROM was provided." ) ;
2013-12-16 18:04:45 +00:00
return 0 ;
}
2014-02-04 22:29:40 +00:00
else
2013-12-16 18:20:47 +00:00
{
2014-02-04 22:29:40 +00:00
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 ;
}
2013-12-16 18:20:47 +00:00
}
2013-12-16 18:04:45 +00:00
}
2013-12-15 20:51:57 +00:00
else
{
2013-12-16 03:57:54 +00:00
// 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 )
{
2014-05-28 03:35:21 +00:00
// 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." ) ;
2013-12-16 03:57:54 +00:00
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 ;
}
2013-12-15 20:51:57 +00:00
}
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
{
2013-12-16 18:04:45 +00:00
throw new Exception ( ) ;
//Console.WriteLine("Couldn't satisfy firmware request {0} for unknown reasons", filename);
//return 0;
}
}
2013-12-20 00:51:48 +00:00
void CDRead ( int lba , IntPtr dest , bool audio )
2013-12-16 18:04:45 +00:00
{
2013-12-20 00:51:48 +00:00
if ( audio )
{
byte [ ] data = new byte [ 2352 ] ;
2014-10-07 22:20:17 +00:00
if ( lba < CD . LBACount )
{
CD . 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);
}
2013-12-20 00:51:48 +00:00
Marshal . Copy ( data , 0 , dest , 2352 ) ;
}
else
{
byte [ ] data = new byte [ 2048 ] ;
CD . ReadLBA_2048 ( lba , data , 0 ) ;
Marshal . Copy ( data , 0 , dest , 2048 ) ;
2013-12-30 20:36:51 +00:00
drivelight = true ;
2013-12-20 00:51:48 +00:00
}
2013-12-16 18:04:45 +00:00
}
LibGPGX . cd_read_cb cd_callback_handle ;
unsafe byte [ ] GetCDData ( )
{
LibGPGX . CDData ret = new LibGPGX . CDData ( ) ;
int size = Marshal . SizeOf ( ret ) ;
ret . readcallback = cd_callback_handle = new LibGPGX . cd_read_cb ( CDRead ) ;
2014-12-04 05:40:10 +00:00
var ses = CD . Structure . Sessions [ 0 ] ;
2013-12-16 18:04:45 +00:00
int ntrack = ses . Tracks . Count ;
2014-02-21 00:30:52 +00:00
2013-12-16 18:04:45 +00:00
// bet you a dollar this is all wrong
for ( int i = 0 ; i < LibGPGX . CD_MAX_TRACKS ; i + + )
{
if ( i < ntrack )
{
ret . tracks [ i ] . start = ses . Tracks [ i ] . Indexes [ 1 ] . aba - 150 ;
2014-12-04 05:40:10 +00:00
ret . tracks [ i ] . end = ses . Tracks [ i ] . LengthInSectors + ret . tracks [ i ] . start ;
2013-12-16 18:04:45 +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 ;
}
2013-12-15 20:51:57 +00:00
}
2013-12-16 18:04:45 +00:00
byte [ ] retdata = new byte [ size ] ;
fixed ( byte * p = & retdata [ 0 ] )
{
Marshal . StructureToPtr ( ret , ( IntPtr ) p , false ) ;
}
return retdata ;
2013-12-15 20:51:57 +00:00
}
2013-12-16 18:04:45 +00:00
2013-12-15 20:51:57 +00:00
#region controller
2014-02-02 02:05:36 +00:00
/// <summary>
/// size of native input struct
/// </summary>
int inputsize ;
2013-12-16 01:58:40 +00:00
GPGXControlConverter ControlConverter ;
public ControllerDefinition ControllerDefinition { get ; private set ; }
2013-12-15 20:51:57 +00:00
public IController Controller { get ; set ; }
2013-12-16 01:58:40 +00:00
void SetControllerDefinition ( )
{
2014-02-02 02:05:36 +00:00
inputsize = Marshal . SizeOf ( typeof ( LibGPGX . InputData ) ) ;
if ( ! LibGPGX . gpgx_get_control ( input , inputsize ) )
2013-12-16 01:58:40 +00:00
throw new Exception ( "gpgx_get_control() failed" ) ;
ControlConverter = new GPGXControlConverter ( input ) ;
ControllerDefinition = ControlConverter . ControllerDef ;
}
2014-06-26 21:31:38 +00:00
2014-06-26 21:33:01 +00:00
public LibGPGX . INPUT_DEVICE [ ] GetDevices ( )
2014-06-26 21:31:38 +00:00
{
2014-06-26 21:33:01 +00:00
return ( LibGPGX . INPUT_DEVICE [ ] ) input . dev . Clone ( ) ;
2014-06-26 21:31:38 +00:00
}
2013-12-16 01:58:40 +00:00
2013-12-21 17:49:32 +00:00
// core callback for input
void input_callback ( )
{
2014-12-04 00:43:12 +00:00
InputCallbacks . Call ( ) ;
2013-12-21 17:49:32 +00:00
IsLagFrame = false ;
}
2014-12-04 00:43:12 +00:00
private readonly InputCallbackSystem _inputCallbacks = new InputCallbackSystem ( ) ;
2014-12-04 03:31:26 +00:00
public IInputCallbackSystem InputCallbacks { get { return _inputCallbacks ; } }
2014-12-04 00:43:12 +00:00
2013-12-15 20:51:57 +00:00
#endregion
2013-12-16 01:58:40 +00:00
// TODO: use render and rendersound
2013-12-15 20:51:57 +00:00
public void FrameAdvance ( bool render , bool rendersound = true )
{
2013-12-20 19:32:12 +00:00
if ( Controller [ "Reset" ] )
LibGPGX . gpgx_reset ( false ) ;
if ( Controller [ "Power" ] )
LibGPGX . gpgx_reset ( true ) ;
2013-12-16 01:58:40 +00:00
// do we really have to get each time? nothing has changed
2014-02-02 02:05:36 +00:00
if ( ! LibGPGX . gpgx_get_control ( input , inputsize ) )
2013-12-16 01:58:40 +00:00
throw new Exception ( "gpgx_get_control() failed!" ) ;
2014-06-27 02:55:14 +00:00
ControlConverter . ScreenWidth = vwidth ;
ControlConverter . ScreenHeight = vheight ;
2013-12-16 01:58:40 +00:00
ControlConverter . Convert ( Controller , input ) ;
2014-02-02 02:05:36 +00:00
if ( ! LibGPGX . gpgx_put_control ( input , inputsize ) )
2013-12-16 01:58:40 +00:00
throw new Exception ( "gpgx_put_control() failed!" ) ;
2013-12-15 20:51:57 +00:00
IsLagFrame = true ;
Frame + + ;
2013-12-30 20:36:51 +00:00
drivelight = false ;
2014-06-21 17:20:18 +00:00
2013-12-15 20:51:57 +00:00
LibGPGX . gpgx_advance ( ) ;
update_video ( ) ;
update_audio ( ) ;
2013-12-16 01:58:40 +00:00
if ( IsLagFrame )
LagCount + + ;
2013-12-30 20:36:51 +00:00
if ( CD ! = null )
2014-12-12 01:49:54 +00:00
DriveLightOn = drivelight ;
2013-12-15 20:51:57 +00:00
}
public int Frame { get ; private set ; }
2015-06-11 01:23:14 +00:00
public int LagCount { get ; private set ; }
2013-12-15 20:51:57 +00:00
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
2013-12-18 02:12:21 +00:00
byte [ ] DisposedSaveRam = null ;
2014-08-13 17:52:13 +00:00
public byte [ ] CloneSaveRam ( )
2013-12-15 20:51:57 +00:00
{
2013-12-18 02:12:21 +00:00
if ( disposed )
{
2014-08-13 17:52:13 +00:00
if ( DisposedSaveRam ! = null )
{
return ( byte [ ] ) DisposedSaveRam . Clone ( ) ;
}
else
{
return new byte [ 0 ] ;
}
2013-12-18 02:12:21 +00:00
}
else
{
int size = 0 ;
IntPtr area = IntPtr . Zero ;
LibGPGX . gpgx_get_sram ( ref area , ref size ) ;
if ( size < = 0 | | area = = IntPtr . Zero )
return new byte [ 0 ] ;
LibGPGX . gpgx_sram_prepread ( ) ;
byte [ ] ret = new byte [ size ] ;
Marshal . Copy ( area , ret , 0 , size ) ;
return ret ;
}
2013-12-15 20:51:57 +00:00
}
public void StoreSaveRam ( byte [ ] data )
{
2013-12-18 02:12:21 +00:00
if ( disposed )
{
throw new ObjectDisposedException ( typeof ( GPGX ) . ToString ( ) ) ;
}
else
{
int size = 0 ;
IntPtr area = IntPtr . Zero ;
LibGPGX . 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 ) ;
LibGPGX . gpgx_sram_commitwrite ( ) ;
}
2013-12-15 20:51:57 +00:00
}
public bool SaveRamModified
{
get
{
2013-12-18 02:12:21 +00:00
if ( disposed )
{
2013-12-18 02:19:00 +00:00
return DisposedSaveRam ! = null ;
2013-12-18 02:12:21 +00:00
}
else
{
int size = 0 ;
IntPtr area = IntPtr . Zero ;
LibGPGX . gpgx_get_sram ( ref area , ref size ) ;
return size > 0 & & area ! = IntPtr . Zero ;
}
2013-12-15 20:51:57 +00:00
}
}
#endregion
public void ResetCounters ( )
{
Frame = 0 ;
IsLagFrame = false ;
LagCount = 0 ;
}
#region savestates
2013-12-16 01:58:40 +00:00
private byte [ ] savebuff ;
private byte [ ] savebuff2 ;
2013-12-15 20:51:57 +00:00
public void SaveStateText ( System . IO . TextWriter writer )
{
2013-12-16 03:39:47 +00:00
var temp = SaveStateBinary ( ) ;
temp . SaveAsHexFast ( writer ) ;
// write extra copy of stuff we don't use
writer . WriteLine ( "Frame {0}" , Frame ) ;
2013-12-15 20:51:57 +00:00
}
public void LoadStateText ( System . IO . TextReader reader )
{
2013-12-16 03:39:47 +00:00
string hex = reader . ReadLine ( ) ;
byte [ ] state = new byte [ hex . Length / 2 ] ;
state . ReadFromHexFast ( hex ) ;
LoadStateBinary ( new System . IO . BinaryReader ( new System . IO . MemoryStream ( state ) ) ) ;
2013-12-15 20:51:57 +00:00
}
public void SaveStateBinary ( System . IO . BinaryWriter writer )
{
2013-12-16 01:58:40 +00:00
if ( ! LibGPGX . 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 ) ;
2013-12-15 20:51:57 +00:00
}
public void LoadStateBinary ( System . IO . BinaryReader reader )
{
2013-12-16 01:58:40 +00:00
int newlen = reader . ReadInt32 ( ) ;
if ( newlen ! = savebuff . Length )
throw new Exception ( "Unexpected state size" ) ;
reader . Read ( savebuff , 0 , savebuff . Length ) ;
if ( ! LibGPGX . 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 ( ) ;
2013-12-20 21:21:21 +00:00
update_video ( ) ;
2013-12-15 20:51:57 +00:00
}
public byte [ ] SaveStateBinary ( )
{
2013-12-16 01:58:40 +00:00
var ms = new System . IO . MemoryStream ( savebuff2 , true ) ;
var bw = new System . IO . BinaryWriter ( ms ) ;
SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
ms . Close ( ) ;
return savebuff2 ;
2013-12-15 20:51:57 +00:00
}
public bool BinarySaveStatesPreferred { get { return true ; } }
#endregion
2013-12-21 17:49:32 +00:00
#region debugging tools
2015-01-14 21:55:48 +00:00
private IMemoryDomains MemoryDomains ;
2013-12-15 20:51:57 +00:00
2013-12-19 03:33:53 +00:00
unsafe void SetMemoryDomains ( )
{
var mm = new List < MemoryDomain > ( ) ;
for ( int i = LibGPGX . MIN_MEM_DOMAIN ; i < = LibGPGX . MAX_MEM_DOMAIN ; i + + )
{
IntPtr area = IntPtr . Zero ;
int size = 0 ;
IntPtr pname = LibGPGX . gpgx_get_memdom ( i , ref area , ref size ) ;
if ( area = = IntPtr . Zero | | pname = = IntPtr . Zero | | size = = 0 )
continue ;
string name = Marshal . PtrToStringAnsi ( pname ) ;
2014-09-19 23:56:08 +00:00
if ( name = = "VRAM" )
{
2015-01-21 17:36:22 +00:00
// vram pokes need to go through hook which invalidates cached tiles
2014-09-19 23:56:08 +00:00
byte * p = ( byte * ) area ;
mm . Add ( new MemoryDomain ( name , size , MemoryDomain . Endian . Unknown ,
2015-01-18 15:25:47 +00:00
delegate ( long addr )
2014-09-19 23:56:08 +00:00
{
if ( addr < 0 | | addr > = 65536 )
throw new ArgumentOutOfRangeException ( ) ;
2014-09-22 14:24:11 +00:00
return p [ addr ^ 1 ] ;
2014-09-19 23:56:08 +00:00
} ,
2015-01-18 15:25:47 +00:00
delegate ( long addr , byte val )
2014-09-19 23:56:08 +00:00
{
if ( addr < 0 | | addr > = 65536 )
throw new ArgumentOutOfRangeException ( ) ;
2015-01-18 15:25:47 +00:00
LibGPGX . gpgx_poke_vram ( ( ( int ) addr ) ^ 1 , val ) ;
2015-02-22 15:19:38 +00:00
} ,
byteSize : 2 ) ) ;
2014-09-19 23:56:08 +00:00
}
2015-02-22 15:19:38 +00:00
2014-09-19 23:56:08 +00:00
else
{
2015-02-22 15:19:38 +00:00
var byteSize = name . Contains ( "Z80" ) ? 1 : 2 ;
mm . Add ( MemoryDomain . FromIntPtrSwap16 ( name , size , MemoryDomain . Endian . Big , area , writable : true , byteSize : byteSize ) ) ;
2014-09-19 23:56:08 +00:00
}
2013-12-19 03:33:53 +00:00
}
2015-01-24 16:02:28 +00:00
MemoryDomains = new MemoryDomainList ( mm ) ;
2015-01-14 21:55:48 +00:00
( ServiceProvider as BasicServiceProvider ) . Register < IMemoryDomains > ( MemoryDomains ) ;
2013-12-19 03:33:53 +00:00
}
2014-12-20 13:16:15 +00:00
public IDictionary < string , RegisterValue > GetCpuFlagsAndRegisters ( )
2013-12-15 20:51:57 +00:00
{
2014-05-04 17:41:20 +00:00
LibGPGX . RegisterInfo [ ] regs = new LibGPGX . RegisterInfo [ LibGPGX . gpgx_getmaxnumregs ( ) ] ;
int n = LibGPGX . gpgx_getregs ( regs ) ;
if ( n > regs . Length )
throw new InvalidOperationException ( "A buffer overrun has occured!" ) ;
2014-12-20 13:16:15 +00:00
var ret = new Dictionary < string , RegisterValue > ( ) ;
2014-05-04 17:41:20 +00:00
for ( int i = 0 ; i < n ; i + + )
2014-12-20 03:48:01 +00:00
{
// 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 ) ] =
2014-12-20 13:16:15 +00:00
new RegisterValue { BitSize = size , Value = ( ulong ) regs [ i ] . Value } ;
2014-12-20 03:48:01 +00:00
}
2014-05-04 17:41:20 +00:00
return ret ;
2013-12-15 20:51:57 +00:00
}
2014-12-20 13:29:57 +00:00
public bool CanStep ( StepType type ) { return false ; }
2014-12-14 18:58:16 +00:00
[FeatureNotImplemented]
2014-12-15 22:19:10 +00:00
public void Step ( StepType type ) { throw new NotImplementedException ( ) ; }
2014-12-14 18:58:16 +00:00
2014-11-24 01:17:05 +00:00
[FeatureNotImplemented]
2014-05-31 17:03:21 +00:00
public void SetCpuRegister ( string register , int value )
{
throw new NotImplementedException ( ) ;
}
2014-02-19 22:26:33 +00:00
public void UpdateVDPViewContext ( LibGPGX . VDPView view )
{
LibGPGX . gpgx_get_vdp_view ( view ) ;
2014-09-19 23:56:08 +00:00
LibGPGX . gpgx_flush_vram ( ) ; // fully regenerate internal caches as needed
2014-02-19 22:26:33 +00:00
}
2014-12-05 02:31:36 +00:00
private readonly MemoryCallbackSystem _memoryCallbacks = new MemoryCallbackSystem ( ) ;
public IMemoryCallbackSystem MemoryCallbacks { get { return _memoryCallbacks ; } }
2014-06-21 17:20:18 +00:00
LibGPGX . mem_cb ExecCallback ;
LibGPGX . mem_cb ReadCallback ;
LibGPGX . mem_cb WriteCallback ;
void InitMemCallbacks ( )
{
2014-12-07 18:53:56 +00:00
ExecCallback = new LibGPGX . mem_cb ( a = > MemoryCallbacks . CallExecutes ( a ) ) ;
ReadCallback = new LibGPGX . mem_cb ( a = > MemoryCallbacks . CallReads ( a ) ) ;
WriteCallback = new LibGPGX . mem_cb ( a = > MemoryCallbacks . CallWrites ( a ) ) ;
2014-12-05 02:31:36 +00:00
_memoryCallbacks . ActiveChanged + = RefreshMemCallbacks ;
2014-06-21 17:20:18 +00:00
}
void RefreshMemCallbacks ( )
{
LibGPGX . gpgx_set_mem_callback (
2014-12-05 01:56:45 +00:00
MemoryCallbacks . HasReads ? ReadCallback : null ,
MemoryCallbacks . HasWrites ? WriteCallback : null ,
MemoryCallbacks . HasExecutes ? ExecCallback : null ) ;
2014-06-21 17:20:18 +00:00
}
void KillMemCallbacks ( )
{
LibGPGX . gpgx_set_mem_callback ( null , null , null ) ;
}
2013-12-21 17:49:32 +00:00
#endregion
2013-12-15 20:51:57 +00:00
public void Dispose ( )
{
if ( ! disposed )
{
if ( AttachedCore ! = this )
throw new Exception ( ) ;
2013-12-18 02:12:21 +00:00
if ( SaveRamModified )
2014-08-13 17:52:13 +00:00
DisposedSaveRam = CloneSaveRam ( ) ;
2014-06-21 17:20:18 +00:00
KillMemCallbacks ( ) ;
2014-12-19 21:53:43 +00:00
if ( CD ! = null )
{
CD . Dispose ( ) ;
}
2013-12-15 20:51:57 +00:00
AttachedCore = null ;
2013-12-16 03:29:41 +00:00
disposed = true ;
2013-12-15 20:51:57 +00:00
}
}
#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 ;
LibGPGX . gpgx_get_audio ( ref nsamp , ref src ) ;
if ( src ! = IntPtr . Zero )
{
Marshal . Copy ( src , samples , 0 , nsamp * 2 ) ;
}
}
#endregion
#region VideoProvider
2014-02-21 00:30:52 +00:00
public DisplayType DisplayType { get ; private set ; }
2013-12-15 20:51:57 +00:00
int [ ] vidbuff = new int [ 0 ] ;
int vwidth ;
int vheight ;
public int [ ] GetVideoBuffer ( ) { return vidbuff ; }
public int VirtualWidth { get { return BufferWidth ; } } // TODO
2014-04-30 23:48:37 +00:00
public int VirtualHeight { get { return BufferHeight ; } } // TODO
2013-12-15 20:51:57 +00:00
public int BufferWidth { get { return vwidth ; } }
public int BufferHeight { get { return vheight ; } }
public int BackgroundColor { get { return unchecked ( ( int ) 0xff000000 ) ; } }
2014-09-22 19:35:00 +00:00
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 ) ;
}
2013-12-15 20:51:57 +00:00
unsafe void update_video ( )
{
int pitch = 0 ;
IntPtr src = IntPtr . Zero ;
LibGPGX . 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 + + )
2014-02-21 17:07:09 +00:00
* pdst + + = * psrc + + ; // | unchecked((int)0xff000000);
2013-12-15 20:51:57 +00:00
psrc + = rinc ;
}
}
}
#endregion
2013-12-22 00:44:39 +00:00
2014-05-04 17:41:20 +00:00
#region Settings
2014-06-27 02:22:23 +00:00
GPGXSyncSettings _SyncSettings ;
2014-07-11 18:51:26 +00:00
GPGXSettings _Settings ;
2013-12-23 23:03:12 +00:00
2014-10-19 01:22:47 +00:00
public GPGXSettings GetSettings ( ) { return _Settings . Clone ( ) ; }
public GPGXSyncSettings GetSyncSettings ( ) { return _SyncSettings . Clone ( ) ; }
public bool PutSettings ( GPGXSettings o )
2014-07-11 18:51:26 +00:00
{
2014-10-19 01:22:47 +00:00
_Settings = o ;
2014-07-11 18:51:26 +00:00
LibGPGX . gpgx_set_draw_mask ( _Settings . GetDrawMask ( ) ) ;
return false ;
}
2014-10-19 01:22:47 +00:00
public bool PutSyncSettings ( GPGXSyncSettings o )
2013-12-23 23:03:12 +00:00
{
2014-10-19 01:22:47 +00:00
bool ret = GPGXSyncSettings . NeedsReboot ( _SyncSettings , o ) ;
_SyncSettings = o ;
2013-12-23 23:03:12 +00:00
return ret ;
}
2014-07-11 18:51:26 +00:00
public class GPGXSettings
{
2014-07-20 00:26:42 +00:00
[DisplayName("Background Layer A")]
2014-07-11 18:51:26 +00:00
[Description("True to draw BG layer A")]
[DefaultValue(true)]
public bool DrawBGA { get ; set ; }
2014-07-20 00:26:42 +00:00
[DisplayName("Background Layer B")]
2014-07-11 18:51:26 +00:00
[Description("True to draw BG layer B")]
[DefaultValue(true)]
public bool DrawBGB { get ; set ; }
2014-07-20 00:26:42 +00:00
[DisplayName("Background Layer W")]
2014-07-11 18:51:26 +00:00
[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 LibGPGX . DrawMask GetDrawMask ( )
{
LibGPGX . DrawMask ret = 0 ;
if ( DrawBGA ) ret | = LibGPGX . DrawMask . BGA ;
if ( DrawBGB ) ret | = LibGPGX . DrawMask . BGB ;
if ( DrawBGW ) ret | = LibGPGX . DrawMask . BGW ;
return ret ;
}
}
2013-12-23 23:03:12 +00:00
public class GPGXSyncSettings
{
2014-07-20 00:26:42 +00:00
[DisplayName("Use Six Button Controllers")]
2013-12-23 23:03:12 +00:00
[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.")]
2014-01-15 00:56:13 +00:00
[DefaultValue(true)]
2013-12-23 23:03:12 +00:00
public bool UseSixButton { get ; set ; }
2014-07-20 00:26:42 +00:00
[DisplayName("Control Type")]
2013-12-23 23:03:12 +00:00
[Description("Sets the type of controls that are plugged into the console. Some games will automatically load with a different control type.")]
2014-01-15 00:56:13 +00:00
[DefaultValue(ControlType.Normal)]
2013-12-23 23:03:12 +00:00
public ControlType ControlType { get ; set ; }
2014-07-20 00:26:42 +00:00
[DisplayName("Autodetect Region")]
2014-01-15 00:56:13 +00:00
[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(LibGPGX.Region.Autodetect)]
public LibGPGX . Region Region { get ; set ; }
public GPGXSyncSettings ( )
{
2014-07-11 18:51:26 +00:00
SettingsUtil . SetDefaultValues ( this ) ;
2013-12-23 23:03:12 +00:00
}
public GPGXSyncSettings Clone ( )
{
return ( GPGXSyncSettings ) MemberwiseClone ( ) ;
}
2014-01-15 00:56:13 +00:00
public static bool NeedsReboot ( GPGXSyncSettings x , GPGXSyncSettings y )
{
2014-08-03 22:19:55 +00:00
return ! DeepEquality . DeepEquals ( x , y ) ;
2014-01-15 00:56:13 +00:00
}
2013-12-23 23:03:12 +00:00
}
2014-05-04 17:41:20 +00:00
#endregion
2013-12-15 20:51:57 +00:00
}
}