2013-12-25 19:09:53 +00:00
using System ;
2014-04-14 12:25:57 +00:00
using System.Collections.Generic ;
2013-12-29 23:54:40 +00:00
using System.IO ;
2013-12-25 19:09:53 +00:00
using BizHawk.Common ;
using BizHawk.Emulation.Common ;
2014-01-21 23:54:30 +00:00
using BizHawk.Emulation.Cores ;
2013-12-29 23:54:40 +00:00
using BizHawk.Emulation.Cores.Atari.Atari2600 ;
using BizHawk.Emulation.Cores.Atari.Atari7800 ;
using BizHawk.Emulation.Cores.Calculators ;
using BizHawk.Emulation.Cores.ColecoVision ;
using BizHawk.Emulation.Cores.Computers.Commodore64 ;
2014-01-08 03:53:53 +00:00
using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES ;
2013-12-25 19:09:53 +00:00
using BizHawk.Emulation.Cores.Consoles.Sega.gpgx ;
using BizHawk.Emulation.Cores.Intellivision ;
2013-12-29 23:54:40 +00:00
using BizHawk.Emulation.Cores.Nintendo.Gameboy ;
2013-12-25 19:09:53 +00:00
using BizHawk.Emulation.Cores.Nintendo.GBA ;
using BizHawk.Emulation.Cores.Nintendo.N64 ;
2013-12-29 23:54:40 +00:00
using BizHawk.Emulation.Cores.Nintendo.NES ;
using BizHawk.Emulation.Cores.Nintendo.SNES ;
using BizHawk.Emulation.Cores.PCEngine ;
using BizHawk.Emulation.Cores.Sega.MasterSystem ;
using BizHawk.Emulation.Cores.Sega.Saturn ;
using BizHawk.Emulation.Cores.Sony.PSP ;
using BizHawk.Emulation.Cores.Sony.PSX ;
using BizHawk.Emulation.DiscSystem ;
2014-05-30 05:09:54 +00:00
using BizHawk.Emulation.Cores.WonderSwan ;
2013-12-25 19:09:53 +00:00
namespace BizHawk.Client.Common
{
public class RomLoader
{
2014-07-31 21:15:07 +00:00
public enum LoadErrorType { Unknown , MissingFirmware , XML }
2013-12-27 04:41:50 +00:00
// helper methods for the settings events
2013-12-29 23:54:40 +00:00
private object GetCoreSettings < T > ( )
2013-12-26 20:19:28 +00:00
where T : IEmulator
{
2014-08-23 19:06:37 +00:00
return GetCoreSettings ( typeof ( T ) ) ;
}
private object GetCoreSyncSettings < T > ( )
where T : IEmulator
{
return GetCoreSyncSettings ( typeof ( T ) ) ;
}
private object GetCoreSettings ( Type t )
{
var e = new SettingsLoadArgs ( t ) ;
2013-12-26 20:19:28 +00:00
if ( OnLoadSettings ! = null )
2013-12-29 23:54:40 +00:00
{
2013-12-26 20:19:28 +00:00
OnLoadSettings ( this , e ) ;
2013-12-29 23:54:40 +00:00
}
2013-12-26 20:19:28 +00:00
return e . Settings ;
}
2014-08-23 19:06:37 +00:00
private object GetCoreSyncSettings ( Type t )
2013-12-26 20:19:28 +00:00
{
2014-08-23 19:06:37 +00:00
var e = new SettingsLoadArgs ( t ) ;
2013-12-26 20:19:28 +00:00
if ( OnLoadSyncSettings ! = null )
2013-12-29 23:54:40 +00:00
{
2013-12-26 20:19:28 +00:00
OnLoadSyncSettings ( this , e ) ;
2013-12-29 23:54:40 +00:00
}
2013-12-26 20:19:28 +00:00
return e . Settings ;
}
2013-12-25 19:47:44 +00:00
2013-12-25 19:09:53 +00:00
public RomLoader ( )
{
2014-05-24 22:06:08 +00:00
2013-12-25 19:09:53 +00:00
}
2014-08-02 02:41:12 +00:00
// For not throwing errors but simply outputing information to the screen
public Action < string > MessageCallback { get ; set ; }
private void DoMessageCallback ( string message )
{
if ( MessageCallback ! = null )
{
MessageCallback ( message ) ;
}
}
2013-12-25 19:09:53 +00:00
// TODO: reconsider the need for exposing these;
public IEmulator LoadedEmulator { get ; private set ; }
public GameInfo Game { get ; private set ; }
public RomGame Rom { get ; private set ; }
public string CanonicalFullPath { get ; private set ; }
2014-05-24 22:06:08 +00:00
public bool Deterministic { get ; set ; }
2013-12-25 19:09:53 +00:00
2013-12-30 01:17:11 +00:00
public class RomErrorArgs : EventArgs
2013-12-25 19:09:53 +00:00
{
// TODO: think about naming here, what to pass, a lot of potential good information about what went wrong could go here!
2014-07-31 21:15:07 +00:00
public RomErrorArgs ( string message , string systemId , LoadErrorType type )
2013-12-25 19:09:53 +00:00
{
Message = message ;
AttemptedCoreLoad = systemId ;
2014-07-31 21:15:07 +00:00
Type = type ;
2013-12-25 19:09:53 +00:00
}
public string Message { get ; private set ; }
public string AttemptedCoreLoad { get ; private set ; }
2014-07-31 21:15:07 +00:00
public LoadErrorType Type { get ; private set ; }
2013-12-25 19:09:53 +00:00
}
2013-12-30 01:17:11 +00:00
public class SettingsLoadArgs : EventArgs
2013-12-26 20:19:28 +00:00
{
public object Settings { get ; set ; }
public Type Core { get ; private set ; }
public SettingsLoadArgs ( Type t )
{
Core = t ;
Settings = null ;
}
}
2013-12-29 23:54:40 +00:00
2013-12-26 20:19:28 +00:00
public delegate void SettingsLoadEventHandler ( object sender , SettingsLoadArgs e ) ;
public event SettingsLoadEventHandler OnLoadSettings ;
public event SettingsLoadEventHandler OnLoadSyncSettings ;
2013-12-25 19:09:53 +00:00
public delegate void LoadErrorEventHandler ( object sener , RomErrorArgs e ) ;
public event LoadErrorEventHandler OnLoadError ;
2013-12-29 23:54:40 +00:00
public Func < HawkFile , int? > ChooseArchive { get ; set ; }
2013-12-25 19:09:53 +00:00
2014-04-14 01:59:57 +00:00
public Func < RomGame , string > ChoosePlatform { get ; set ; }
2013-12-25 19:09:53 +00:00
private int? HandleArchive ( HawkFile file )
{
if ( ChooseArchive ! = null )
{
return ChooseArchive ( file ) ;
}
return null ;
}
2014-07-31 21:15:07 +00:00
private void DoLoadErrorCallback ( string message , string systemId , LoadErrorType type = LoadErrorType . Unknown )
2013-12-25 19:09:53 +00:00
{
if ( OnLoadError ! = null )
{
2014-07-31 21:15:07 +00:00
OnLoadError ( this , new RomErrorArgs ( message , systemId , type ) ) ;
2013-12-25 19:09:53 +00:00
}
}
2014-04-14 16:48:45 +00:00
private bool PreferredPlatformIsDefined ( string extension )
{
if ( Global . Config . PreferredPlatformsForExtensions . ContainsKey ( extension ) )
{
return ! string . IsNullOrEmpty ( Global . Config . PreferredPlatformsForExtensions [ extension ] ) ;
}
return false ;
}
2014-05-12 00:14:45 +00:00
public bool LoadRom ( string path , CoreComm nextComm , bool forceAccurateCore = false ) // forceAccurateCore is currently just for Quicknes vs Neshawk but could be used for other situations
2013-12-25 19:09:53 +00:00
{
if ( path = = null )
{
return false ;
}
using ( var file = new HawkFile ( ) )
{
2014-08-11 15:29:53 +00:00
var romExtensions = new [ ] { "SMS" , "SMC" , "SFC" , "PCE" , "SGX" , "GG" , "SG" , "BIN" , "GEN" , "MD" , "SMD" , "GB" , "NES" , "FDS" , "ROM" , "INT" , "GBC" , "UNF" , "A78" , "CRT" , "COL" , "XML" , "Z64" , "V64" , "N64" , "WS" , "WSC" , "GBA" } ;
2013-12-25 19:09:53 +00:00
// lets not use this unless we need to
// file.NonArchiveExtensions = romExtensions;
file . Open ( path ) ;
// if the provided file doesnt even exist, give up!
if ( ! file . Exists )
{
return false ;
}
// try binding normal rom extensions first
if ( ! file . IsBound )
{
file . BindSoleItemOf ( romExtensions ) ;
}
// if we have an archive and need to bind something, then pop the dialog
if ( file . IsArchive & & ! file . IsBound )
{
2013-12-29 23:54:40 +00:00
var result = HandleArchive ( file ) ;
2013-12-25 19:09:53 +00:00
if ( result . HasValue )
{
file . BindArchiveMember ( result . Value ) ;
}
else
{
return false ;
}
}
2014-01-15 02:16:06 +00:00
// set this here so we can see what file we tried to load even if an error occurs
CanonicalFullPath = file . CanonicalFullPath ;
2013-12-25 19:09:53 +00:00
IEmulator nextEmulator = null ;
RomGame rom = null ;
GameInfo game = null ;
try
{
var ext = file . Extension . ToLower ( ) ;
if ( ext = = ".iso" | | ext = = ".cue" )
{
var disc = ext = = ".iso" ? Disc . FromIsoPath ( path ) : Disc . FromCuePath ( path , new CueBinPrefs ( ) ) ;
var hash = disc . GetHash ( ) ;
game = Database . CheckDatabase ( hash ) ;
if ( game = = null )
{
// try to use our wizard methods
game = new GameInfo { Name = Path . GetFileNameWithoutExtension ( file . Name ) , Hash = hash } ;
switch ( disc . DetectDiscType ( ) )
{
case DiscType . SegaSaturn :
game . System = "SAT" ;
break ;
case DiscType . SonyPSP :
game . System = "PSP" ;
break ;
case DiscType . SonyPSX :
game . System = "PSX" ;
break ;
case DiscType . MegaCD :
game . System = "GEN" ;
break ;
case DiscType . TurboCD :
case DiscType . UnknownCDFS :
case DiscType . UnknownFormat :
default : // PCECD was bizhawk's first CD core,
// and during that time, all CDs were blindly sent to it
// so this prevents regressions
game . System = "PCECD" ;
break ;
}
}
switch ( game . System )
{
case "GEN" :
2013-12-29 23:54:40 +00:00
var genesis = new GPGX (
2014-08-23 19:06:37 +00:00
nextComm , null , disc , GetCoreSettings < GPGX > ( ) , GetCoreSyncSettings < GPGX > ( ) ) ;
2014-08-01 14:56:23 +00:00
nextEmulator = genesis ;
2013-12-25 19:09:53 +00:00
break ;
case "SAT" :
2013-12-29 23:54:40 +00:00
nextEmulator = new Yabause ( nextComm , disc , GetCoreSyncSettings < Yabause > ( ) ) ;
2013-12-25 19:09:53 +00:00
break ;
case "PSP" :
2013-12-29 23:54:40 +00:00
nextEmulator = new PSP ( nextComm , file . Name ) ;
2013-12-25 19:09:53 +00:00
break ;
case "PSX" :
2013-12-29 23:54:40 +00:00
nextEmulator = new Octoshock ( nextComm ) ;
( nextEmulator as Octoshock ) . LoadCuePath ( file . CanonicalFullPath ) ;
nextEmulator . CoreComm . RomStatusDetails = "PSX etc." ;
2013-12-25 19:09:53 +00:00
break ;
case "PCE" :
case "PCECD" :
2014-06-28 22:48:07 +00:00
nextEmulator = new PCEngine ( nextComm , game , disc , GetCoreSettings < PCEngine > ( ) , GetCoreSyncSettings < PCEngine > ( ) ) ;
2013-12-29 23:54:40 +00:00
break ;
2013-12-25 19:09:53 +00:00
}
}
else if ( file . Extension . ToLower ( ) = = ".xml" )
{
try
{
2013-12-29 23:54:40 +00:00
var xmlGame = XmlGame . Create ( file ) ; // if load fails, are we supposed to retry as a bsnes XML????????
game = xmlGame . GI ;
2013-12-25 19:09:53 +00:00
switch ( game . System )
{
case "DGB" :
2013-12-29 23:54:40 +00:00
var left = Database . GetGameInfo ( xmlGame . Assets [ "LeftRom" ] , "left.gb" ) ;
var right = Database . GetGameInfo ( xmlGame . Assets [ "RightRom" ] , "right.gb" ) ;
nextEmulator = new GambatteLink (
nextComm ,
left ,
xmlGame . Assets [ "LeftRom" ] ,
right ,
xmlGame . Assets [ "RightRom" ] ,
2013-12-26 20:19:28 +00:00
GetCoreSettings < GambatteLink > ( ) ,
2014-05-12 17:24:43 +00:00
GetCoreSyncSettings < GambatteLink > ( ) ,
Deterministic ) ;
2013-12-25 19:09:53 +00:00
// other stuff todo
break ;
default :
return false ;
}
}
catch ( Exception ex )
{
2014-07-31 21:15:07 +00:00
DoLoadErrorCallback ( ex . ToString ( ) , "DGB" , LoadErrorType . XML ) ;
2014-01-15 02:16:06 +00:00
return false ;
2013-12-25 19:09:53 +00:00
}
}
else // most extensions
{
rom = new RomGame ( file ) ;
2014-04-14 12:42:04 +00:00
if ( string . IsNullOrEmpty ( rom . GameInfo . System ) )
2014-04-14 01:59:57 +00:00
{
2014-04-14 12:42:04 +00:00
// Has the user picked a preference for this extension?
2014-04-14 16:48:45 +00:00
if ( PreferredPlatformIsDefined ( rom . Extension . ToLower ( ) ) )
2014-04-14 12:42:04 +00:00
{
rom . GameInfo . System = Global . Config . PreferredPlatformsForExtensions [ rom . Extension . ToLower ( ) ] ;
}
2014-04-14 16:48:45 +00:00
else if ( ChoosePlatform ! = null )
2014-04-14 12:42:04 +00:00
{
rom . GameInfo . System = ChoosePlatform ( rom ) ;
}
2014-04-14 01:59:57 +00:00
}
2013-12-25 19:09:53 +00:00
game = rom . GameInfo ;
2013-12-29 23:54:40 +00:00
var isXml = false ;
2013-12-25 19:09:53 +00:00
// other xml has already been handled
if ( file . Extension . ToLower ( ) = = ".xml" )
{
game . System = "SNES" ;
isXml = true ;
}
2014-08-23 19:06:37 +00:00
CoreInventory . Core core = null ;
2013-12-25 19:09:53 +00:00
switch ( game . System )
{
2014-08-23 19:06:37 +00:00
default :
core = CoreInventory . Instance [ game . System ] ;
break ;
2014-11-22 15:56:02 +00:00
case null :
// The user picked nothing in the Core picker
break ;
2013-12-25 19:09:53 +00:00
case "SNES" :
2014-07-29 02:26:42 +00:00
if ( Global . Config . SNES_InSnes9x & & VersionInfo . DeveloperBuild )
2014-07-28 23:58:52 +00:00
{
2014-08-23 19:06:37 +00:00
core = CoreInventory . Instance [ "SNES" , "Snes9x" ] ;
2014-07-28 23:58:52 +00:00
}
else
2013-12-25 19:09:53 +00:00
{
// need to get rid of this hack at some point
2013-12-29 23:54:40 +00:00
( ( CoreFileProvider ) nextComm . CoreFileProvider ) . SubfileDirectory = Path . GetDirectoryName ( path . Replace ( "|" , String . Empty ) ) ; // Dirty hack to get around archive filenames (since we are just getting the directory path, it is safe to mangle the filename
var romData = isXml ? null : rom . FileData ;
var xmlData = isXml ? rom . FileData : null ;
2014-08-01 14:56:23 +00:00
var snes = new LibsnesCore ( game , romData , Deterministic , xmlData , nextComm , GetCoreSettings < LibsnesCore > ( ) , GetCoreSyncSettings < LibsnesCore > ( ) ) ;
nextEmulator = snes ;
2013-12-25 19:09:53 +00:00
}
2013-12-29 23:54:40 +00:00
2013-12-25 19:09:53 +00:00
break ;
case "NES" :
2014-05-12 00:14:45 +00:00
if ( ! Global . Config . NES_InQuickNES | | forceAccurateCore )
2014-01-05 20:58:36 +00:00
{
2014-08-23 19:06:37 +00:00
core = CoreInventory . Instance [ "NES" , "NesHawk" ] ;
2014-01-05 20:58:36 +00:00
}
else
{
2014-08-23 19:06:37 +00:00
core = CoreInventory . Instance [ "NES" , "QuickNes" ] ;
2014-01-05 20:58:36 +00:00
}
2014-01-08 03:53:53 +00:00
2013-12-25 19:09:53 +00:00
break ;
case "GB" :
case "GBC" :
if ( ! Global . Config . GB_AsSGB )
{
2014-08-23 19:06:37 +00:00
core = CoreInventory . Instance [ "GB" , "Gambatte" ] ;
2013-12-25 19:09:53 +00:00
}
else
{
try
{
game . System = "SNES" ;
game . AddOption ( "SGB" ) ;
2014-08-01 14:56:23 +00:00
var snes = new LibsnesCore ( game , rom . FileData , Deterministic , null , nextComm , GetCoreSettings < LibsnesCore > ( ) , GetCoreSyncSettings < LibsnesCore > ( ) ) ;
2013-12-25 19:09:53 +00:00
nextEmulator = snes ;
}
catch
{
2014-08-02 02:41:12 +00:00
// failed to load SGB bios or game does not support SGB mode.
// To avoid catch-22, disable SGB mode
2013-12-25 19:09:53 +00:00
Global . Config . GB_AsSGB = false ;
throw ;
}
}
2013-12-29 23:54:40 +00:00
2013-12-25 19:09:53 +00:00
break ;
case "A78" :
var gamedbpath = Path . Combine ( PathManager . GetExeDirectoryAbsolute ( ) , "gamedb" , "EMU7800.csv" ) ;
2013-12-29 23:54:40 +00:00
nextEmulator = new Atari7800 ( nextComm , game , rom . RomData , gamedbpath ) ;
2013-12-25 19:09:53 +00:00
break ;
case "C64" :
2013-12-29 23:54:40 +00:00
var c64 = new C64 ( nextComm , game , rom . RomData , rom . Extension ) ;
2013-12-25 19:09:53 +00:00
nextEmulator = c64 ;
break ;
case "GBA" :
2014-08-24 14:12:31 +00:00
//core = CoreInventory.Instance["GBA", "Meteor"];
core = CoreInventory . Instance [ "GBA" , "VBA-Next" ] ;
2013-12-25 19:09:53 +00:00
break ;
case "DEBUG" :
2014-06-04 17:02:54 +00:00
if ( VersionInfo . DeveloperBuild )
2013-12-25 19:09:53 +00:00
{
nextEmulator = LibRetroEmulator . CreateDebug ( nextComm , rom . RomData ) ;
}
2013-12-29 23:54:40 +00:00
2013-12-25 19:09:53 +00:00
break ;
}
2014-08-23 19:06:37 +00:00
if ( core ! = null )
{
// use coreinventory
2014-09-12 01:02:09 +00:00
nextEmulator = core . Create ( nextComm , game , rom . RomData , rom . FileData , Deterministic , GetCoreSettings ( core . Type ) , GetCoreSyncSettings ( core . Type ) ) ;
2014-08-23 19:06:37 +00:00
}
2013-12-25 19:09:53 +00:00
}
if ( nextEmulator = = null )
{
2014-07-31 21:15:07 +00:00
DoLoadErrorCallback ( "No core could load the rom." , null ) ;
2013-12-25 19:09:53 +00:00
return false ;
}
}
catch ( Exception ex )
{
2014-05-08 03:18:00 +00:00
string system = null ;
if ( game ! = null )
2014-05-12 00:14:45 +00:00
{
2014-05-08 03:18:00 +00:00
system = game . System ;
2014-05-12 00:14:45 +00:00
}
2014-08-24 17:25:29 +00:00
// all of the specific exceptions we're trying to catch here aren't expected to have inner exceptions,
// so drill down in case we got a TargetInvocationException or something like that
while ( ex . InnerException ! = null )
ex = ex . InnerException ;
2014-05-12 00:14:45 +00:00
// Specific hack here, as we get more cores of the same system, this isn't scalable
2014-10-29 18:58:43 +00:00
if ( ex is UnsupportedGameException )
2014-05-12 00:14:45 +00:00
{
2014-07-31 20:42:12 +00:00
return LoadRom ( path , nextComm , forceAccurateCore : true ) ;
2014-05-12 00:14:45 +00:00
}
2014-07-31 21:15:07 +00:00
else if ( ex is MissingFirmwareException )
{
DoLoadErrorCallback ( ex . Message , system , LoadErrorType . MissingFirmware ) ;
}
2014-08-02 02:41:12 +00:00
else if ( ex is CGBNotSupportedException )
{
// Note: GB as SGB was set to false by this point, otherwise we would want to do it here
DoMessageCallback ( "Failed to load a GB rom in SGB mode. Disabling SGB Mode." ) ;
return LoadRom ( path , nextComm ) ;
}
2014-05-12 00:14:45 +00:00
else
{
2014-07-31 21:15:07 +00:00
DoLoadErrorCallback ( "A core accepted the rom, but threw an exception while loading it:\n\n" + ex , system ) ;
2014-05-12 00:14:45 +00:00
}
2013-12-25 19:09:53 +00:00
return false ;
}
2013-12-29 23:54:40 +00:00
2013-12-25 19:09:53 +00:00
Rom = rom ;
LoadedEmulator = nextEmulator ;
Game = game ;
return true ;
}
}
}
2014-08-01 14:56:23 +00:00
}