2012-09-08 21:36:04 +00:00
using System ;
using System.Collections.Generic ;
using System.IO ;
2013-10-27 22:07:40 +00:00
using BizHawk.Common ;
2013-11-04 01:06:36 +00:00
using BizHawk.Emulation.Common ;
2013-10-27 22:07:40 +00:00
2013-11-13 03:32:25 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
2012-09-08 21:36:04 +00:00
{
2012-09-08 22:01:47 +00:00
/// <summary>
/// a gameboy/gameboy color emulator wrapped around native C++ libgambatte
/// </summary>
2014-04-25 01:19:57 +00:00
[ CoreAttributes (
"Gambatte" ,
"sinamas" ,
isPorted : true ,
2014-04-25 23:17:10 +00:00
isReleased : true
2014-04-25 01:19:57 +00:00
) ]
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
public class Gameboy : IEmulator , IVideoProvider , ISyncSoundProvider
2012-09-08 21:36:04 +00:00
{
/// <summary>
/// internal gambatte state
/// </summary>
2012-12-29 01:25:06 +00:00
internal IntPtr GambatteState = IntPtr . Zero ;
2012-09-08 21:36:04 +00:00
2012-09-09 14:17:57 +00:00
/// <summary>
/// keep a copy of the input callback delegate so it doesn't get GCed
/// </summary>
LibGambatte . InputGetter InputCallback ;
/// <summary>
/// whatever keys are currently depressed
/// </summary>
LibGambatte . Buttons CurrentButtons = 0 ;
2012-09-08 21:36:04 +00:00
2013-05-09 23:15:59 +00:00
/// <summary>
/// RTC time when emulation begins.
/// </summary>
2013-11-24 17:32:46 +00:00
uint zerotime = 0 ;
2013-05-09 23:15:59 +00:00
LibGambatte . RTCCallback TimeCallback ;
2013-11-24 17:32:46 +00:00
uint GetCurrentTime ( )
2013-05-09 23:15:59 +00:00
{
2013-11-24 17:32:46 +00:00
uint fn = ( uint ) Frame ;
2013-05-09 23:15:59 +00:00
fn / = 60 ; // exactly 60 fps. in case you feel bad about it, remember that we're not exactly tracking cpu cycles either.
fn + = zerotime ;
return fn ;
}
2013-12-23 02:51:41 +00:00
public Gameboy ( CoreComm comm , GameInfo game , byte [ ] romdata , object Settings , object SyncSettings )
2012-09-08 21:36:04 +00:00
{
2012-12-10 00:43:43 +00:00
CoreComm = comm ;
2012-12-10 01:33:09 +00:00
comm . VsyncNum = 262144 ;
comm . VsyncDen = 4389 ;
comm . RomStatusAnnotation = null ;
comm . RomStatusDetails = null ;
comm . CpuTraceAvailable = true ;
comm . NominalWidth = 160 ;
comm . NominalHeight = 144 ;
2012-09-24 20:20:21 +00:00
ThrowExceptionForBadRom ( romdata ) ;
2013-08-24 16:54:22 +00:00
BoardName = MapperName ( romdata ) ;
2012-09-24 20:20:21 +00:00
2012-09-09 21:15:54 +00:00
GambatteState = LibGambatte . gambatte_create ( ) ;
if ( GambatteState = = IntPtr . Zero )
throw new Exception ( "gambatte_create() returned null???" ) ;
2013-12-09 20:50:21 +00:00
try
{
2013-12-23 02:51:41 +00:00
this . SyncSettings = ( GambatteSyncSettings ) SyncSettings ? ? GambatteSyncSettings . GetDefaults ( ) ;
2013-12-09 20:50:21 +00:00
LibGambatte . LoadFlags flags = 0 ;
2012-09-15 16:14:03 +00:00
2013-12-23 02:51:41 +00:00
if ( this . SyncSettings . ForceDMG )
2013-12-09 20:50:21 +00:00
flags | = LibGambatte . LoadFlags . FORCE_DMG ;
2013-12-23 02:51:41 +00:00
if ( this . SyncSettings . GBACGB )
2013-12-09 20:50:21 +00:00
flags | = LibGambatte . LoadFlags . GBA_CGB ;
2013-12-23 02:51:41 +00:00
if ( this . SyncSettings . MulticartCompat )
2013-12-09 20:50:21 +00:00
flags | = LibGambatte . LoadFlags . MULTICART_COMPAT ;
2012-09-15 16:14:03 +00:00
2013-12-09 20:50:21 +00:00
if ( LibGambatte . gambatte_load ( GambatteState , romdata , ( uint ) romdata . Length , GetCurrentTime ( ) , flags ) ! = 0 )
throw new Exception ( "gambatte_load() returned non-zero (is this not a gb or gbc rom?)" ) ;
2012-09-08 21:36:04 +00:00
2013-12-09 20:50:21 +00:00
// set real default colors (before anyone mucks with them at all)
2013-12-23 02:51:41 +00:00
PutSettings ( Settings ? ? GambatteSettings . GetDefaults ( ) ) ;
2012-11-18 17:02:55 +00:00
2013-12-09 20:50:21 +00:00
InitSound ( ) ;
2012-09-09 14:17:57 +00:00
2013-12-09 20:50:21 +00:00
Frame = 0 ;
LagCount = 0 ;
IsLagFrame = false ;
2012-09-09 14:17:57 +00:00
2013-12-09 20:50:21 +00:00
InputCallback = new LibGambatte . InputGetter ( ControllerCallback ) ;
2012-09-09 14:17:57 +00:00
2013-12-09 20:50:21 +00:00
LibGambatte . gambatte_setinputgetter ( GambatteState , InputCallback ) ;
2012-09-11 15:28:38 +00:00
2013-12-09 20:50:21 +00:00
InitMemoryDomains ( ) ;
2012-09-26 03:24:00 +00:00
2013-12-09 20:50:21 +00:00
CoreComm . RomStatusDetails = string . Format ( "{0}\r\nSHA1:{1}\r\nMD5:{2}\r\n" ,
game . Name ,
2013-12-27 04:41:50 +00:00
Util . Hash_SHA1 ( romdata ) , Util . Hash_MD5 ( romdata )
2013-12-09 20:50:21 +00:00
) ;
2013-05-09 23:15:59 +00:00
2013-12-09 20:50:21 +00:00
TimeCallback = new LibGambatte . RTCCallback ( GetCurrentTime ) ;
LibGambatte . gambatte_setrtccallback ( GambatteState , TimeCallback ) ;
}
catch
{
Dispose ( ) ;
throw ;
}
2012-09-08 21:36:04 +00:00
}
2012-09-09 02:06:07 +00:00
public static readonly ControllerDefinition GbController = new ControllerDefinition
2012-09-08 21:36:04 +00:00
{
Name = "Gameboy Controller" ,
BoolButtons =
{
2013-07-29 02:11:00 +00:00
"Up" , "Down" , "Left" , "Right" , "Start" , "Select" , "B" , "A" , "Power"
2012-09-08 21:36:04 +00:00
}
} ;
public ControllerDefinition ControllerDefinition
{
get { return GbController ; }
}
public IController Controller { get ; set ; }
2012-09-09 21:57:15 +00:00
LibGambatte . Buttons ControllerCallback ( )
{
2013-11-10 18:15:32 +00:00
CoreComm . InputCallback . Call ( ) ;
2012-09-09 21:57:15 +00:00
IsLagFrame = false ;
return CurrentButtons ;
}
2014-04-19 22:23:13 +00:00
public Dictionary < string , int > GetCpuFlagsAndRegisters ( )
2013-11-11 03:20:33 +00:00
{
2013-11-11 18:05:29 +00:00
int [ ] data = new int [ 10 ] ;
LibGambatte . gambatte_getregs ( GambatteState , data ) ;
2014-04-19 22:23:13 +00:00
return new Dictionary < string , int >
{
{ "PC" , data [ ( int ) LibGambatte . RegIndicies . PC ] & 0xffff } ,
{ "SP" , data [ ( int ) LibGambatte . RegIndicies . SP ] & 0xffff } ,
{ "A" , data [ ( int ) LibGambatte . RegIndicies . A ] & 0xff } ,
{ "B" , data [ ( int ) LibGambatte . RegIndicies . B ] & 0xff } ,
{ "C" , data [ ( int ) LibGambatte . RegIndicies . C ] & 0xff } ,
{ "D" , data [ ( int ) LibGambatte . RegIndicies . D ] & 0xff } ,
{ "E" , data [ ( int ) LibGambatte . RegIndicies . E ] & 0xff } ,
{ "F" , data [ ( int ) LibGambatte . RegIndicies . F ] & 0xff } ,
{ "H" , data [ ( int ) LibGambatte . RegIndicies . H ] & 0xff } ,
{ "L" , data [ ( int ) LibGambatte . RegIndicies . L ] & 0xff }
} ;
2013-11-11 03:20:33 +00:00
}
2012-09-15 18:30:11 +00:00
/// <summary>
/// true if the emulator is currently emulating CGB
/// </summary>
/// <returns></returns>
public bool IsCGBMode ( )
{
return ( LibGambatte . gambatte_iscgb ( GambatteState ) ) ;
}
2012-09-09 14:17:57 +00:00
2012-12-29 15:48:30 +00:00
internal void FrameAdvancePrep ( )
2012-09-08 21:36:04 +00:00
{
2013-12-07 00:53:06 +00:00
Frame + + ;
2012-09-09 14:17:57 +00:00
// update our local copy of the controller data
CurrentButtons = 0 ;
if ( Controller [ "Up" ] )
CurrentButtons | = LibGambatte . Buttons . UP ;
if ( Controller [ "Down" ] )
CurrentButtons | = LibGambatte . Buttons . DOWN ;
if ( Controller [ "Left" ] )
CurrentButtons | = LibGambatte . Buttons . LEFT ;
if ( Controller [ "Right" ] )
CurrentButtons | = LibGambatte . Buttons . RIGHT ;
if ( Controller [ "A" ] )
CurrentButtons | = LibGambatte . Buttons . A ;
if ( Controller [ "B" ] )
CurrentButtons | = LibGambatte . Buttons . B ;
if ( Controller [ "Select" ] )
CurrentButtons | = LibGambatte . Buttons . SELECT ;
if ( Controller [ "Start" ] )
CurrentButtons | = LibGambatte . Buttons . START ;
2012-09-09 21:57:15 +00:00
// the controller callback will set this to false if it actually gets called during the frame
IsLagFrame = true ;
2012-09-11 17:37:17 +00:00
// download any modified data to the core
foreach ( var r in MemoryRefreshers )
r . RefreshWrite ( ) ;
2012-09-16 16:25:54 +00:00
if ( Controller [ "Power" ] )
2013-05-09 23:15:59 +00:00
LibGambatte . gambatte_reset ( GambatteState , GetCurrentTime ( ) ) ;
2012-09-15 02:36:19 +00:00
2012-10-14 15:10:33 +00:00
RefreshMemoryCallbacks ( ) ;
2012-12-10 00:43:43 +00:00
if ( CoreComm . Tracer . Enabled )
2012-11-02 19:44:31 +00:00
tracecb = MakeTrace ;
else
tracecb = null ;
LibGambatte . gambatte_settracecallback ( GambatteState , tracecb ) ;
2012-12-29 15:48:30 +00:00
}
2012-10-14 15:10:33 +00:00
2012-12-29 15:48:30 +00:00
internal void FrameAdvancePost ( )
{
2012-09-11 17:37:17 +00:00
// upload any modified data to the memory domains
foreach ( var r in MemoryRefreshers )
r . RefreshRead ( ) ;
2012-09-09 21:57:15 +00:00
if ( IsLagFrame )
LagCount + + ;
2012-09-08 21:36:04 +00:00
2012-11-06 17:54:04 +00:00
if ( endofframecallback ! = null )
endofframecallback ( LibGambatte . gambatte_cpuread ( GambatteState , 0xff40 ) ) ;
2012-09-08 21:36:04 +00:00
}
2014-04-28 16:02:11 +00:00
ulong _cycleCount = 0 ;
uint _nextRunAdjust = 0 ;
public ulong CycleCount { get { return _cycleCount ; } }
2012-12-29 15:48:30 +00:00
public void FrameAdvance ( bool render , bool rendersound )
{
FrameAdvancePrep ( ) ;
2014-04-28 16:02:11 +00:00
uint nsamp = 35112 + _nextRunAdjust ; // according to gambatte docs, this is the nominal length of a frame in 2mhz clocks
2012-12-29 15:48:30 +00:00
2014-04-28 16:02:11 +00:00
// Gambatte is going to run whatever it feels like, and report what it ran into nsamp
// Therefore we should track it and factor this in next frame, to keep a consistent definition of 1 frame = 35112 cycles
2012-12-29 15:48:30 +00:00
LibGambatte . gambatte_runfor ( GambatteState , VideoBuffer , 160 , soundbuff , ref nsamp ) ;
2014-04-28 16:02:11 +00:00
_cycleCount + = ( ulong ) nsamp ;
_nextRunAdjust = 35112 - nsamp ;
2012-12-29 15:48:30 +00:00
if ( rendersound )
2013-10-25 01:00:31 +00:00
{
2012-12-29 15:48:30 +00:00
soundbuffcontains = ( int ) nsamp ;
2013-10-25 01:00:31 +00:00
ProcessSound ( ) ;
}
2012-12-29 15:48:30 +00:00
else
2013-10-25 01:00:31 +00:00
{
2012-12-29 15:48:30 +00:00
soundbuffcontains = 0 ;
2013-10-25 01:00:31 +00:00
}
2012-12-29 15:48:30 +00:00
FrameAdvancePost ( ) ;
}
2013-08-24 16:54:22 +00:00
static string MapperName ( byte [ ] romdata )
{
switch ( romdata [ 0x147 ] )
{
case 0x00 : return "Plain ROM" ; // = PLAIN; break;
case 0x01 : return "MBC1 ROM" ; // = MBC1; break;
case 0x02 : return "MBC1 ROM+RAM" ; // = MBC1; break;
case 0x03 : return "MBC1 ROM+RAM+BATTERY" ; // = MBC1; break;
case 0x05 : return "MBC2 ROM" ; // = MBC2; break;
case 0x06 : return "MBC2 ROM+BATTERY" ; // = MBC2; break;
case 0x08 : return "Plain ROM+RAM" ; // = PLAIN; break;
case 0x09 : return "Plain ROM+RAM+BATTERY" ; // = PLAIN; break;
case 0x0F : return "MBC3 ROM+TIMER+BATTERY" ; // = MBC3; break;
case 0x10 : return "MBC3 ROM+TIMER+RAM+BATTERY" ; // = MBC3; break;
case 0x11 : return "MBC3 ROM" ; // = MBC3; break;
case 0x12 : return "MBC3 ROM+RAM" ; // = MBC3; break;
case 0x13 : return "MBC3 ROM+RAM+BATTERY" ; // = MBC3; break;
case 0x19 : return "MBC5 ROM" ; // = MBC5; break;
case 0x1A : return "MBC5 ROM+RAM" ; // = MBC5; break;
case 0x1B : return "MBC5 ROM+RAM+BATTERY" ; // = MBC5; break;
case 0x1C : return "MBC5 ROM+RUMBLE" ; // = MBC5; break;
case 0x1D : return "MBC5 ROM+RUMBLE+RAM" ; // = MBC5; break;
case 0x1E : return "MBC5 ROM+RUMBLE+RAM+BATTERY" ; // = MBC5; break;
case 0xFF : return "HuC1 ROM+RAM+BATTERY" ; // = HUC1; break;
default : return "UNKNOWN" ;
}
}
2012-09-24 20:20:21 +00:00
/// <summary>
/// throw exception with intelligible message on some kinds of bad rom
/// </summary>
/// <param name="romdata"></param>
static void ThrowExceptionForBadRom ( byte [ ] romdata )
{
if ( romdata . Length < 0x148 )
throw new Exception ( "ROM is far too small to be a valid GB\\GBC rom!" ) ;
2012-09-09 14:17:57 +00:00
2012-09-24 20:20:21 +00:00
switch ( romdata [ 0x147 ] )
{
case 0x00 : break ;
case 0x01 : break ;
case 0x02 : break ;
case 0x03 : break ;
case 0x05 : break ;
case 0x06 : break ;
case 0x08 : break ;
case 0x09 : break ;
case 0x0b : throw new Exception ( "\"MM01\" Mapper not supported!" ) ;
case 0x0c : throw new Exception ( "\"MM01\" Mapper not supported!" ) ;
case 0x0d : throw new Exception ( "\"MM01\" Mapper not supported!" ) ;
case 0x0f : break ;
case 0x10 : break ;
case 0x11 : break ;
case 0x12 : break ;
case 0x13 : break ;
case 0x15 : throw new Exception ( "\"MBC4\" Mapper not supported!" ) ;
case 0x16 : throw new Exception ( "\"MBC4\" Mapper not supported!" ) ;
case 0x17 : throw new Exception ( "\"MBC4\" Mapper not supported!" ) ;
case 0x19 : break ;
case 0x1a : break ;
case 0x1b : break ;
case 0x1c : break ; // rumble
case 0x1d : break ; // rumble
case 0x1e : break ; // rumble
case 0x20 : throw new Exception ( "\"MBC6\" Mapper not supported!" ) ;
case 0x22 : throw new Exception ( "\"MBC7\" Mapper not supported!" ) ;
case 0xfc : throw new Exception ( "\"Pocket Camera\" Mapper not supported!" ) ;
case 0xfd : throw new Exception ( "\"Bandai TAMA5\" Mapper not supported!" ) ;
case 0xfe : throw new Exception ( "\"HuC3\" Mapper not supported!" ) ;
case 0xff : break ;
default : throw new Exception ( string . Format ( "Unknown mapper: {0:x2}" , romdata [ 0x147 ] ) ) ;
}
return ;
}
2012-09-09 21:57:15 +00:00
2012-09-09 14:17:57 +00:00
public int Frame { get ; set ; }
2012-09-08 21:36:04 +00:00
public int LagCount { get ; set ; }
2012-09-09 21:57:15 +00:00
public bool IsLagFrame { get ; private set ; }
2012-09-08 21:36:04 +00:00
public string SystemId
{
get { return "GB" ; }
}
2013-08-24 16:54:22 +00:00
public string BoardName { get ; private set ; }
2012-10-03 15:31:04 +00:00
public bool DeterministicEmulation { get { return true ; } }
2012-09-08 21:36:04 +00:00
2012-10-14 15:10:33 +00:00
#region saveram
2012-09-14 22:28:38 +00:00
public byte [ ] ReadSaveRam ( )
2012-09-08 21:36:04 +00:00
{
2012-09-14 22:28:38 +00:00
int length = LibGambatte . gambatte_savesavedatalength ( GambatteState ) ;
if ( length > 0 )
2012-09-10 23:40:53 +00:00
{
2012-09-14 22:28:38 +00:00
byte [ ] ret = new byte [ length ] ;
LibGambatte . gambatte_savesavedata ( GambatteState , ret ) ;
return ret ;
2012-09-10 23:40:53 +00:00
}
2012-09-14 22:28:38 +00:00
else
return new byte [ 0 ] ;
2012-09-10 23:40:53 +00:00
}
public void StoreSaveRam ( byte [ ] data )
{
if ( data . Length ! = LibGambatte . gambatte_savesavedatalength ( GambatteState ) )
throw new ArgumentException ( "Size of saveram data does not match expected!" ) ;
LibGambatte . gambatte_loadsavedata ( GambatteState , data ) ;
2012-09-08 21:36:04 +00:00
}
2012-09-14 21:29:35 +00:00
/// <summary>
/// reset cart save ram, if any, to initial state
/// </summary>
public void ClearSaveRam ( )
{
int length = LibGambatte . gambatte_savesavedatalength ( GambatteState ) ;
if ( length = = 0 )
return ;
byte [ ] clear = new byte [ length ] ;
for ( int i = 0 ; i < clear . Length ; i + + )
clear [ i ] = 0xff ; // this exactly matches what gambatte core does
StoreSaveRam ( clear ) ;
}
2012-09-10 23:40:53 +00:00
2012-09-08 21:36:04 +00:00
public bool SaveRamModified
{
2012-09-10 23:40:53 +00:00
get
{
if ( LibGambatte . gambatte_savesavedatalength ( GambatteState ) = = 0 )
return false ;
else
return true ; // need to wire more stuff into the core to actually know this
}
set { }
2012-09-08 21:36:04 +00:00
}
2012-10-14 15:10:33 +00:00
#endregion
2013-11-03 16:29:51 +00:00
public void ResetCounters ( )
2012-09-08 21:36:04 +00:00
{
2012-09-09 21:57:15 +00:00
Frame = 0 ;
LagCount = 0 ;
2012-11-25 15:41:40 +00:00
IsLagFrame = false ;
2012-09-08 21:36:04 +00:00
}
2012-09-09 21:57:15 +00:00
#region savestates
2012-09-23 16:56:11 +00:00
/// <summary>
/// handles the core-portion of savestating
/// </summary>
/// <returns>private binary data corresponding to a savestate</returns>
byte [ ] SaveCoreBinary ( )
{
uint nlen = 0 ;
IntPtr ndata = IntPtr . Zero ;
if ( ! LibGambatte . gambatte_savestate ( GambatteState , VideoBuffer , 160 , ref ndata , ref nlen ) )
throw new Exception ( "Gambatte failed to save the savestate!" ) ;
if ( nlen = = 0 )
throw new Exception ( "Gambatte returned a 0-length savestate?" ) ;
byte [ ] data = new byte [ nlen ] ;
System . Runtime . InteropServices . Marshal . Copy ( ndata , data , 0 , ( int ) nlen ) ;
LibGambatte . gambatte_savestate_destroy ( ndata ) ;
return data ;
}
/// <summary>
/// handles the core portion of loadstating
/// </summary>
/// <param name="data">private binary data previously returned from SaveCoreBinary()</param>
void LoadCoreBinary ( byte [ ] data )
{
if ( ! LibGambatte . gambatte_loadstate ( GambatteState , data , ( uint ) data . Length ) )
throw new Exception ( "Gambatte failed to load the savestate!" ) ;
2012-09-29 13:03:14 +00:00
// since a savestate has been loaded, all memory domain data is now dirty
foreach ( var r in MemoryRefreshers )
r . RefreshRead ( ) ;
2012-09-23 16:56:11 +00:00
}
2012-11-19 17:59:57 +00:00
2012-09-08 21:36:04 +00:00
public void SaveStateText ( System . IO . TextWriter writer )
{
2012-09-09 18:47:00 +00:00
var temp = SaveStateBinary ( ) ;
temp . SaveAsHex ( writer ) ;
2012-09-23 16:56:11 +00:00
// write extra copy of stuff we don't use
writer . WriteLine ( "Frame {0}" , Frame ) ;
2012-09-08 21:36:04 +00:00
}
public void LoadStateText ( System . IO . TextReader reader )
{
2012-09-09 18:47:00 +00:00
string hex = reader . ReadLine ( ) ;
byte [ ] state = new byte [ hex . Length / 2 ] ;
state . ReadFromHex ( hex ) ;
LoadStateBinary ( new BinaryReader ( new MemoryStream ( state ) ) ) ;
2012-09-08 21:36:04 +00:00
}
public void SaveStateBinary ( System . IO . BinaryWriter writer )
{
2012-09-23 16:56:11 +00:00
byte [ ] data = SaveCoreBinary ( ) ;
2012-09-09 18:47:00 +00:00
2012-09-23 16:56:11 +00:00
writer . Write ( data . Length ) ;
2012-09-09 18:47:00 +00:00
writer . Write ( data ) ;
2012-09-11 01:46:57 +00:00
// other variables
writer . Write ( IsLagFrame ) ;
writer . Write ( LagCount ) ;
writer . Write ( Frame ) ;
2012-09-08 21:36:04 +00:00
}
public void LoadStateBinary ( System . IO . BinaryReader reader )
{
2012-09-09 18:47:00 +00:00
int length = reader . ReadInt32 ( ) ;
byte [ ] data = reader . ReadBytes ( length ) ;
2012-09-08 21:48:46 +00:00
2012-09-23 16:56:11 +00:00
LoadCoreBinary ( data ) ;
2012-09-11 01:46:57 +00:00
// other variables
IsLagFrame = reader . ReadBoolean ( ) ;
LagCount = reader . ReadInt32 ( ) ;
Frame = reader . ReadInt32 ( ) ;
2012-09-08 21:36:04 +00:00
}
public byte [ ] SaveStateBinary ( )
{
2012-09-09 18:47:00 +00:00
MemoryStream ms = new MemoryStream ( ) ;
BinaryWriter bw = new BinaryWriter ( ms ) ;
SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
return ms . ToArray ( ) ;
2012-09-08 21:36:04 +00:00
}
2013-05-06 20:51:28 +00:00
public bool BinarySaveStatesPreferred { get { return true ; } }
2012-09-09 21:57:15 +00:00
#endregion
2012-10-14 15:10:33 +00:00
#region memorycallback
LibGambatte . MemoryCallback readcb ;
LibGambatte . MemoryCallback writecb ;
2013-11-13 17:08:52 +00:00
LibGambatte . MemoryCallback execcb ;
2012-10-14 15:10:33 +00:00
void RefreshMemoryCallbacks ( )
{
2012-12-10 00:43:43 +00:00
var mcs = CoreComm . MemoryCallbackSystem ;
2012-10-14 15:10:33 +00:00
// we RefreshMemoryCallbacks() after the triggers in case the trigger turns itself off at that point
2013-11-10 21:20:55 +00:00
if ( mcs . HasReads )
readcb = delegate ( uint addr ) { mcs . CallRead ( addr ) ; RefreshMemoryCallbacks ( ) ; } ;
2012-10-14 15:10:33 +00:00
else
readcb = null ;
2013-11-10 21:20:55 +00:00
if ( mcs . HasWrites )
writecb = delegate ( uint addr ) { mcs . CallWrite ( addr ) ; RefreshMemoryCallbacks ( ) ; } ;
2012-10-14 15:10:33 +00:00
else
writecb = null ;
2013-11-13 17:08:52 +00:00
if ( mcs . HasExecutes )
execcb = delegate ( uint addr ) { mcs . CallExecute ( addr ) ; RefreshMemoryCallbacks ( ) ; } ;
else
execcb = null ;
2012-10-14 15:10:33 +00:00
LibGambatte . gambatte_setreadcallback ( GambatteState , readcb ) ;
LibGambatte . gambatte_setwritecallback ( GambatteState , writecb ) ;
2013-11-13 17:08:52 +00:00
LibGambatte . gambatte_setexeccallback ( GambatteState , execcb ) ;
2012-10-14 15:10:33 +00:00
}
2012-11-19 17:59:57 +00:00
2012-10-14 15:10:33 +00:00
#endregion
2012-09-08 21:36:04 +00:00
2012-12-10 00:43:43 +00:00
public CoreComm CoreComm { get ; set ; }
2012-09-08 21:36:04 +00:00
2012-11-02 19:44:31 +00:00
LibGambatte . TraceCallback tracecb ;
void MakeTrace ( IntPtr _s )
{
int [ ] s = new int [ 13 ] ;
System . Runtime . InteropServices . Marshal . Copy ( _s , s , 0 , 13 ) ;
2012-11-02 23:19:16 +00:00
ushort unused ;
2012-11-02 19:44:31 +00:00
2012-12-10 00:43:43 +00:00
CoreComm . Tracer . Put ( string . Format (
2012-11-02 23:19:16 +00:00
"{13} SP:{2:x2} A:{3:x2} B:{4:x2} C:{5:x2} D:{6:x2} E:{7:x2} F:{8:x2} H:{9:x2} L:{10:x2} {11} Cy:{0}" ,
2012-11-02 19:44:31 +00:00
s [ 0 ] ,
s [ 1 ] & 0xffff ,
s [ 2 ] & 0xffff ,
s [ 3 ] & 0xff ,
s [ 4 ] & 0xff ,
s [ 5 ] & 0xff ,
s [ 6 ] & 0xff ,
s [ 7 ] & 0xff ,
s [ 8 ] & 0xff ,
s [ 9 ] & 0xff ,
s [ 10 ] & 0xff ,
s [ 11 ] ! = 0 ? "skip" : "" ,
2012-11-02 23:19:16 +00:00
s [ 12 ] & 0xff ,
2013-11-14 15:01:32 +00:00
Common . Components . Z80GB . NewDisassembler . Disassemble ( ( ushort ) s [ 1 ] , ( addr ) = > LibGambatte . gambatte_cpuread ( GambatteState , addr ) , out unused ) . PadRight ( 30 )
2012-11-02 19:44:31 +00:00
) ) ;
}
2012-09-11 17:37:17 +00:00
#region MemoryDomains
class MemoryRefresher
{
IntPtr data ;
int length ;
byte [ ] CachedMemory ;
public MemoryRefresher ( IntPtr data , int length )
{
this . data = data ;
this . length = length ;
CachedMemory = new byte [ length ] ;
writeneeded = false ;
2012-09-14 21:29:35 +00:00
// needs to be true in case a read is attempted before the first frame advance
readneeded = true ;
2012-09-11 17:37:17 +00:00
}
2012-11-19 17:59:57 +00:00
2012-09-11 17:37:17 +00:00
bool readneeded ;
bool writeneeded ;
/// <summary>
/// reads data from native core to managed buffer
/// </summary>
public void RefreshRead ( )
{
readneeded = true ;
}
/// <summary>
/// writes data from managed buffer back to core
/// </summary>
public void RefreshWrite ( )
{
if ( writeneeded )
{
System . Runtime . InteropServices . Marshal . Copy ( CachedMemory , 0 , data , length ) ;
writeneeded = false ;
}
}
public byte Peek ( int addr )
{
if ( readneeded )
{
System . Runtime . InteropServices . Marshal . Copy ( data , CachedMemory , 0 , length ) ;
readneeded = false ;
}
2014-02-26 20:18:48 +00:00
return CachedMemory [ addr ] ;
2012-09-11 17:37:17 +00:00
}
public void Poke ( int addr , byte val )
{
// a poke without any peek is certainly legal. we need to update read, because writeneeded = true means that
// all of this data will be downloaded before the next frame. so everything but that which was poked needs to
// be up to date.
if ( readneeded )
{
System . Runtime . InteropServices . Marshal . Copy ( data , CachedMemory , 0 , length ) ;
readneeded = false ;
}
2014-02-26 20:18:48 +00:00
CachedMemory [ addr ] = val ;
2012-09-11 17:37:17 +00:00
writeneeded = true ;
}
}
2012-09-11 15:28:38 +00:00
2012-09-13 21:19:26 +00:00
void CreateMemoryDomain ( LibGambatte . MemoryAreas which , string name )
2012-09-08 21:36:04 +00:00
{
2012-09-11 15:28:38 +00:00
IntPtr data = IntPtr . Zero ;
int length = 0 ;
if ( ! LibGambatte . gambatte_getmemoryarea ( GambatteState , which , ref data , ref length ) )
throw new Exception ( "gambatte_getmemoryarea() failed!" ) ;
2012-09-11 19:05:44 +00:00
// if length == 0, it's an empty block; (usually rambank on some carts); that's ok
2012-09-13 21:03:34 +00:00
// TODO: when length == 0, should we simply not add the memory domain at all?
2012-09-11 19:05:44 +00:00
if ( data = = IntPtr . Zero & & length > 0 )
2012-09-11 15:28:38 +00:00
throw new Exception ( "bad return from gambatte_getmemoryarea()" ) ;
2012-09-13 21:03:34 +00:00
var refresher = new MemoryRefresher ( data , length ) ;
MemoryRefreshers . Add ( refresher ) ;
2012-09-11 17:37:17 +00:00
2013-11-06 02:15:29 +00:00
_MemoryDomains . Add ( new MemoryDomain ( name , length , MemoryDomain . Endian . Little , refresher . Peek , refresher . Poke ) ) ;
2012-09-08 21:36:04 +00:00
}
2012-09-11 15:28:38 +00:00
void InitMemoryDomains ( )
2012-09-08 21:36:04 +00:00
{
2012-09-13 21:03:34 +00:00
MemoryRefreshers = new List < MemoryRefresher > ( ) ;
2012-09-11 15:28:38 +00:00
2012-09-13 21:19:26 +00:00
CreateMemoryDomain ( LibGambatte . MemoryAreas . wram , "WRAM" ) ;
CreateMemoryDomain ( LibGambatte . MemoryAreas . rom , "ROM" ) ;
CreateMemoryDomain ( LibGambatte . MemoryAreas . vram , "VRAM" ) ;
CreateMemoryDomain ( LibGambatte . MemoryAreas . cartram , "Cart RAM" ) ;
CreateMemoryDomain ( LibGambatte . MemoryAreas . oam , "OAM" ) ;
CreateMemoryDomain ( LibGambatte . MemoryAreas . hram , "HRAM" ) ;
2012-09-11 15:28:38 +00:00
2012-09-13 21:03:34 +00:00
// also add a special memory domain for the system bus, where calls get sent directly to the core each time
2013-11-06 02:15:29 +00:00
_MemoryDomains . Add ( new MemoryDomain ( "System Bus" , 65536 , MemoryDomain . Endian . Little ,
2012-09-13 21:03:34 +00:00
delegate ( int addr )
{
2014-02-26 20:18:48 +00:00
if ( addr < 0 | | addr > = 65536 )
throw new ArgumentOutOfRangeException ( ) ;
2012-09-13 21:03:34 +00:00
return LibGambatte . gambatte_cpuread ( GambatteState , ( ushort ) addr ) ;
} ,
delegate ( int addr , byte val )
{
2014-02-26 20:18:48 +00:00
if ( addr < 0 | | addr > = 65536 )
throw new ArgumentOutOfRangeException ( ) ;
2012-09-13 21:03:34 +00:00
LibGambatte . gambatte_cpuwrite ( GambatteState , ( ushort ) addr , val ) ;
} ) ) ;
2013-11-06 02:15:29 +00:00
MemoryDomains = new MemoryDomainList ( _MemoryDomains ) ;
2012-09-08 21:36:04 +00:00
}
2013-11-06 02:15:29 +00:00
private List < MemoryDomain > _MemoryDomains = new List < MemoryDomain > ( ) ;
public MemoryDomainList MemoryDomains { get ; private set ; }
2012-09-11 15:28:38 +00:00
2012-11-19 17:59:57 +00:00
List < MemoryRefresher > MemoryRefreshers ;
2012-09-11 17:37:17 +00:00
#endregion
2012-11-05 01:34:11 +00:00
#region ppudebug
2012-11-05 20:15:53 +00:00
public bool GetGPUMemoryAreas ( out IntPtr vram , out IntPtr bgpal , out IntPtr sppal , out IntPtr oam )
{
IntPtr _vram = IntPtr . Zero ;
IntPtr _bgpal = IntPtr . Zero ;
IntPtr _sppal = IntPtr . Zero ;
IntPtr _oam = IntPtr . Zero ;
int unused = 0 ;
if ( ! LibGambatte . gambatte_getmemoryarea ( GambatteState , LibGambatte . MemoryAreas . vram , ref _vram , ref unused )
| | ! LibGambatte . gambatte_getmemoryarea ( GambatteState , LibGambatte . MemoryAreas . bgpal , ref _bgpal , ref unused )
| | ! LibGambatte . gambatte_getmemoryarea ( GambatteState , LibGambatte . MemoryAreas . sppal , ref _sppal , ref unused )
| | ! LibGambatte . gambatte_getmemoryarea ( GambatteState , LibGambatte . MemoryAreas . oam , ref _oam , ref unused ) )
{
vram = IntPtr . Zero ;
bgpal = IntPtr . Zero ;
sppal = IntPtr . Zero ;
oam = IntPtr . Zero ;
return false ;
}
vram = _vram ;
bgpal = _bgpal ;
sppal = _sppal ;
oam = _oam ;
return true ;
}
2012-11-05 01:34:11 +00:00
/// <summary>
2012-11-05 04:09:04 +00:00
///
2012-11-05 01:34:11 +00:00
/// </summary>
2012-11-05 20:15:53 +00:00
/// <param name="lcdc">current value of register $ff40 (LCDC)</param>
public delegate void ScanlineCallback ( int lcdc ) ;
2012-11-05 01:34:11 +00:00
/// <summary>
/// set up callback
/// </summary>
/// <param name="callback"></param>
2012-11-06 17:54:04 +00:00
/// <param name="line">scanline. -1 = end of frame, -2 = RIGHT NOW</param>
2012-11-05 01:34:11 +00:00
public void SetScanlineCallback ( ScanlineCallback callback , int line )
{
2012-11-05 20:15:53 +00:00
if ( GambatteState = = IntPtr . Zero )
// not sure how this is being reached. tried the debugger...
return ;
2012-11-06 17:54:04 +00:00
endofframecallback = null ;
if ( callback = = null | | line = = - 1 | | line = = - 2 )
{
2012-11-05 20:15:53 +00:00
scanlinecb = null ;
2012-11-06 17:54:04 +00:00
LibGambatte . gambatte_setscanlinecallback ( GambatteState , null , 0 ) ;
if ( line = = - 1 )
endofframecallback = callback ;
else if ( line = = - 2 )
callback ( LibGambatte . gambatte_cpuread ( GambatteState , 0xff40 ) ) ;
}
else if ( line > = 0 & & line < = 153 )
{
2012-11-05 20:15:53 +00:00
scanlinecb = delegate ( )
{
callback ( LibGambatte . gambatte_cpuread ( GambatteState , 0xff40 ) ) ;
} ;
2012-11-06 17:54:04 +00:00
LibGambatte . gambatte_setscanlinecallback ( GambatteState , scanlinecb , line ) ;
}
else
throw new ArgumentOutOfRangeException ( "line must be in [0, 153]" ) ;
2012-11-05 01:34:11 +00:00
}
2012-11-05 20:15:53 +00:00
LibGambatte . ScanlineCallback scanlinecb ;
2012-11-06 17:54:04 +00:00
ScanlineCallback endofframecallback ;
2012-11-05 20:15:53 +00:00
2012-11-05 01:34:11 +00:00
#endregion
2012-09-08 21:36:04 +00:00
public void Dispose ( )
{
2013-12-09 20:50:21 +00:00
if ( GambatteState ! = IntPtr . Zero )
{
LibGambatte . gambatte_destroy ( GambatteState ) ;
GambatteState = IntPtr . Zero ;
}
2012-09-09 13:35:58 +00:00
DisposeSound ( ) ;
2012-09-08 21:36:04 +00:00
}
#region IVideoProvider
2012-09-09 21:57:15 +00:00
public IVideoProvider VideoProvider
{
get { return this ; }
}
2012-09-08 22:01:47 +00:00
/// <summary>
/// stored image of most recent frame
/// </summary>
2012-09-08 21:36:04 +00:00
int [ ] VideoBuffer = new int [ 160 * 144 ] ;
public int [ ] GetVideoBuffer ( )
{
return VideoBuffer ;
}
public int VirtualWidth
{
2012-09-09 21:57:15 +00:00
// only sgb changes this, which we don't emulate here
2012-09-08 21:36:04 +00:00
get { return 160 ; }
}
public int BufferWidth
{
get { return 160 ; }
}
public int BufferHeight
{
get { return 144 ; }
}
public int BackgroundColor
{
get { return 0 ; }
}
2012-09-14 00:02:37 +00:00
#endregion
#region palette
2012-09-12 22:18:51 +00:00
/// <summary>
/// update gambatte core's internal colors
/// </summary>
2012-09-15 18:30:11 +00:00
public void ChangeDMGColors ( int [ ] colors )
2012-09-12 22:18:51 +00:00
{
2012-09-15 18:30:11 +00:00
for ( int i = 0 ; i < 12 ; i + + )
LibGambatte . gambatte_setdmgpalettecolor ( GambatteState , ( LibGambatte . PalType ) ( i / 4 ) , ( uint ) i % 4 , ( uint ) colors [ i ] ) ;
2012-09-12 22:18:51 +00:00
}
2012-11-18 18:46:57 +00:00
public void SetCGBColors ( GBColors . ColorType type )
2012-11-18 17:02:55 +00:00
{
2012-11-18 18:46:57 +00:00
int [ ] lut = GBColors . GetLut ( type ) ;
2012-11-19 17:59:57 +00:00
LibGambatte . gambatte_setcgbpalette ( GambatteState , lut ) ;
2012-11-18 17:02:55 +00:00
}
2012-09-08 21:36:04 +00:00
#endregion
2012-09-09 00:41:11 +00:00
#region ISoundProvider
2012-11-19 17:59:57 +00:00
public ISoundProvider SoundProvider { get { return null ; } }
2013-10-25 01:00:31 +00:00
public ISyncSoundProvider SyncSoundProvider { get { return this ; } }
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
public bool StartAsyncSound ( ) { return false ; }
public void EndAsyncSound ( ) { }
2012-09-09 21:57:15 +00:00
2012-09-09 00:41:11 +00:00
/// <summary>
/// sample pairs before resampling
/// </summary>
2012-12-29 17:11:19 +00:00
short [ ] soundbuff = new short [ ( 35112 + 2064 ) * 2 ] ;
2012-09-09 00:41:11 +00:00
/// <summary>
/// how many sample pairs are in soundbuff
/// </summary>
2012-12-29 17:11:19 +00:00
int soundbuffcontains = 0 ;
2012-09-09 00:41:11 +00:00
2013-10-25 01:00:31 +00:00
int soundoutbuffcontains = 0 ;
short [ ] soundoutbuff = new short [ 2048 ] ;
2014-03-05 05:03:13 +00:00
int latchL = 0 ;
int latchR = 0 ;
2013-10-25 01:00:31 +00:00
2014-03-05 05:03:13 +00:00
BlipBuffer blipL , blipR ;
2013-10-25 01:00:31 +00:00
void ProcessSound ( )
{
for ( uint i = 0 ; i < soundbuffcontains ; i + + )
{
int curr = soundbuff [ i * 2 ] ;
2014-03-05 05:03:13 +00:00
if ( curr ! = latchL )
2013-10-25 01:00:31 +00:00
{
2014-03-05 05:03:13 +00:00
int diff = latchL - curr ;
latchL = curr ;
blipL . AddDelta ( i , diff ) ;
}
curr = soundbuff [ i * 2 + 1 ] ;
if ( curr ! = latchR )
{
int diff = latchR - curr ;
latchR = curr ;
blipR . AddDelta ( i , diff ) ;
2013-10-25 01:00:31 +00:00
}
}
2014-03-05 05:03:13 +00:00
blipL . EndFrame ( ( uint ) soundbuffcontains ) ;
blipR . EndFrame ( ( uint ) soundbuffcontains ) ;
2013-10-25 01:00:31 +00:00
2014-03-05 05:03:13 +00:00
soundoutbuffcontains = blipL . SamplesAvailable ( ) ;
if ( soundoutbuffcontains ! = blipR . SamplesAvailable ( ) )
throw new Exception ( "Audio processing error" ) ;
2013-10-25 01:00:31 +00:00
2014-03-05 05:03:13 +00:00
blipL . ReadSamplesLeft ( soundoutbuff , soundoutbuffcontains ) ;
blipR . ReadSamplesRight ( soundoutbuff , soundoutbuffcontains ) ;
2013-10-25 01:00:31 +00:00
soundbuffcontains = 0 ;
}
2012-09-09 00:41:11 +00:00
void InitSound ( )
{
2014-03-05 05:03:13 +00:00
blipL = new BlipBuffer ( 1024 ) ;
blipL . SetRates ( 2097152 , 44100 ) ;
blipR = new BlipBuffer ( 1024 ) ;
blipR . SetRates ( 2097152 , 44100 ) ;
2012-09-09 00:41:11 +00:00
}
2012-09-09 13:35:58 +00:00
void DisposeSound ( )
{
2014-03-05 05:03:13 +00:00
if ( blipL ! = null )
{
blipL . Dispose ( ) ;
blipL = null ;
}
if ( blipR ! = null )
2013-12-09 20:50:21 +00:00
{
2014-03-05 05:03:13 +00:00
blipR . Dispose ( ) ;
blipR = null ;
2013-12-09 20:50:21 +00:00
}
2012-09-09 13:35:58 +00:00
}
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
public void DiscardSamples ( )
2012-09-29 22:38:47 +00:00
{
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
soundbuffcontains = 0 ;
2012-09-29 22:38:47 +00:00
}
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
public void GetSamples ( out short [ ] samples , out int nsamp )
2012-09-09 00:41:11 +00:00
{
2013-10-25 01:00:31 +00:00
samples = soundoutbuff ;
nsamp = soundoutbuffcontains ;
2012-09-09 00:41:11 +00:00
}
#endregion
2012-09-09 21:57:15 +00:00
2013-12-23 02:51:41 +00:00
GambatteSettings Settings ;
GambatteSyncSettings SyncSettings ;
public object GetSettings ( ) { return Settings . Clone ( ) ; }
public object GetSyncSettings ( ) { return SyncSettings . Clone ( ) ; }
public bool PutSettings ( object o )
{
Settings = ( GambatteSettings ) o ;
if ( IsCGBMode ( ) )
SetCGBColors ( Settings . CGBColors ) ;
else
ChangeDMGColors ( Settings . GBPalette ) ;
return false ;
}
public bool PutSyncSettings ( object o )
{
2013-12-23 16:58:20 +00:00
var s = ( GambatteSyncSettings ) o ;
bool ret ;
if ( s . ForceDMG ! = SyncSettings . ForceDMG | |
s . GBACGB ! = SyncSettings . GBACGB | |
s . MulticartCompat ! = SyncSettings . MulticartCompat )
ret = true ;
else
ret = false ;
SyncSettings = s ;
return ret ;
2013-12-23 02:51:41 +00:00
}
public class GambatteSettings
{
public int [ ] GBPalette ;
public GBColors . ColorType CGBColors ;
public static GambatteSettings GetDefaults ( )
{
var ret = new GambatteSettings ( ) ;
ret . GBPalette = new [ ]
{
10798341 , 8956165 , 1922333 , 337157 ,
10798341 , 8956165 , 1922333 , 337157 ,
10798341 , 8956165 , 1922333 , 337157
} ;
ret . CGBColors = GBColors . ColorType . gambatte ;
return ret ;
}
public GambatteSettings Clone ( )
{
var ret = ( GambatteSettings ) MemberwiseClone ( ) ;
ret . GBPalette = ( int [ ] ) GBPalette . Clone ( ) ;
return ret ;
}
}
public class GambatteSyncSettings
{
2013-12-23 16:58:20 +00:00
[System.ComponentModel.Description("Force the game to run on DMG hardware, even if it's detected as a CGB game. Relevant for games that are \"CGB Enhanced\" but do not require CGB.")]
public bool ForceDMG { get ; set ; }
[System.ComponentModel.Description("Emulate GBA hardware running a CGB game, instead of CGB hardware. Relevant only for titles that detect the presense of a GBA, such as Shantae.")]
public bool GBACGB { get ; set ; }
[System.ComponentModel.Description("Use special compatibility hacks for certain multicart games. Relevant only for specific multicarts.")]
public bool MulticartCompat { get ; set ; }
2013-12-23 02:51:41 +00:00
public static GambatteSyncSettings GetDefaults ( )
{
2013-12-23 16:58:20 +00:00
return new GambatteSyncSettings
{
ForceDMG = false ,
GBACGB = false ,
MulticartCompat = false
} ;
2013-12-23 02:51:41 +00:00
}
public GambatteSyncSettings Clone ( )
{
2013-12-23 16:58:20 +00:00
// this does include anonymous backing fields for auto properties
2013-12-23 02:51:41 +00:00
return ( GambatteSyncSettings ) MemberwiseClone ( ) ;
}
}
2012-09-08 21:36:04 +00:00
}
}