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
{
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
{
var e = new SettingsLoadArgs ( typeof ( T ) ) ;
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 ;
}
2013-12-29 23:54:40 +00:00
private object GetCoreSyncSettings < T > ( )
2013-12-26 20:19:28 +00:00
where T : IEmulator
{
var e = new SettingsLoadArgs ( typeof ( T ) ) ;
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
}
// 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!
public RomErrorArgs ( string message , string systemId )
{
Message = message ;
AttemptedCoreLoad = systemId ;
}
public string Message { get ; private set ; }
public string AttemptedCoreLoad { get ; private set ; }
}
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 ;
}
private void ThrowLoadError ( string message , string systemId )
{
if ( OnLoadError ! = null )
{
OnLoadError ( this , new RomErrorArgs ( message , systemId ) ) ;
}
}
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-05-30 20:53:52 +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" } ;
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 (
2013-12-26 20:19:28 +00:00
nextComm , null , disc , "GEN" , GetCoreSettings < GPGX > ( ) ) ;
2013-12-29 23:54:40 +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-02-10 15:26:18 +00:00
nextEmulator = new PCEngine ( nextComm , game , disc , GetCoreSettings < 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 )
{
ThrowLoadError ( ex . ToString ( ) , "XMLGame Load Error" ) ; // TODO: don't pass in XMLGame Load Error as a system ID
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 ;
}
switch ( game . System )
{
case "SNES" :
{
// 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
2013-12-27 17:59:19 +00:00
var snes = new LibsnesCore ( nextComm , GetCoreSettings < LibsnesCore > ( ) , GetCoreSyncSettings < LibsnesCore > ( ) ) ;
2013-12-25 19:09:53 +00:00
nextEmulator = snes ;
2013-12-29 23:54:40 +00:00
var romData = isXml ? null : rom . FileData ;
var xmlData = isXml ? rom . FileData : null ;
2013-12-25 19:09:53 +00:00
snes . Load ( game , romData , Deterministic , xmlData ) ;
}
2013-12-29 23:54:40 +00:00
2013-12-25 19:09:53 +00:00
break ;
case "SMS" :
case "SG" :
case "GG" :
2013-12-26 20:19:28 +00:00
nextEmulator = new SMS ( nextComm , game , rom . RomData , GetCoreSettings < SMS > ( ) , GetCoreSyncSettings < SMS > ( ) ) ;
2013-12-25 19:09:53 +00:00
break ;
case "A26" :
2013-12-29 23:54:40 +00:00
nextEmulator = new Atari2600 (
nextComm ,
game ,
rom . FileData ,
2013-12-26 20:19:28 +00:00
GetCoreSettings < Atari2600 > ( ) ,
2013-12-25 19:47:44 +00:00
GetCoreSyncSettings < Atari2600 > ( ) ) ;
2013-12-25 19:09:53 +00:00
break ;
case "PCE" :
case "PCECD" :
case "SGX" :
2013-12-26 20:19:28 +00:00
nextEmulator = new PCEngine ( nextComm , game , rom . RomData , GetCoreSettings < PCEngine > ( ) ) ;
2013-12-25 19:09:53 +00:00
break ;
case "GEN" :
2013-12-29 23:54:40 +00:00
nextEmulator = new GPGX ( nextComm , rom . RomData , null , "GEN" , GetCoreSyncSettings < GPGX > ( ) ) ;
break ;
2013-12-25 19:09:53 +00:00
case "TI83" :
2014-05-26 14:06:54 +00:00
nextEmulator = new TI83 ( nextComm , game , rom . RomData , GetCoreSettings < TI83 > ( ) ) ;
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
{
nextEmulator = new NES (
nextComm ,
game ,
rom . FileData ,
GetCoreSettings < NES > ( ) ,
GetCoreSyncSettings < NES > ( ) ) ;
}
else
{
2014-01-09 23:50:10 +00:00
nextEmulator = new QuickNES ( nextComm , rom . FileData , GetCoreSettings < 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 )
{
2013-12-29 23:54:40 +00:00
nextEmulator = new Gameboy (
nextComm ,
game ,
rom . FileData ,
2013-12-26 20:19:28 +00:00
GetCoreSettings < Gameboy > ( ) ,
2014-05-12 17:24:43 +00:00
GetCoreSyncSettings < Gameboy > ( ) ,
Deterministic ) ;
2013-12-25 19:09:53 +00:00
}
else
{
try
{
game . System = "SNES" ;
game . AddOption ( "SGB" ) ;
2013-12-27 17:59:19 +00:00
var snes = new LibsnesCore ( nextComm , GetCoreSettings < LibsnesCore > ( ) , GetCoreSyncSettings < LibsnesCore > ( ) ) ;
2013-12-25 19:09:53 +00:00
nextEmulator = snes ;
snes . Load ( game , rom . FileData , Deterministic , null ) ;
}
catch
{
// failed to load SGB bios. to avoid catch-22, disable SGB mode
2013-12-29 23:54:40 +00:00
ThrowLoadError ( "Failed to load a GB rom in SGB mode. Disabling SGB Mode." , game . System ) ;
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 "Coleco" :
2013-12-29 23:54:40 +00:00
nextEmulator = new ColecoVision ( nextComm , game , rom . RomData , GetCoreSyncSettings < ColecoVision > ( ) ) ;
2013-12-25 19:09:53 +00:00
break ;
case "INTV" :
2013-12-29 23:54:40 +00:00
nextEmulator = new Intellivision ( nextComm , game , rom . RomData ) ;
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-06-04 17:02:54 +00:00
if ( VersionInfo . DeveloperBuild )
2013-12-25 19:09:53 +00:00
{
2014-01-08 03:53:53 +00:00
var gba = new GBA ( nextComm ) ;
2013-12-25 19:09:53 +00:00
gba . Load ( rom . RomData ) ;
nextEmulator = gba ;
}
2013-12-29 23:54:40 +00:00
2013-12-25 19:09:53 +00:00
break ;
case "N64" :
2014-06-01 12:06:22 +00:00
nextEmulator = new N64 ( nextComm , game , rom . RomData ,
GetCoreSettings < N64 > ( ) , GetCoreSyncSettings < N64 > ( ) ) ;
2013-12-25 19:09:53 +00:00
break ;
2014-05-30 05:09:54 +00:00
case "WSWAN" :
2014-05-30 22:31:16 +00:00
nextEmulator = new WonderSwan ( nextComm , rom . RomData , Deterministic ,
GetCoreSettings < WonderSwan > ( ) , GetCoreSyncSettings < WonderSwan > ( ) ) ;
2014-05-30 05:09:54 +00:00
break ;
2013-12-25 19:09:53 +00:00
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 ;
}
}
if ( nextEmulator = = null )
{
2014-05-08 03:18:00 +00:00
ThrowLoadError ( "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
}
// Specific hack here, as we get more cores of the same system, this isn't scalable
if ( ex is LibQuickNES . UnsupportedMapperException )
{
LoadRom ( path , nextComm , forceAccurateCore : true ) ;
return true ;
}
else
{
2014-05-28 04:41:12 +00:00
ThrowLoadError ( "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 ;
}
}
}
}