2013-12-25 19:09:53 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using BizHawk.Common ;
using BizHawk.Emulation.Common ;
using BizHawk.Emulation.DiscSystem ;
using System.IO ;
using BizHawk.Emulation.Cores.Consoles.Sega.gpgx ;
using BizHawk.Emulation.Cores.Sega.Saturn ;
using BizHawk.Emulation.Cores.Sony.PSP ;
using BizHawk.Emulation.Cores.Sony.PSX ;
using BizHawk.Emulation.Cores.PCEngine ;
using BizHawk.Emulation.Cores.Nintendo.Gameboy ;
using BizHawk.Emulation.Cores.Nintendo.SNES ;
using BizHawk.Emulation.Cores.Sega.MasterSystem ;
using BizHawk.Emulation.Cores.Nintendo.NES ;
using BizHawk.Emulation.Cores.Intellivision ;
using BizHawk.Emulation.Cores.ColecoVision ;
using BizHawk.Emulation.Cores.Nintendo.GBA ;
using BizHawk.Emulation.Cores.Atari.Atari7800 ;
using BizHawk.Emulation.Cores.Computers.Commodore64 ;
using BizHawk.Emulation.Cores.Calculators ;
using BizHawk.Emulation.Cores.Atari.Atari2600 ;
using BizHawk.Emulation.Cores.Nintendo.N64 ;
namespace BizHawk.Client.Common
{
public class RomLoader
{
2013-12-25 19:47:44 +00:00
#region Duplicate code to mainform , need to refactor !
2013-12-26 20:19:28 +00:00
/ *
2013-12-25 19:47:44 +00:00
object __SyncSettingsHack = null ;
object GetCoreSyncSettings < T > ( )
where T : IEmulator
{
// if movie 2.0 was finished, this is where you'd decide whether to get a settings object
// from a config file or from the movie file
// since all we have right now is movie 1.0, we get silly hacks instead
return __SyncSettingsHack ? ? Global . Config . GetCoreSyncSettings < T > ( ) ;
}
2013-12-26 20:19:28 +00:00
* /
object GetCoreSettings < T > ( )
where T : IEmulator
{
var e = new SettingsLoadArgs ( typeof ( T ) ) ;
if ( OnLoadSettings ! = null )
OnLoadSettings ( this , e ) ;
return e . Settings ;
}
object GetCoreSyncSettings < T > ( )
where T : IEmulator
{
var e = new SettingsLoadArgs ( typeof ( T ) ) ;
if ( OnLoadSyncSettings ! = null )
OnLoadSyncSettings ( this , e ) ;
return e . Settings ;
}
2013-12-25 19:47:44 +00:00
#endregion
2013-12-25 19:09:53 +00:00
#region SNES specific stuff - clean up or move elsewhere
private readonly Dictionary < string , string > _snesPrepared = new Dictionary < string , string > ( ) ;
// Contains a mapping: profilename->exepath ; or null if the exe wasnt available
private string SNES_Prepare ( string profile )
{
SNES_Check ( profile ) ;
if ( _snesPrepared [ profile ] = = null )
{
throw new InvalidOperationException ( "Couldn't locate the executable for SNES emulation for profile: " + profile + ". Please make sure you're using a fresh dearchive of a BizHawk distribution." ) ;
}
return _snesPrepared [ profile ] ;
}
private void SNES_Check ( string profile )
{
if ( _snesPrepared . ContainsKey ( profile ) )
{
return ;
}
const string bits = "32" ;
// disabled til it works
// if (Win32.Is64BitOperatingSystem)
// bits = "64";
var exename = "libsneshawk-" + bits + "-" + profile . ToLower ( ) + ".exe" ;
var thisDir = PathManager . GetExeDirectoryAbsolute ( ) ;
var exePath = Path . Combine ( thisDir , exename ) ;
if ( ! File . Exists ( exePath ) )
{
exePath = Path . Combine ( Path . Combine ( thisDir , "dll" ) , exename ) ;
}
if ( ! File . Exists ( exePath ) )
{
exePath = null ;
}
_snesPrepared [ profile ] = exePath ;
}
#endregion
#region N64 specific stuff - clean up or move elsewhere
private static VideoPluginSettings N64GenerateVideoSettings ( GameInfo game , bool hasmovie )
{
var pluginToUse = String . Empty ;
if ( hasmovie & & Global . MovieSession . Movie . Header [ HeaderKeys . PLATFORM ] = = "N64" & & Global . MovieSession . Movie . Header . ContainsKey ( HeaderKeys . VIDEOPLUGIN ) )
{
pluginToUse = Global . MovieSession . Movie . Header [ HeaderKeys . VIDEOPLUGIN ] ;
}
if ( pluginToUse = = string . Empty | | ( pluginToUse ! = "Rice" & & pluginToUse ! = "Glide64" ) )
{
pluginToUse = Global . Config . N64VidPlugin ;
}
var video_settings = new VideoPluginSettings ( pluginToUse , Global . Config . N64VideoSizeX , Global . Config . N64VideoSizeY ) ;
if ( pluginToUse = = "Rice" )
{
Global . Config . RicePlugin . FillPerGameHacks ( game ) ;
video_settings . Parameters = Global . Config . RicePlugin . GetPluginSettings ( ) ;
}
else if ( pluginToUse = = "Glide64" )
{
Global . Config . GlidePlugin . FillPerGameHacks ( game ) ;
video_settings . Parameters = Global . Config . GlidePlugin . GetPluginSettings ( ) ;
}
else if ( pluginToUse = = "Glide64mk2" )
{
Global . Config . Glide64mk2Plugin . FillPerGameHacks ( game ) ;
video_settings . Parameters = Global . Config . Glide64mk2Plugin . GetPluginSettings ( ) ;
}
if ( hasmovie & & Global . MovieSession . Movie . Header [ HeaderKeys . PLATFORM ] = = "N64" & & Global . MovieSession . Movie . Header . ContainsKey ( HeaderKeys . VIDEOPLUGIN ) )
{
var settings = new List < string > ( video_settings . Parameters . Keys ) ;
foreach ( var setting in settings )
{
if ( Global . MovieSession . Movie . Header . ContainsKey ( setting ) )
{
var Value = Global . MovieSession . Movie . Header [ setting ] ;
if ( video_settings . Parameters [ setting ] is bool )
{
try
{
video_settings . Parameters [ setting ] = bool . Parse ( Value ) ;
}
catch { }
}
else if ( video_settings . Parameters [ setting ] is int )
{
try
{
video_settings . Parameters [ setting ] = int . Parse ( Value ) ;
}
catch { }
}
}
}
}
return video_settings ;
}
#endregion
public RomLoader ( )
{
Deterministic = true ;
}
// TODO: reconsider the need for exposing these;
public IEmulator LoadedEmulator { get ; private set ; }
public CoreComm NextComm { get ; private set ; }
public GameInfo Game { get ; private set ; }
public RomGame Rom { get ; private set ; }
public string CanonicalFullPath { get ; private set ; }
public bool Deterministic { get ; private set ; }
public class RomErrorArgs
{
// 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-26 20:19:28 +00:00
public class SettingsLoadArgs
{
public object Settings { get ; set ; }
public Type Core { get ; private set ; }
public SettingsLoadArgs ( Type t )
{
Core = t ;
Settings = null ;
}
}
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 ;
public Func < HawkFile , int? > ChooseArchive ;
public Action < string > CoreCommMessageCallback ; // TODO: eww, do we have to do this?
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 ) ) ;
}
}
// TODO I'm in mainform.cs and here, move me to a common place that both can call, static method on the config object?
private void CommitCoreSettingsToConfig ( )
{
// save settings object
Type t = Global . Emulator . GetType ( ) ;
Global . Config . PutCoreSettings ( Global . Emulator . GetSettings ( ) , t ) ;
// don't trample config with loaded-from-movie settings
if ( ! Global . MovieSession . Movie . IsActive )
{
Global . Config . PutCoreSyncSettings ( Global . Emulator . GetSyncSettings ( ) , t ) ;
}
}
// TODO: the hasMovie hack should be obsoleted by the standardized movie sync setting saving/loading
public bool LoadRom ( string path , bool hasmovie = false )
{
if ( path = = null )
{
return false ;
}
using ( var file = new HawkFile ( ) )
{
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" } ;
// 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 )
{
var result = ChooseArchive ( file ) ;
if ( result . HasValue )
{
file . BindArchiveMember ( result . Value ) ;
}
else
{
return false ;
}
}
IEmulator nextEmulator = null ;
RomGame rom = null ;
GameInfo game = null ;
var nextComm = new CoreComm ( CoreCommMessageCallback ) ;
CoreFileProvider . SyncCoreCommInputSignals ( nextComm ) ;
// this also happens in CloseGame(). but it needs to happen here since if we're restarting with the same core,
// any settings changes that we made need to make it back to config before we try to instantiate that core with
// the new settings objects
CommitCoreSettingsToConfig ( ) ;
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" :
{
var genesis = new GPGX (
2013-12-26 20:19:28 +00:00
nextComm , null , disc , "GEN" , GetCoreSettings < GPGX > ( ) ) ;
2013-12-25 19:09:53 +00:00
nextEmulator = genesis ;
}
break ;
case "SAT" :
{
var saturn = new Yabause ( nextComm , disc , Global . Config . SaturnUseGL ) ;
nextEmulator = saturn ;
}
break ;
case "PSP" :
{
var psp = new PSP ( nextComm , file . Name ) ;
nextEmulator = psp ;
}
break ;
case "PSX" :
{
var psx = new Octoshock ( nextComm ) ;
nextEmulator = psx ;
psx . LoadCuePath ( file . CanonicalFullPath ) ;
nextEmulator . CoreComm . RomStatusDetails = "PSX etc." ;
}
break ;
case "PCE" :
case "PCECD" :
{
var biosPath = Global . FirmwareManager . Request ( "PCECD" , "Bios" ) ;
if ( File . Exists ( biosPath ) = = false )
{
ThrowLoadError ( "PCE-CD System Card not found. Please check the BIOS path in Config->Paths->PC Engine."
, game . System ) ;
return false ;
}
rom = new RomGame ( new HawkFile ( biosPath ) ) ;
if ( rom . GameInfo . Status = = RomStatus . BadDump )
{
ThrowLoadError (
"The PCE-CD System Card you have selected is known to be a bad dump. This may cause problems playing PCE-CD games.\n\n"
+ "It is recommended that you find a good dump of the system card. Sorry to be the bearer of bad news!"
, game . System ) ;
}
else if ( rom . GameInfo . NotInDatabase )
{
ThrowLoadError (
"The PCE-CD System Card you have selected is not recognized in our database. That might mean it's a bad dump, or isn't the correct rom."
, game . System ) ;
}
else if ( rom . GameInfo [ "BIOS" ] = = false )
{
ThrowLoadError (
"The PCE-CD System Card you have selected is not a BIOS image. You may have selected the wrong rom."
, game . System ) ;
}
if ( rom . GameInfo [ "SuperSysCard" ] )
{
game . AddOption ( "SuperSysCard" ) ;
}
if ( game [ "NeedSuperSysCard" ] & & game [ "SuperSysCard" ] = = false )
{
ThrowLoadError (
"This game requires a version 3.0 System card and won't run with the system card you've selected. Try selecting a 3.0 System Card in Config->Paths->PC Engine."
, game . System ) ;
}
game . FirmwareHash = Util . BytesToHexString ( System . Security . Cryptography . SHA1 . Create ( ) . ComputeHash ( rom . RomData ) ) ;
2013-12-26 20:19:28 +00:00
nextEmulator = new PCEngine ( nextComm , game , disc , rom . RomData , GetCoreSettings < PCEngine > ( ) ) ;
2013-12-25 19:09:53 +00:00
break ;
}
}
}
else if ( file . Extension . ToLower ( ) = = ".xml" )
{
try
{
var XMLG = XmlGame . Create ( file ) ; // if load fails, are we supposed to retry as a bsnes XML????????
game = XMLG . GI ;
switch ( game . System )
{
case "DGB" :
var L = Database . GetGameInfo ( XMLG . Assets [ "LeftRom" ] , "left.gb" ) ;
var R = Database . GetGameInfo ( XMLG . Assets [ "RightRom" ] , "right.gb" ) ;
var gbl = new GambatteLink ( nextComm , L , XMLG . Assets [ "LeftRom" ] , R , XMLG . Assets [ "RightRom" ] ,
2013-12-26 20:19:28 +00:00
GetCoreSettings < GambatteLink > ( ) ,
2013-12-25 19:47:44 +00:00
GetCoreSyncSettings < GambatteLink > ( ) ) ;
2013-12-25 19:09:53 +00:00
nextEmulator = gbl ;
// 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
}
}
else // most extensions
{
rom = new RomGame ( file ) ;
game = rom . GameInfo ;
bool isXml = false ;
// other xml has already been handled
if ( file . Extension . ToLower ( ) = = ".xml" )
{
game . System = "SNES" ;
isXml = true ;
}
switch ( game . System )
{
case "SNES" :
{
game . System = "SNES" ;
nextComm . SNES_ExePath = SNES_Prepare ( Global . Config . SNESProfile ) ;
// need to get rid of this hack at some point
( ( 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 snes = new LibsnesCore ( nextComm ) ;
nextEmulator = snes ;
byte [ ] romData = isXml ? null : rom . FileData ;
byte [ ] xmlData = isXml ? rom . FileData : null ;
snes . Load ( game , romData , Deterministic , xmlData ) ;
}
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" :
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" :
{
// nextEmulator = new Genesis(nextComm, game, rom.RomData);
2013-12-26 20:19:28 +00:00
nextEmulator = new GPGX ( nextComm , rom . RomData , null , "GEN" , GetCoreSyncSettings < GPGX > ( ) ) ;
2013-12-25 19:09:53 +00:00
break ;
}
case "TI83" :
nextEmulator = new TI83 ( nextComm , game , rom . RomData ) ;
break ;
case "NES" :
nextEmulator = new NES ( nextComm , game , rom . FileData ,
2013-12-26 20:19:28 +00:00
GetCoreSettings < NES > ( ) ,
2013-12-25 19:09:53 +00:00
Global . MovieSession . Movie . Header . BoardProperties ) ;
break ;
case "GB" :
case "GBC" :
if ( ! Global . Config . GB_AsSGB )
{
var gb = new Gameboy ( nextComm , game , rom . FileData ,
2013-12-26 20:19:28 +00:00
GetCoreSettings < Gameboy > ( ) ,
2013-12-25 19:47:44 +00:00
GetCoreSyncSettings < Gameboy > ( ) ) ;
2013-12-25 19:09:53 +00:00
nextEmulator = gb ;
}
else
{
try
{
game . System = "SNES" ;
game . AddOption ( "SGB" ) ;
nextComm . SNES_ExePath = SNES_Prepare ( Global . Config . SNESProfile ) ;
var snes = new LibsnesCore ( nextComm ) ;
nextEmulator = snes ;
snes . Load ( game , rom . FileData , Deterministic , null ) ;
}
catch
{
// failed to load SGB bios. to avoid catch-22, disable SGB mode
ThrowLoadError ( "Failed to load a GB rom in SGB mode. Disabling SGB Mode."
, game . System ) ;
Global . Config . GB_AsSGB = false ;
throw ;
}
}
break ;
case "Coleco" :
{
2013-12-25 19:47:44 +00:00
var c = new ColecoVision ( nextComm , game , rom . RomData , GetCoreSyncSettings < ColecoVision > ( ) ) ;
nextEmulator = c ;
2013-12-25 19:09:53 +00:00
}
break ;
case "INTV" :
{
var intv = new Intellivision ( nextComm , game , rom . RomData ) ;
nextEmulator = intv ;
}
break ;
case "A78" :
var gamedbpath = Path . Combine ( PathManager . GetExeDirectoryAbsolute ( ) , "gamedb" , "EMU7800.csv" ) ;
var a78 = new Atari7800 ( nextComm , game , rom . RomData , gamedbpath ) ;
nextEmulator = a78 ;
break ;
case "C64" :
C64 c64 = new C64 ( nextComm , game , rom . RomData , rom . Extension ) ;
c64 . HardReset ( ) ;
nextEmulator = c64 ;
break ;
case "GBA" :
if ( VersionInfo . INTERIM )
{
GBA gba = new GBA ( nextComm ) ;
gba . Load ( rom . RomData ) ;
nextEmulator = gba ;
}
break ;
case "N64" :
Global . Game = game ;
var video_settings = N64GenerateVideoSettings ( game , hasmovie ) ;
int SaveType = 0 ;
if ( game . OptionValue ( "SaveType" ) = = "EEPROM_16K" )
{
SaveType = 1 ;
}
nextEmulator = new N64 ( nextComm , game , rom . RomData , video_settings , SaveType ) ;
break ;
case "DEBUG" :
if ( VersionInfo . INTERIM )
{
nextEmulator = LibRetroEmulator . CreateDebug ( nextComm , rom . RomData ) ;
}
break ;
}
}
if ( nextEmulator = = null )
{
ThrowLoadError ( "No core could load the rom." , "NULL" ) ;
return false ;
}
}
catch ( Exception ex )
{
ThrowLoadError ( "Exception during loadgame:\n\n" + ex , "NULL" ) ;
return false ;
}
Rom = rom ;
LoadedEmulator = nextEmulator ;
NextComm = nextComm ;
Game = game ;
CanonicalFullPath = file . CanonicalFullPath ;
return true ;
}
}
}
}