2015-06-04 02:04:42 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using BizHawk.Common ;
using BizHawk.Emulation.Common ;
using System.Runtime.InteropServices ;
2015-06-06 17:34:19 +00:00
using System.IO ;
2015-06-13 18:01:26 +00:00
using System.ComponentModel ;
2015-06-04 02:04:42 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.GBA
{
2016-02-07 17:51:00 +00:00
[CoreAttributes("mGBA", "endrift", true, true, "0.4.0", "https://mgba.io/", false)]
2015-08-06 00:12:09 +00:00
[ServiceNotApplicable(typeof(IDriveLight), typeof(IRegionable))]
2016-02-21 13:54:00 +00:00
public class MGBAHawk : IEmulator , IVideoProvider , ISyncSoundProvider , IGBAGPUViewable , ISaveRam , IStatable , IInputPollable , ISettable < MGBAHawk . Settings , MGBAHawk . SyncSettings >
2015-06-04 02:04:42 +00:00
{
IntPtr core ;
[CoreConstructor("GBA")]
2016-02-21 13:54:00 +00:00
public MGBAHawk ( byte [ ] file , CoreComm comm , SyncSettings syncSettings , Settings settings , bool deterministic )
2015-06-04 02:04:42 +00:00
{
2015-06-13 18:01:26 +00:00
_syncSettings = syncSettings ? ? new SyncSettings ( ) ;
2016-02-21 13:54:00 +00:00
_settings = settings ? ? new Settings ( ) ;
2015-06-13 18:01:26 +00:00
DeterministicEmulation = deterministic ;
byte [ ] bios = comm . CoreFileProvider . GetFirmware ( "GBA" , "Bios" , false ) ;
DeterministicEmulation & = bios ! = null ;
if ( DeterministicEmulation ! = deterministic )
{
throw new InvalidOperationException ( "A BIOS is required for deterministic recordings!" ) ;
}
if ( ! DeterministicEmulation & & bios ! = null & & ! _syncSettings . RTCUseRealTime & & ! _syncSettings . SkipBios )
2015-06-05 00:12:12 +00:00
{
2015-06-13 18:01:26 +00:00
// in these situations, this core is deterministic even though it wasn't asked to be
DeterministicEmulation = true ;
2015-06-05 00:12:12 +00:00
}
if ( bios ! = null & & bios . Length ! = 16384 )
{
throw new InvalidOperationException ( "BIOS must be exactly 16384 bytes!" ) ;
}
core = LibmGBA . BizCreate ( bios ) ;
2015-06-04 02:04:42 +00:00
if ( core = = IntPtr . Zero )
2015-06-05 00:12:12 +00:00
{
throw new InvalidOperationException ( "BizCreate() returned NULL! Bad BIOS?" ) ;
}
2015-06-04 02:04:42 +00:00
try
{
if ( ! LibmGBA . BizLoad ( core , file , file . Length ) )
{
2015-06-05 00:12:12 +00:00
throw new InvalidOperationException ( "BizLoad() returned FALSE! Bad ROM?" ) ;
2015-06-04 02:04:42 +00:00
}
2015-06-13 18:01:26 +00:00
if ( ! DeterministicEmulation & & _syncSettings . SkipBios )
{
LibmGBA . BizSkipBios ( core ) ;
}
2016-03-27 21:35:34 +00:00
CreateMemoryDomains ( file . Length ) ;
2015-06-05 00:43:41 +00:00
var ser = new BasicServiceProvider ( this ) ;
ser . Register < IDisassemblable > ( new ArmV4Disassembler ( ) ) ;
2016-03-27 21:35:34 +00:00
ser . Register < IMemoryDomains > ( MemoryDomains ) ;
2015-06-05 00:43:41 +00:00
ServiceProvider = ser ;
CoreComm = comm ;
2015-06-06 17:34:19 +00:00
2015-06-13 18:01:26 +00:00
CoreComm . VsyncNum = 262144 ;
CoreComm . VsyncDen = 4389 ;
CoreComm . NominalWidth = 240 ;
CoreComm . NominalHeight = 160 ;
2015-06-06 17:34:19 +00:00
InitStates ( ) ;
2015-06-04 02:04:42 +00:00
}
catch
{
LibmGBA . BizDestroy ( core ) ;
throw ;
}
}
2016-03-27 21:35:34 +00:00
MemoryDomainList MemoryDomains ;
2015-06-04 02:04:42 +00:00
public IEmulatorServiceProvider ServiceProvider { get ; private set ; }
public ControllerDefinition ControllerDefinition { get { return GBA . GBAController ; } }
public IController Controller { get ; set ; }
public void FrameAdvance ( bool render , bool rendersound = true )
{
2015-06-04 22:47:51 +00:00
Frame + + ;
2015-06-04 23:30:24 +00:00
if ( Controller [ "Power" ] )
2016-03-27 21:35:34 +00:00
{
2015-06-04 23:30:24 +00:00
LibmGBA . BizReset ( core ) ;
2016-03-27 21:35:34 +00:00
//BizReset caused memorydomain pointers to change.
WireMemoryDomainPointers ( ) ;
}
2015-06-04 23:30:24 +00:00
2015-06-10 01:19:09 +00:00
IsLagFrame = LibmGBA . BizAdvance ( core , VBANext . GetButtons ( Controller ) , videobuff , ref nsamp , soundbuff ,
2015-06-13 18:01:26 +00:00
RTCTime ( ) ,
2015-06-06 22:23:42 +00:00
( short ) Controller . GetFloat ( "Tilt X" ) ,
( short ) Controller . GetFloat ( "Tilt Y" ) ,
( short ) Controller . GetFloat ( "Tilt Z" ) ,
( byte ) ( 255 - Controller . GetFloat ( "Light Sensor" ) ) ) ;
2015-06-13 18:01:26 +00:00
2015-06-10 01:19:09 +00:00
if ( IsLagFrame )
2015-06-13 18:01:26 +00:00
LagCount + + ;
// this should be called in hblank on the appropriate line, but until we implement that, just do it here
if ( _scanlinecb ! = null )
_scanlinecb ( ) ;
2015-06-04 02:04:42 +00:00
}
public int Frame { get ; private set ; }
public string SystemId { get { return "GBA" ; } }
2015-06-13 18:01:26 +00:00
public bool DeterministicEmulation { get ; private set ; }
2015-06-04 02:04:42 +00:00
public string BoardName { get { return null ; } }
public void ResetCounters ( )
{
2015-06-04 22:47:51 +00:00
Frame = 0 ;
2015-06-13 18:01:26 +00:00
LagCount = 0 ;
2015-06-06 17:34:19 +00:00
IsLagFrame = false ;
2015-06-04 02:04:42 +00:00
}
public CoreComm CoreComm { get ; private set ; }
public void Dispose ( )
{
if ( core ! = IntPtr . Zero )
{
LibmGBA . BizDestroy ( core ) ;
core = IntPtr . Zero ;
}
}
2015-06-04 22:47:51 +00:00
#region IVideoProvider
2015-06-04 02:04:42 +00:00
public int VirtualWidth { get { return 240 ; } }
public int VirtualHeight { get { return 160 ; } }
public int BufferWidth { get { return 240 ; } }
public int BufferHeight { get { return 160 ; } }
public int BackgroundColor
{
get { return unchecked ( ( int ) 0xff000000 ) ; }
}
public int [ ] GetVideoBuffer ( )
{
return videobuff ;
}
2015-06-04 22:47:51 +00:00
private readonly int [ ] videobuff = new int [ 240 * 160 ] ;
#endregion
2015-06-04 02:04:42 +00:00
2015-06-04 22:47:51 +00:00
#region ISoundProvider
private readonly short [ ] soundbuff = new short [ 2048 ] ;
private int nsamp ;
public void GetSamples ( out short [ ] samples , out int nsamp )
{
nsamp = this . nsamp ;
samples = soundbuff ;
DiscardSamples ( ) ;
}
public void DiscardSamples ( )
{
nsamp = 0 ;
}
public ISoundProvider SoundProvider { get { throw new InvalidOperationException ( ) ; } }
public ISyncSoundProvider SyncSoundProvider { get { return this ; } }
public bool StartAsyncSound ( ) { return false ; }
public void EndAsyncSound ( ) { }
#endregion
2015-06-05 00:43:41 +00:00
#region IMemoryDomains
2016-02-01 23:38:10 +00:00
unsafe byte PeekWRAM ( IntPtr xwram , long addr ) { return ( ( byte * ) xwram . ToPointer ( ) ) [ addr ] ; }
unsafe void PokeWRAM ( IntPtr xwram , long addr , byte value ) { ( ( byte * ) xwram . ToPointer ( ) ) [ addr ] = value ; }
2015-09-29 04:54:44 +00:00
2016-03-27 21:35:34 +00:00
void WireMemoryDomainPointers ( )
2015-06-05 00:43:41 +00:00
{
var s = new LibmGBA . MemoryAreas ( ) ;
LibmGBA . BizGetMemoryAreas ( core , s ) ;
2016-03-27 21:35:34 +00:00
var LE = MemoryDomain . Endian . Little ;
MemoryDomains [ "IWRAM" ] . SetDelegatesForIntPtr ( MemoryDomains [ "IWRAM" ] . Size , LE , s . wram , true , 4 ) ;
MemoryDomains [ "EWRAM" ] . SetDelegatesForIntPtr ( MemoryDomains [ "EWRAM" ] . Size , LE , s . wram , true , 4 ) ;
MemoryDomains [ "BIOS" ] . SetDelegatesForIntPtr ( MemoryDomains [ "BIOS" ] . Size , LE , s . bios , false , 4 ) ;
MemoryDomains [ "PALRAM" ] . SetDelegatesForIntPtr ( MemoryDomains [ "PALRAM" ] . Size , LE , s . palram , false , 4 ) ;
MemoryDomains [ "VRAM" ] . SetDelegatesForIntPtr ( MemoryDomains [ "VRAM" ] . Size , LE , s . vram , true , 4 ) ;
MemoryDomains [ "OAM" ] . SetDelegatesForIntPtr ( MemoryDomains [ "OAM" ] . Size , LE , s . oam , false , 4 ) ;
MemoryDomains [ "ROM" ] . SetDelegatesForIntPtr ( MemoryDomains [ "ROM" ] . Size , LE , s . rom , false , 4 ) ;
2015-06-05 00:43:41 +00:00
2015-09-29 04:54:44 +00:00
// special combined ram memory domain
2016-03-27 21:35:34 +00:00
MemoryDomains [ "Combined WRAM" ] . SetPeekPokeDelegates (
delegate ( long addr )
{
LibmGBA . BizGetMemoryAreas ( core , s ) ;
if ( addr < 0 | | addr > = ( 256 + 32 ) * 1024 )
throw new IndexOutOfRangeException ( ) ;
if ( addr > = 256 * 1024 )
return PeekWRAM ( s . iwram , addr & 32767 ) ;
else
return PeekWRAM ( s . wram , addr ) ;
} ,
delegate ( long addr , byte val )
{
if ( addr < 0 | | addr > = ( 256 + 32 ) * 1024 )
throw new IndexOutOfRangeException ( ) ;
if ( addr > = 256 * 1024 )
PokeWRAM ( s . iwram , addr & 32767 , val ) ;
else
PokeWRAM ( s . wram , addr , val ) ;
}
) ;
2015-09-29 04:54:44 +00:00
2015-06-05 00:43:41 +00:00
_gpumem = new GBAGPUMemoryAreas
{
mmio = s . mmio ,
oam = s . oam ,
palram = s . palram ,
vram = s . vram
} ;
2016-03-27 21:35:34 +00:00
}
2015-06-05 00:43:41 +00:00
2016-03-27 21:35:34 +00:00
private void CreateMemoryDomains ( int romsize )
{
var LE = MemoryDomain . Endian . Little ;
var mm = new List < MemoryDomain > ( ) ;
mm . Add ( new MemoryDomain ( "IWRAM" , 32 * 1024 , LE , null , null , 4 ) ) ;
mm . Add ( new MemoryDomain ( "EWRAM" , 256 * 1024 , LE , null , null , 4 ) ) ;
mm . Add ( new MemoryDomain ( "BIOS" , 16 * 1024 , LE , null , null , 4 ) ) ;
mm . Add ( new MemoryDomain ( "PALRAM" , 1024 , LE , null , null , 4 ) ) ;
mm . Add ( new MemoryDomain ( "VRAM" , 96 * 1024 , LE , null , null , 4 ) ) ;
mm . Add ( new MemoryDomain ( "OAM" , 1024 , LE , null , null , 4 ) ) ;
mm . Add ( new MemoryDomain ( "ROM" , romsize , LE , null , null , 4 ) ) ;
mm . Add ( new MemoryDomain ( "Combined WRAM" , ( 256 + 32 ) * 1024 , LE , null , null , 4 ) ) ;
MemoryDomains = new MemoryDomainList ( mm ) ;
WireMemoryDomainPointers ( ) ;
2015-06-05 00:43:41 +00:00
}
#endregion
2015-06-13 18:01:26 +00:00
private Action _scanlinecb ;
2015-06-05 00:43:41 +00:00
private GBAGPUMemoryAreas _gpumem ;
public GBAGPUMemoryAreas GetMemoryAreas ( )
{
return _gpumem ;
}
[FeatureNotImplemented]
public void SetScanlineCallback ( Action callback , int scanline )
{
2015-06-13 18:01:26 +00:00
_scanlinecb = callback ;
2015-06-05 00:43:41 +00:00
}
2015-06-06 12:49:31 +00:00
#region ISaveRam
public byte [ ] CloneSaveRam ( )
{
byte [ ] ret = new byte [ LibmGBA . BizGetSaveRamSize ( core ) ] ;
if ( ret . Length > 0 )
{
LibmGBA . BizGetSaveRam ( core , ret ) ;
return ret ;
}
else
{
return null ;
}
}
2015-06-06 17:42:47 +00:00
private static byte [ ] LegacyFix ( byte [ ] saveram )
{
// at one point vbanext-hawk had a special saveram format which we want to load.
var br = new BinaryReader ( new MemoryStream ( saveram , false ) ) ;
br . ReadBytes ( 8 ) ; // header;
int flashSize = br . ReadInt32 ( ) ;
int eepromsize = br . ReadInt32 ( ) ;
byte [ ] flash = br . ReadBytes ( flashSize ) ;
byte [ ] eeprom = br . ReadBytes ( eepromsize ) ;
if ( flash . Length = = 0 )
return eeprom ;
else if ( eeprom . Length = = 0 )
return flash ;
else
{
// well, isn't this a sticky situation!
return flash ; // woops
}
}
2015-06-06 12:49:31 +00:00
public void StoreSaveRam ( byte [ ] data )
{
2015-06-06 17:42:47 +00:00
if ( data . Take ( 8 ) . SequenceEqual ( Encoding . ASCII . GetBytes ( "GBABATT\0" ) ) )
{
data = LegacyFix ( data ) ;
}
2016-02-21 20:37:39 +00:00
int len = LibmGBA . BizGetSaveRamSize ( core ) ;
if ( len > data . Length )
2015-06-06 12:49:31 +00:00
{
2016-02-21 20:37:39 +00:00
byte [ ] _tmp = new byte [ len ] ;
Array . Copy ( data , _tmp , data . Length ) ;
for ( int i = data . Length ; i < len ; i + + )
_tmp [ i ] = 0xff ;
data = _tmp ;
2015-06-06 12:49:31 +00:00
}
2016-02-21 20:37:39 +00:00
else if ( len < data . Length )
2015-06-06 12:49:31 +00:00
{
2016-02-21 20:37:39 +00:00
// we could continue from this, but we don't expect it
throw new InvalidOperationException ( "Saveram will be truncated!" ) ;
2015-06-06 12:49:31 +00:00
}
LibmGBA . BizPutSaveRam ( core , data ) ;
}
public bool SaveRamModified
{
get { return LibmGBA . BizGetSaveRamSize ( core ) > 0 ; }
}
#endregion
2015-06-06 17:34:19 +00:00
private void InitStates ( )
{
2016-02-21 21:14:43 +00:00
savebuff = new byte [ LibmGBA . BizGetStateMaxSize ( core ) ] ;
2016-02-21 20:37:39 +00:00
savebuff2 = new byte [ savebuff . Length + 13 ] ;
2015-06-06 17:34:19 +00:00
}
private byte [ ] savebuff ;
2016-02-21 20:37:39 +00:00
private byte [ ] savebuff2 ;
2015-06-06 17:34:19 +00:00
public bool BinarySaveStatesPreferred
{
get { return true ; }
}
public void SaveStateText ( TextWriter writer )
{
var tmp = SaveStateBinary ( ) ;
BizHawk . Common . BufferExtensions . BufferExtensions . SaveAsHexFast ( tmp , writer ) ;
}
public void LoadStateText ( TextReader reader )
{
string hex = reader . ReadLine ( ) ;
byte [ ] state = new byte [ hex . Length / 2 ] ;
BizHawk . Common . BufferExtensions . BufferExtensions . ReadFromHexFast ( state , hex ) ;
LoadStateBinary ( new BinaryReader ( new MemoryStream ( state ) ) ) ;
}
public void SaveStateBinary ( BinaryWriter writer )
{
2016-02-21 21:14:43 +00:00
int size = LibmGBA . BizGetState ( core , savebuff , savebuff . Length ) ;
if ( size < 0 )
throw new InvalidOperationException ( "Core failed to save!" ) ;
writer . Write ( size ) ;
writer . Write ( savebuff , 0 , size ) ;
2015-06-06 17:34:19 +00:00
// other variables
writer . Write ( IsLagFrame ) ;
writer . Write ( LagCount ) ;
writer . Write ( Frame ) ;
}
public void LoadStateBinary ( BinaryReader reader )
{
int length = reader . ReadInt32 ( ) ;
2016-02-21 21:14:43 +00:00
if ( length > savebuff . Length )
{
savebuff = new byte [ length ] ;
savebuff2 = new byte [ length + 13 ] ;
}
2015-06-06 17:34:19 +00:00
reader . Read ( savebuff , 0 , length ) ;
2016-02-21 21:14:43 +00:00
if ( ! LibmGBA . BizPutState ( core , savebuff , length ) )
2016-02-07 17:51:00 +00:00
throw new InvalidOperationException ( "Core rejected the savestate!" ) ;
2015-06-06 17:34:19 +00:00
// other variables
IsLagFrame = reader . ReadBoolean ( ) ;
2015-06-13 18:01:26 +00:00
LagCount = reader . ReadInt32 ( ) ;
2015-06-06 17:34:19 +00:00
Frame = reader . ReadInt32 ( ) ;
}
public byte [ ] SaveStateBinary ( )
{
2016-02-21 20:37:39 +00:00
var ms = new MemoryStream ( savebuff2 , true ) ;
2015-06-06 17:34:19 +00:00
var bw = new BinaryWriter ( ms ) ;
SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
ms . Close ( ) ;
2016-02-21 20:37:39 +00:00
return savebuff2 ;
2015-06-06 17:34:19 +00:00
}
2015-07-09 17:05:30 +00:00
public int LagCount { get ; set ; }
2016-01-26 10:34:42 +00:00
public bool IsLagFrame { get ; set ; }
2015-06-06 17:34:19 +00:00
[FeatureNotImplemented]
public IInputCallbackSystem InputCallbacks
{
get { throw new NotImplementedException ( ) ; }
}
2015-06-13 18:01:26 +00:00
private long RTCTime ( )
{
if ( ! DeterministicEmulation & & _syncSettings . RTCUseRealTime )
{
return ( long ) DateTime . Now . Subtract ( new DateTime ( 1970 , 1 , 1 ) ) . TotalSeconds ;
}
long basetime = ( long ) _syncSettings . RTCInitialTime . Subtract ( new DateTime ( 1970 , 1 , 1 ) ) . TotalSeconds ;
long increment = Frame * 4389L > > 18 ;
return basetime + increment ;
}
2016-02-21 13:54:00 +00:00
public Settings GetSettings ( )
2015-06-13 18:01:26 +00:00
{
2016-02-21 13:54:00 +00:00
return _settings . Clone ( ) ;
2015-06-13 18:01:26 +00:00
}
2016-02-21 13:54:00 +00:00
public bool PutSettings ( Settings o )
2015-06-13 18:01:26 +00:00
{
2016-02-21 13:54:00 +00:00
LibmGBA . Layers mask = 0 ;
if ( o . DisplayBG0 ) mask | = LibmGBA . Layers . BG0 ;
if ( o . DisplayBG1 ) mask | = LibmGBA . Layers . BG1 ;
if ( o . DisplayBG2 ) mask | = LibmGBA . Layers . BG2 ;
if ( o . DisplayBG3 ) mask | = LibmGBA . Layers . BG3 ;
if ( o . DisplayOBJ ) mask | = LibmGBA . Layers . OBJ ;
LibmGBA . BizSetLayerMask ( core , mask ) ;
_settings = o ;
return false ;
2015-06-13 18:01:26 +00:00
}
2016-02-21 13:54:00 +00:00
private Settings _settings ;
public class Settings
2015-06-13 18:01:26 +00:00
{
2016-02-21 13:54:00 +00:00
[DefaultValue(true)]
public bool DisplayBG0 { get ; set ; }
[DefaultValue(true)]
public bool DisplayBG1 { get ; set ; }
[DefaultValue(true)]
public bool DisplayBG2 { get ; set ; }
[DefaultValue(true)]
public bool DisplayBG3 { get ; set ; }
[DefaultValue(true)]
public bool DisplayOBJ { get ; set ; }
public Settings Clone ( )
{
return ( Settings ) MemberwiseClone ( ) ;
}
public Settings ( )
{
SettingsUtil . SetDefaultValues ( this ) ;
}
}
public SyncSettings GetSyncSettings ( )
{
return _syncSettings . Clone ( ) ;
2015-06-13 18:01:26 +00:00
}
public bool PutSyncSettings ( SyncSettings o )
{
bool ret = SyncSettings . NeedsReboot ( o , _syncSettings ) ;
_syncSettings = o ;
return ret ;
}
private SyncSettings _syncSettings ;
public class SyncSettings
{
[DisplayName("Skip BIOS")]
[Description("Skips the BIOS intro. Not applicable when a BIOS is not provided.")]
[DefaultValue(true)]
public bool SkipBios { get ; set ; }
[DisplayName("RTC Use Real Time")]
[Description("Causes the internal clock to reflect your system clock. Only relevant when a game has an RTC chip. Forced to false for movie recording.")]
[DefaultValue(true)]
public bool RTCUseRealTime { get ; set ; }
[DisplayName("RTC Initial Time")]
[Description("The initial time of emulation. Only relevant when a game has an RTC chip and \"RTC Use Real Time\" is false.")]
[DefaultValue(typeof(DateTime), "2010-01-01")]
public DateTime RTCInitialTime { get ; set ; }
public SyncSettings ( )
{
SettingsUtil . SetDefaultValues ( this ) ;
}
public static bool NeedsReboot ( SyncSettings x , SyncSettings y )
{
return ! DeepEquality . DeepEquals ( x , y ) ;
}
public SyncSettings Clone ( )
{
return ( SyncSettings ) MemberwiseClone ( ) ;
}
}
2015-06-04 02:04:42 +00:00
}
}