2012-09-08 21:36:04 +00:00
using System ;
using System.Collections.Generic ;
2014-07-03 19:20:34 +00:00
using System.ComponentModel ;
2012-09-08 21:36:04 +00:00
using System.IO ;
2014-07-03 19:20:34 +00:00
using BizHawk.Common.BufferExtensions ;
2013-11-04 01:06:36 +00:00
using BizHawk.Emulation.Common ;
2014-07-14 16:10:45 +00:00
using BizHawk.Common ;
2014-07-03 19:20:34 +00:00
2014-05-11 20:48:19 +00:00
using Newtonsoft.Json ;
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" ,
2014-06-01 01:57:22 +00:00
"" ,
2014-04-25 01:19:57 +00:00
isPorted : true ,
2014-06-01 01:57:22 +00:00
isReleased : true ,
portedVersion : "SVN 344" ,
portedUrl : "http://gambatte.sourceforge.net/"
2014-04-25 01:19:57 +00:00
) ]
2014-11-30 20:29:30 +00:00
public class Gameboy : IEmulator , IVideoProvider , ISyncSoundProvider , ISaveRam , IStatable , IInputPollable ,
2014-10-19 01:22:47 +00:00
IMemoryDomains , IDebuggable , ISettable < Gameboy . GambatteSettings , Gameboy . GambatteSyncSettings >
2012-09-08 21:36:04 +00:00
{
2014-08-04 22:25:07 +00:00
#region ALL SAVESTATEABLE STATE GOES HERE
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
2014-08-04 22:25:07 +00:00
public int Frame { get ; set ; }
public int LagCount { get ; set ; }
public bool IsLagFrame { get ; private set ; }
2014-12-04 02:04:14 +00:00
private InputCallbackSystem _inputCallbacks = new InputCallbackSystem ( ) ;
2014-12-04 01:46:59 +00:00
// low priority TODO: due to certain aspects of the core implementation,
// we don't smartly use the ActiveChanged event here.
public IInputCallbackSystem InputCallbacks { get { return _inputCallbacks ; } }
2014-12-04 00:43:12 +00:00
2014-12-04 02:04:14 +00:00
/// <summary>
/// for use in dual core
/// </summary>
/// <param name="ics"></param>
public void ConnectInputCallbackSystem ( InputCallbackSystem ics )
{
_inputCallbacks = ics ;
}
2014-08-04 22:25:07 +00:00
// all cycle counts are relative to a 2*1024*1024 mhz refclock
/// <summary>
/// total cycles actually executed
/// </summary>
private ulong _cycleCount = 0 ;
/// <summary>
/// number of extra cycles we overran in the last frame
/// </summary>
private uint frameOverflow = 0 ;
public ulong CycleCount { get { return _cycleCount ; } }
#endregion
/// <summary>
/// the nominal length of one frame
/// </summary>
private const uint TICKSINFRAME = 35112 ;
/// <summary>
/// number of ticks per second
/// </summary>
private const uint TICKSPERSECOND = 2097152 ;
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
2014-05-12 17:24:43 +00:00
#region RTC
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
2014-05-12 17:24:43 +00:00
/// <summary>
/// if true, RTC will run off of real elapsed time
/// </summary>
bool real_rtc_time = false ;
2013-05-09 23:15:59 +00:00
LibGambatte . RTCCallback TimeCallback ;
2014-05-12 17:24:43 +00:00
static long GetUnixNow ( )
{
// because internally the RTC works off of relative time, we don't need to base
// this off of any particular canonical epoch.
return DateTime . UtcNow . Ticks / 10000000L - 60000000000L ;
}
2013-11-24 17:32:46 +00:00
uint GetCurrentTime ( )
2013-05-09 23:15:59 +00:00
{
2014-05-12 17:24:43 +00:00
if ( real_rtc_time )
{
return ( uint ) GetUnixNow ( ) ;
}
else
{
ulong fn = ( ulong ) Frame ;
// as we're exactly tracking cpu cycles, this can be pretty accurate
fn * = 4389 ;
fn / = 262144 ;
fn + = zerotime ;
return ( uint ) fn ;
}
}
uint GetInitialTime ( )
{
if ( real_rtc_time )
return ( uint ) GetUnixNow ( ) ;
else
// setting the initial boot time to 0 will cause our zerotime
// to function as an initial offset, which is what we want
return 0 ;
2013-05-09 23:15:59 +00:00
}
2014-05-12 17:24:43 +00:00
#endregion
2014-08-23 19:06:37 +00:00
[CoreConstructor("GB", "GBC")]
2014-09-12 15:39:04 +00:00
public Gameboy ( CoreComm comm , GameInfo game , byte [ ] file , object Settings , object SyncSettings , bool deterministic )
2012-09-08 21:36:04 +00:00
{
2014-12-04 03:38:30 +00:00
ServiceProvider = new BasicServiceProvider ( this ) ;
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 ;
2014-09-12 15:39:04 +00:00
ThrowExceptionForBadRom ( file ) ;
BoardName = MapperName ( file ) ;
2012-09-24 20:20:21 +00:00
2014-05-12 17:24:43 +00:00
DeterministicEmulation = deterministic ;
2012-09-09 21:15:54 +00:00
GambatteState = LibGambatte . gambatte_create ( ) ;
if ( GambatteState = = IntPtr . Zero )
2014-08-01 14:56:23 +00:00
throw new InvalidOperationException ( "gambatte_create() returned null???" ) ;
2012-09-09 21:15:54 +00:00
2013-12-09 20:50:21 +00:00
try
{
2014-07-14 16:10:45 +00:00
this . _SyncSettings = ( GambatteSyncSettings ) SyncSettings ? ? new GambatteSyncSettings ( ) ;
2014-05-12 17:24:43 +00:00
// copy over non-loadflag syncsettings now; they won't take effect if changed later
2014-07-14 16:10:45 +00:00
zerotime = ( uint ) this . _SyncSettings . RTCInitialTime ;
real_rtc_time = DeterministicEmulation ? false : this . _SyncSettings . RealTimeRTC ;
2013-12-23 02:51:41 +00:00
2013-12-09 20:50:21 +00:00
LibGambatte . LoadFlags flags = 0 ;
2012-09-15 16:14:03 +00:00
2014-07-14 16:10:45 +00:00
if ( this . _SyncSettings . ForceDMG )
2013-12-09 20:50:21 +00:00
flags | = LibGambatte . LoadFlags . FORCE_DMG ;
2014-07-14 16:10:45 +00:00
if ( this . _SyncSettings . GBACGB )
2013-12-09 20:50:21 +00:00
flags | = LibGambatte . LoadFlags . GBA_CGB ;
2014-07-14 16:10:45 +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
2014-09-12 15:39:04 +00:00
if ( LibGambatte . gambatte_load ( GambatteState , file , ( uint ) file . Length , GetCurrentTime ( ) , flags ) ! = 0 )
2014-08-01 14:56:23 +00:00
throw new InvalidOperationException ( "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)
2014-10-19 01:22:47 +00:00
PutSettings ( ( GambatteSettings ) Settings ? ? new GambatteSettings ( ) ) ;
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 ,
2014-09-12 15:39:04 +00:00
file . HashSHA1 ( ) ,
file . HashMD5 ( ) ) ;
2013-05-09 23:15:59 +00:00
2014-06-16 15:59:39 +00:00
{
byte [ ] buff = new byte [ 32 ] ;
LibGambatte . gambatte_romtitle ( GambatteState , buff ) ;
string romname = System . Text . Encoding . ASCII . GetString ( buff ) ;
Console . WriteLine ( "Core reported rom name: {0}" , romname ) ;
}
2013-12-09 20:50:21 +00:00
TimeCallback = new LibGambatte . RTCCallback ( GetCurrentTime ) ;
LibGambatte . gambatte_setrtccallback ( GambatteState , TimeCallback ) ;
2014-05-10 04:22:12 +00:00
NewSaveCoreSetBuff ( ) ;
2013-12-09 20:50:21 +00:00
}
catch
{
Dispose ( ) ;
throw ;
}
2012-09-08 21:36:04 +00:00
}
2014-12-04 03:38:30 +00:00
public IEmulatorServiceProvider ServiceProvider { get ; private set ; }
2014-08-04 22:25:07 +00:00
#region controller
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 ( )
{
2014-12-04 00:43:12 +00:00
InputCallbacks . Call ( ) ;
2012-09-09 21:57:15 +00:00
IsLagFrame = false ;
return CurrentButtons ;
}
2014-08-04 22:25:07 +00:00
#endregion
#region debug
2014-11-23 16:22:02 +00:00
public IDictionary < 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
}
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 ( ) ;
}
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
2014-08-04 22:25:07 +00:00
#endregion
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-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-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
}
2012-12-29 15:48:30 +00:00
public void FrameAdvance ( bool render , bool rendersound )
{
FrameAdvancePrep ( ) ;
2014-08-04 22:25:07 +00:00
if ( _SyncSettings . EqualLengthFrames )
2014-04-29 23:31:25 +00:00
{
2014-08-04 22:25:07 +00:00
while ( true )
{
// target number of samples to emit: length of 1 frame minus whatever overflow
uint samplesEmitted = TICKSINFRAME - frameOverflow ;
System . Diagnostics . Debug . Assert ( samplesEmitted * 2 < = soundbuff . Length ) ;
if ( LibGambatte . gambatte_runfor ( GambatteState , soundbuff , ref samplesEmitted ) > 0 )
LibGambatte . gambatte_blitto ( GambatteState , VideoBuffer , 160 ) ;
// account for actual number of samples emitted
_cycleCount + = ( ulong ) samplesEmitted ;
frameOverflow + = samplesEmitted ;
2014-11-16 21:56:32 +00:00
if ( rendersound & & ! Muted )
2014-08-04 22:25:07 +00:00
{
ProcessSound ( ( int ) samplesEmitted ) ;
}
if ( frameOverflow > = TICKSINFRAME )
{
frameOverflow - = TICKSINFRAME ;
break ;
}
}
}
else
{
// target number of samples to emit: always 59.7fps
// runfor() always ends after creating a video frame, so sync-up is guaranteed
// when the display has been off, some frames can be markedly shorter than expected
uint samplesEmitted = TICKSINFRAME ;
2014-05-03 03:05:34 +00:00
if ( LibGambatte . gambatte_runfor ( GambatteState , soundbuff , ref samplesEmitted ) > 0 )
LibGambatte . gambatte_blitto ( GambatteState , VideoBuffer , 160 ) ;
2012-12-29 15:48:30 +00:00
2014-04-29 23:31:25 +00:00
_cycleCount + = ( ulong ) samplesEmitted ;
2014-08-04 22:25:07 +00:00
frameOverflow = 0 ;
2014-11-16 21:56:32 +00:00
if ( rendersound & & ! Muted )
2014-04-29 23:31:25 +00:00
{
2014-08-04 22:25:07 +00:00
ProcessSound ( ( int ) samplesEmitted ) ;
2014-04-29 23:31:25 +00:00
}
2013-10-25 01:00:31 +00:00
}
2012-12-29 15:48:30 +00:00
2014-11-16 21:56:32 +00:00
if ( rendersound & & ! Muted )
2014-04-30 03:46:37 +00:00
ProcessSoundEnd ( ) ;
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 )
2014-08-01 14:56:23 +00:00
throw new ArgumentException ( "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 ;
2014-10-29 18:58:43 +00:00
case 0x0b : throw new UnsupportedGameException ( "\"MM01\" Mapper not supported!" ) ;
case 0x0c : throw new UnsupportedGameException ( "\"MM01\" Mapper not supported!" ) ;
case 0x0d : throw new UnsupportedGameException ( "\"MM01\" Mapper not supported!" ) ;
2012-09-24 20:20:21 +00:00
case 0x0f : break ;
case 0x10 : break ;
case 0x11 : break ;
case 0x12 : break ;
case 0x13 : break ;
2014-10-29 18:58:43 +00:00
case 0x15 : throw new UnsupportedGameException ( "\"MBC4\" Mapper not supported!" ) ;
case 0x16 : throw new UnsupportedGameException ( "\"MBC4\" Mapper not supported!" ) ;
case 0x17 : throw new UnsupportedGameException ( "\"MBC4\" Mapper not supported!" ) ;
2012-09-24 20:20:21 +00:00
case 0x19 : break ;
case 0x1a : break ;
case 0x1b : break ;
case 0x1c : break ; // rumble
case 0x1d : break ; // rumble
case 0x1e : break ; // rumble
2014-10-29 18:58:43 +00:00
case 0x20 : throw new UnsupportedGameException ( "\"MBC6\" Mapper not supported!" ) ;
case 0x22 : throw new UnsupportedGameException ( "\"MBC7\" Mapper not supported!" ) ;
2012-09-24 20:20:21 +00:00
2014-10-29 18:58:43 +00:00
case 0xfc : throw new UnsupportedGameException ( "\"Pocket Camera\" Mapper not supported!" ) ;
case 0xfd : throw new UnsupportedGameException ( "\"Bandai TAMA5\" Mapper not supported!" ) ;
case 0xfe : throw new UnsupportedGameException ( "\"HuC3\" Mapper not supported!" ) ;
2012-09-24 20:20:21 +00:00
case 0xff : break ;
2014-10-29 18:58:43 +00:00
default : throw new UnsupportedGameException ( string . Format ( "Unknown mapper: {0:x2}" , romdata [ 0x147 ] ) ) ;
2012-09-24 20:20:21 +00:00
}
return ;
}
2012-09-09 21:57:15 +00:00
2014-08-04 22:25:07 +00:00
public string SystemId { get { return "GB" ; } }
2012-09-08 21:36:04 +00:00
2013-08-24 16:54:22 +00:00
public string BoardName { get ; private set ; }
2014-05-12 17:24:43 +00:00
public bool DeterministicEmulation { get ; private set ; }
2012-09-08 21:36:04 +00:00
2012-10-14 15:10:33 +00:00
#region saveram
2014-08-13 17:52:13 +00:00
public byte [ ] CloneSaveRam ( )
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
}
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
}
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 ;
2014-08-04 22:25:07 +00:00
// reset frame counters is meant to "re-zero" emulation time wherever it was
// so these should be reset as well
_cycleCount = 0 ;
frameOverflow = 0 ;
2012-09-08 21:36:04 +00:00
}
2012-09-09 21:57:15 +00:00
#region savestates
2014-08-18 16:26:40 +00:00
byte [ ] savebuff ;
byte [ ] savebuff2 ;
2014-05-10 04:22:12 +00:00
void NewSaveCoreSetBuff ( )
{
2014-08-18 16:26:40 +00:00
savebuff = new byte [ LibGambatte . gambatte_newstatelen ( GambatteState ) ] ;
savebuff2 = new byte [ savebuff . Length + 4 + 21 ] ;
2014-05-10 04:22:12 +00:00
}
2014-08-18 17:21:17 +00:00
JsonSerializer ser = new JsonSerializer { Formatting = Formatting . Indented } ;
2014-05-11 20:48:19 +00:00
2014-05-31 04:01:07 +00:00
// other data in the text state besides core
2014-08-18 17:21:17 +00:00
internal class TextStateData
2014-05-31 04:01:07 +00:00
{
public int Frame ;
public int LagCount ;
public bool IsLagFrame ;
public ulong _cycleCount ;
public uint frameOverflow ;
}
2014-08-18 17:21:17 +00:00
internal TextState < TextStateData > SaveState ( )
2012-09-08 21:36:04 +00:00
{
2014-05-31 04:01:07 +00:00
var s = new TextState < TextStateData > ( ) ;
2014-05-11 20:48:19 +00:00
s . Prepare ( ) ;
2014-06-04 18:53:57 +00:00
var ff = s . GetFunctionPointersSave ( ) ;
2014-05-31 04:01:07 +00:00
LibGambatte . gambatte_newstatesave_ex ( GambatteState , ref ff ) ;
s . ExtraData . IsLagFrame = IsLagFrame ;
s . ExtraData . LagCount = LagCount ;
s . ExtraData . Frame = Frame ;
s . ExtraData . frameOverflow = frameOverflow ;
s . ExtraData . _cycleCount = _cycleCount ;
2014-08-18 17:21:17 +00:00
return s ;
2012-09-08 21:36:04 +00:00
}
2014-08-18 17:21:17 +00:00
internal void LoadState ( TextState < TextStateData > s )
2012-09-08 21:36:04 +00:00
{
2014-05-11 20:48:19 +00:00
s . Prepare ( ) ;
2014-06-04 18:53:57 +00:00
var ff = s . GetFunctionPointersLoad ( ) ;
2014-05-31 04:01:07 +00:00
LibGambatte . gambatte_newstateload_ex ( GambatteState , ref ff ) ;
IsLagFrame = s . ExtraData . IsLagFrame ;
LagCount = s . ExtraData . LagCount ;
Frame = s . ExtraData . Frame ;
frameOverflow = s . ExtraData . frameOverflow ;
_cycleCount = s . ExtraData . _cycleCount ;
2012-09-08 21:36:04 +00:00
}
2014-08-18 17:21:17 +00:00
public void SaveStateText ( System . IO . TextWriter writer )
{
var s = SaveState ( ) ;
ser . Serialize ( writer , s ) ;
// write extra copy of stuff we don't use
writer . WriteLine ( ) ;
writer . WriteLine ( "Frame {0}" , Frame ) ;
}
public void LoadStateText ( System . IO . TextReader reader )
{
var s = ( TextState < TextStateData > ) ser . Deserialize ( reader , typeof ( TextState < TextStateData > ) ) ;
LoadState ( s ) ;
}
2012-09-08 21:36:04 +00:00
public void SaveStateBinary ( System . IO . BinaryWriter writer )
{
2014-08-18 16:26:40 +00:00
if ( ! LibGambatte . gambatte_newstatesave ( GambatteState , savebuff , savebuff . Length ) )
throw new Exception ( "gambatte_newstatesave() returned false" ) ;
2012-09-09 18:47:00 +00:00
2014-08-18 16:26:40 +00:00
writer . Write ( savebuff . Length ) ;
writer . Write ( savebuff ) ;
2012-09-11 01:46:57 +00:00
// other variables
writer . Write ( IsLagFrame ) ;
writer . Write ( LagCount ) ;
writer . Write ( Frame ) ;
2014-04-30 17:36:35 +00:00
writer . Write ( frameOverflow ) ;
writer . Write ( _cycleCount ) ;
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 ( ) ;
2014-08-18 16:26:40 +00:00
if ( length ! = savebuff . Length )
throw new InvalidOperationException ( "Savestate buffer size mismatch!" ) ;
2012-09-08 21:48:46 +00:00
2014-08-18 16:26:40 +00:00
reader . Read ( savebuff , 0 , savebuff . Length ) ;
if ( ! LibGambatte . gambatte_newstateload ( GambatteState , savebuff , savebuff . Length ) )
throw new Exception ( "gambatte_newstateload() returned false" ) ;
2012-09-11 01:46:57 +00:00
// other variables
IsLagFrame = reader . ReadBoolean ( ) ;
LagCount = reader . ReadInt32 ( ) ;
Frame = reader . ReadInt32 ( ) ;
2014-04-30 17:36:35 +00:00
frameOverflow = reader . ReadUInt32 ( ) ;
_cycleCount = reader . ReadUInt64 ( ) ;
2012-09-08 21:36:04 +00:00
}
public byte [ ] SaveStateBinary ( )
{
2014-08-18 16:26:40 +00:00
MemoryStream ms = new MemoryStream ( savebuff2 ) ;
2012-09-09 18:47:00 +00:00
BinaryWriter bw = new BinaryWriter ( ms ) ;
SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
2014-08-18 16:26:40 +00:00
if ( ms . Position ! = savebuff2 . Length )
throw new InvalidOperationException ( ) ;
ms . Close ( ) ;
return savebuff2 ;
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
2014-08-15 21:21:17 +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
2014-08-18 16:26:40 +00:00
if ( data ! = IntPtr . Zero & & length > 0 )
_MemoryDomains . Add ( MemoryDomain . FromIntPtr ( name , length , MemoryDomain . Endian . Little , data ) ) ;
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: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-09-11 17:37:17 +00:00
#endregion
2012-11-05 01:34:11 +00:00
#region ppudebug
2014-08-04 22:25:07 +00:00
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 ; }
}
2014-04-30 23:48:37 +00:00
public int VirtualHeight
{
get { return 144 ; }
}
2012-09-08 21:36:04 +00:00
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>
2014-08-04 22:25:07 +00:00
short [ ] soundbuff = new short [ ( 35112 + 2064 ) * 2 ] ;
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 ;
2014-04-30 03:46:37 +00:00
uint blipAccumulate ;
2013-10-25 01:00:31 +00:00
2014-08-04 22:25:07 +00:00
private void ProcessSound ( int nsamp )
2013-10-25 01:00:31 +00:00
{
2014-08-04 22:25:07 +00:00
for ( uint i = 0 ; i < nsamp ; i + + )
2013-10-25 01:00:31 +00:00
{
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 ;
2014-04-30 03:46:37 +00:00
blipL . AddDelta ( blipAccumulate , diff ) ;
2014-03-05 05:03:13 +00:00
}
curr = soundbuff [ i * 2 + 1 ] ;
if ( curr ! = latchR )
{
int diff = latchR - curr ;
latchR = curr ;
2014-04-30 03:46:37 +00:00
blipR . AddDelta ( blipAccumulate , diff ) ;
2013-10-25 01:00:31 +00:00
}
2014-04-30 03:46:37 +00:00
blipAccumulate + + ;
2013-10-25 01:00:31 +00:00
}
2014-04-30 03:46:37 +00:00
}
private void ProcessSoundEnd ( )
{
2014-08-04 22:25:07 +00:00
blipL . EndFrame ( blipAccumulate ) ;
blipR . EndFrame ( blipAccumulate ) ;
2014-04-30 03:46:37 +00:00
blipAccumulate = 0 ;
2013-10-25 01:00:31 +00:00
2014-03-05 05:03:13 +00:00
soundoutbuffcontains = blipL . SamplesAvailable ( ) ;
if ( soundoutbuffcontains ! = blipR . SamplesAvailable ( ) )
2014-08-04 22:25:07 +00:00
throw new InvalidOperationException ( "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
}
2012-09-09 00:41:11 +00:00
void InitSound ( )
{
2014-03-05 05:03:13 +00:00
blipL = new BlipBuffer ( 1024 ) ;
2014-08-04 22:25:07 +00:00
blipL . SetRates ( TICKSPERSECOND , 44100 ) ;
2014-03-05 05:03:13 +00:00
blipR = new BlipBuffer ( 1024 ) ;
2014-08-04 22:25:07 +00:00
blipR . SetRates ( TICKSPERSECOND , 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
{
2014-08-04 22:25:07 +00:00
soundoutbuffcontains = 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
}
2014-04-29 23:31:25 +00:00
2014-11-16 21:56:32 +00:00
public bool Muted { get { return _Settings . Muted ; } }
2012-09-09 00:41:11 +00:00
#endregion
2012-09-09 21:57:15 +00:00
2014-04-29 23:31:25 +00:00
#region Settings
2014-07-14 16:10:45 +00:00
GambatteSettings _Settings ;
GambatteSyncSettings _SyncSettings ;
2013-12-23 02:51:41 +00:00
2014-10-19 01:22:47 +00:00
public GambatteSettings GetSettings ( ) { return _Settings . Clone ( ) ; }
public GambatteSyncSettings GetSyncSettings ( ) { return _SyncSettings . Clone ( ) ; }
public bool PutSettings ( GambatteSettings o )
2013-12-23 02:51:41 +00:00
{
2014-10-19 01:22:47 +00:00
_Settings = o ;
2013-12-23 02:51:41 +00:00
if ( IsCGBMode ( ) )
2014-07-14 16:10:45 +00:00
SetCGBColors ( _Settings . CGBColors ) ;
2013-12-23 02:51:41 +00:00
else
2014-07-14 16:10:45 +00:00
ChangeDMGColors ( _Settings . GBPalette ) ;
2013-12-23 02:51:41 +00:00
return false ;
}
2014-10-19 01:22:47 +00:00
public bool PutSyncSettings ( GambatteSyncSettings o )
2013-12-23 02:51:41 +00:00
{
2014-10-19 01:22:47 +00:00
bool ret = GambatteSyncSettings . NeedsReboot ( _SyncSettings , o ) ;
_SyncSettings = o ;
2013-12-23 16:58:20 +00:00
return ret ;
2013-12-23 02:51:41 +00:00
}
public class GambatteSettings
{
2014-07-14 16:56:23 +00:00
private static readonly int [ ] DefaultPalette = new [ ]
2013-12-23 02:51:41 +00:00
{
10798341 , 8956165 , 1922333 , 337157 ,
10798341 , 8956165 , 1922333 , 337157 ,
10798341 , 8956165 , 1922333 , 337157
} ;
2014-07-14 16:56:23 +00:00
public int [ ] GBPalette ;
public GBColors . ColorType CGBColors ;
2014-11-16 21:56:32 +00:00
/// <summary>
/// true to mute all audio
/// </summary>
public bool Muted ;
2014-07-14 16:56:23 +00:00
public GambatteSettings ( )
{
GBPalette = ( int [ ] ) DefaultPalette . Clone ( ) ;
CGBColors = GBColors . ColorType . gambatte ;
2013-12-23 02:51:41 +00:00
}
public GambatteSettings Clone ( )
{
var ret = ( GambatteSettings ) MemberwiseClone ( ) ;
ret . GBPalette = ( int [ ] ) GBPalette . Clone ( ) ;
return ret ;
}
}
public class GambatteSyncSettings
{
2014-07-20 00:16:40 +00:00
[DisplayName("Force DMG Mode")]
2014-05-12 04:08:22 +00:00
[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.")]
2014-05-12 17:24:43 +00:00
[DefaultValue(false)]
2013-12-23 16:58:20 +00:00
public bool ForceDMG { get ; set ; }
2014-07-20 00:16:40 +00:00
[DisplayName("CGB in GBA")]
2014-05-12 04:08:22 +00:00
[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.")]
2014-05-12 17:24:43 +00:00
[DefaultValue(false)]
2013-12-23 16:58:20 +00:00
public bool GBACGB { get ; set ; }
2014-07-20 00:16:40 +00:00
[DisplayName("Multicart Compatibility")]
2014-05-12 04:08:22 +00:00
[Description("Use special compatibility hacks for certain multicart games. Relevant only for specific multicarts.")]
2014-05-12 17:24:43 +00:00
[DefaultValue(false)]
2013-12-23 16:58:20 +00:00
public bool MulticartCompat { get ; set ; }
2014-07-20 00:16:40 +00:00
[DisplayName("Realtime RTC")]
2014-05-12 17:24:43 +00:00
[Description("If true, the real time clock in MBC3 games will reflect real time, instead of emulated time. Ignored (treated as false) when a movie is recording.")]
[DefaultValue(false)]
public bool RealTimeRTC { get ; set ; }
2014-07-20 00:16:40 +00:00
[DisplayName("RTC Initial Time")]
2014-05-12 17:24:43 +00:00
[Description("Set the initial RTC time in terms of elapsed seconds. Only used when RealTimeRTC is false.")]
[DefaultValue(0)]
public int RTCInitialTime
{
get { return _RTCInitialTime ; }
set { _RTCInitialTime = Math . Max ( 0 , Math . Min ( 1024 * 24 * 60 * 60 , value ) ) ; }
}
[JsonIgnore]
int _RTCInitialTime ;
2013-12-23 02:51:41 +00:00
2014-08-04 22:25:07 +00:00
[DisplayName("Equal Length Frames")]
[Description("When false, emulation frames sync to vblank. Only useful for high level TASing.")]
[DefaultValue(true)]
public bool EqualLengthFrames { get { return _EqualLengthFrames ; } set { _EqualLengthFrames = value ; } }
[JsonIgnore]
[DeepEqualsIgnore]
private bool _EqualLengthFrames ;
2014-07-14 16:10:45 +00:00
public GambatteSyncSettings ( )
2013-12-23 02:51:41 +00:00
{
2014-07-14 16:10:45 +00:00
SettingsUtil . SetDefaultValues ( this ) ;
2013-12-23 02:51:41 +00:00
}
public GambatteSyncSettings Clone ( )
{
return ( GambatteSyncSettings ) MemberwiseClone ( ) ;
}
2014-08-03 22:19:55 +00:00
public static bool NeedsReboot ( GambatteSyncSettings x , GambatteSyncSettings y )
{
return ! DeepEquality . DeepEquals ( x , y ) ;
}
2013-12-23 02:51:41 +00:00
}
2014-04-29 23:31:25 +00:00
#endregion
2012-09-08 21:36:04 +00:00
}
}