BizHawk/BizHawk.Client.Common/RomLoader.cs

590 lines
18 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
2013-12-29 23:54:40 +00:00
using System.IO;
using BizHawk.Common;
using BizHawk.Emulation.Common;
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;
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;
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;
2015-02-17 22:58:25 +00:00
using BizHawk.Emulation.Cores.Computers.AppleII;
namespace BizHawk.Client.Common
{
public class RomLoader
{
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
{
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;
}
private object GetCoreSyncSettings(Type t)
2013-12-26 20:19:28 +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;
}
public RomLoader()
{
}
// 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);
}
}
// 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; }
public bool Deterministic { get; set; }
public class RomErrorArgs : EventArgs
{
// 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, LoadErrorType type)
{
Message = message;
AttemptedCoreLoad = systemId;
Type = type;
}
public string Message { get; private set; }
public string AttemptedCoreLoad { get; private set; }
public LoadErrorType Type { get; private set; }
}
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;
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; }
public Func<RomGame, string> ChoosePlatform { get; set; }
// in case we get sent back through the picker more than once, use the same choice the second time
int? previouschoice;
private int? HandleArchive(HawkFile file)
{
if (previouschoice.HasValue)
return previouschoice;
if (ChooseArchive != null)
{
previouschoice = ChooseArchive(file);
return previouschoice;
}
return null;
}
private void DoLoadErrorCallback(string message, string systemId, LoadErrorType type = LoadErrorType.Unknown)
{
if (OnLoadError != null)
{
OnLoadError(this, new RomErrorArgs(message, systemId, type));
}
}
private bool PreferredPlatformIsDefined(string extension)
{
if (Global.Config.PreferredPlatformsForExtensions.ContainsKey(extension))
{
return !string.IsNullOrEmpty(Global.Config.PreferredPlatformsForExtensions[extension]);
}
return false;
}
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
{
bool cancel = 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", "WS", "WSC", "GBA" };
// 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)
{
int? result = HandleArchive(file);
if (result.HasValue)
{
file.BindArchiveMember(result.Value);
}
else
{
return false;
}
}
// set this here so we can see what file we tried to load even if an error occurs
CanonicalFullPath = file.CanonicalFullPath;
IEmulator nextEmulator = null;
RomGame rom = null;
GameInfo game = null;
try
{
var ext = file.Extension.ToLower();
if (ext == ".m3u")
{
//HACK ZONE - currently only psx supports m3u
M3U_File m3u;
using(var sr = new StreamReader(path))
m3u = M3U_File.Read(sr);
if(m3u.Entries.Count == 0)
throw new InvalidOperationException("Can't load an empty M3U");
//load discs for all the m3u
m3u.Rebase(Path.GetDirectoryName(path));
List<Disc> discs = new List<Disc>();
List<string> discNames = new List<string>();
foreach (var e in m3u.Entries)
{
Disc disc = null;
string discPath = e.Path;
string discExt = Path.GetExtension(discPath).ToLower();
if (discExt == ".iso")
disc = Disc.FromIsoPath(discPath);
if (discExt == ".cue")
disc = Disc.FromCuePath(discPath, new CueBinPrefs());
if (discExt == ".ccd")
disc = Disc.FromCCDPath(discPath);
if(disc == null)
throw new InvalidOperationException("Can't load one of the files specified in the M3U");
discNames.Add(Path.GetFileNameWithoutExtension(discPath));
discs.Add(disc);
}
nextEmulator = new Octoshock(nextComm, discs, discNames, null, GetCoreSettings<Octoshock>(), GetCoreSyncSettings<Octoshock>());
nextEmulator.CoreComm.RomStatusDetails = "PSX etc.";
game = new GameInfo { Name = Path.GetFileNameWithoutExtension(file.Name) };
game.System = "PSX";
}
else if (ext == ".iso" || ext == ".cue" || ext == ".ccd")
{
if (file.IsArchive)
{
throw new InvalidOperationException("Can't load CD files from archives!");
}
Disc disc = null;
if(ext == ".iso")
disc = Disc.FromIsoPath(path);
if(ext == ".cue")
disc = Disc.FromCuePath(path, new CueBinPrefs());
if (ext == ".ccd")
disc = Disc.FromCCDPath(path);
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;
default:
case DiscType.SonyPSX:
game.System = "PSX";
break;
case DiscType.MegaCD:
game.System = "GEN";
break;
case DiscType.TurboCD:
case DiscType.UnknownCDFS:
case DiscType.UnknownFormat:
game.System = "PCECD";
break;
}
}
switch (game.System)
{
case "GEN":
2013-12-29 23:54:40 +00:00
var genesis = new GPGX(
nextComm, null, disc, GetCoreSettings<GPGX>(), GetCoreSyncSettings<GPGX>());
nextEmulator = genesis;
break;
case "SAT":
2013-12-29 23:54:40 +00:00
nextEmulator = new Yabause(nextComm, disc, GetCoreSyncSettings<Yabause>());
break;
case "PSP":
2013-12-29 23:54:40 +00:00
nextEmulator = new PSP(nextComm, file.Name);
break;
case "PSX":
nextEmulator = new Octoshock(nextComm, new List<Disc>(new[]{disc}), new List<string>(new[]{Path.GetFileNameWithoutExtension(path)}), null, GetCoreSettings<Octoshock>(), GetCoreSyncSettings<Octoshock>());
2013-12-29 23:54:40 +00:00
nextEmulator.CoreComm.RomStatusDetails = "PSX etc.";
break;
case "PCE":
case "PCECD":
nextEmulator = new PCEngine(nextComm, game, disc, GetCoreSettings<PCEngine>(), GetCoreSyncSettings<PCEngine>());
2013-12-29 23:54:40 +00:00
break;
}
}
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;
switch (game.System)
{
case "GB":
case "DGB":
// adelikat: remove need for tags to be hardcoded to left and right, we should clean this up, also maybe the DGB core should just take the xml file and handle it itself
var leftBytes = xmlGame.Assets.First().Value;
var rightBytes = xmlGame.Assets.Skip(1).First().Value;
var left = Database.GetGameInfo(leftBytes, "left.gb");
var right = Database.GetGameInfo(rightBytes, "right.gb");
2013-12-29 23:54:40 +00:00
nextEmulator = new GambatteLink(
nextComm,
left,
leftBytes,
2013-12-29 23:54:40 +00:00
right,
rightBytes,
2013-12-26 20:19:28 +00:00
GetCoreSettings<GambatteLink>(),
GetCoreSyncSettings<GambatteLink>(),
Deterministic);
// other stuff todo
break;
case "AppleII":
var assets = xmlGame.Assets.Select(a => Database.GetGameInfo(a.Value, a.Key));
var roms = xmlGame.Assets.Select(a => a.Value);
nextEmulator = new AppleII(
nextComm,
assets,
roms);
break;
default:
return false;
}
}
catch (Exception ex)
{
2015-04-21 23:50:15 +00:00
try
{
// need to get rid of this hack at some point
rom = new RomGame(file);
((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
byte[] romData = null;
byte[] xmlData = rom.FileData;
game = rom.GameInfo;
game.System = "SNES";
var snes = new LibsnesCore(game, romData, Deterministic, xmlData, nextComm, GetCoreSettings<LibsnesCore>(), GetCoreSyncSettings<LibsnesCore>());
nextEmulator = snes;
}
catch
{
DoLoadErrorCallback(ex.ToString(), "DGB", LoadErrorType.XML);
return false;
}
}
}
else // most extensions
{
rom = new RomGame(file);
2014-12-11 08:30:37 +00:00
//hacky for now
if (file.Extension.ToLower() == ".exe")
{
rom.GameInfo.System = "PSX";
}
if (string.IsNullOrEmpty(rom.GameInfo.System))
{
// Has the user picked a preference for this extension?
if (PreferredPlatformIsDefined(rom.Extension.ToLower()))
{
rom.GameInfo.System = Global.Config.PreferredPlatformsForExtensions[rom.Extension.ToLower()];
}
else if (ChoosePlatform != null)
{
var result = ChoosePlatform(rom);
if (!string.IsNullOrEmpty(result))
{
rom.GameInfo.System = result;
}
else
{
cancel = true;
}
}
}
game = rom.GameInfo;
2013-12-29 23:54:40 +00:00
var isXml = false;
// other xml has already been handled
if (file.Extension.ToLower() == ".xml")
{
game.System = "SNES";
isXml = true;
}
CoreInventory.Core core = null;
switch (game.System)
{
default:
core = CoreInventory.Instance[game.System];
break;
case null:
// The user picked nothing in the Core picker
break;
2015-02-08 21:51:15 +00:00
case "83P":
var ti83Bios = ((CoreFileProvider)nextComm.CoreFileProvider).GetFirmware("TI83", "Rom", true);
var ti83BiosPath = ((CoreFileProvider)nextComm.CoreFileProvider).GetFirmwarePath("TI83", "Rom", true);
using (var ti83AsHawkFile = new HawkFile())
{
ti83AsHawkFile.Open(ti83BiosPath);
var ti83BiosAsRom = new RomGame(ti83AsHawkFile);
var ti83 = new TI83(nextComm, ti83BiosAsRom.GameInfo, ti83Bios, GetCoreSettings<TI83>());
ti83.LinkPort.SendFileToCalc(File.OpenRead(path), false);
nextEmulator = ti83;
}
break;
case "SNES":
if (Global.Config.SNES_InSnes9x && VersionInfo.DeveloperBuild)
{
core = CoreInventory.Instance["SNES", "Snes9x"];
}
else
{
// 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;
var snes = new LibsnesCore(game, romData, Deterministic, xmlData, nextComm, GetCoreSettings<LibsnesCore>(), GetCoreSyncSettings<LibsnesCore>());
nextEmulator = snes;
}
2013-12-29 23:54:40 +00:00
break;
case "NES":
if (!Global.Config.NES_InQuickNES || forceAccurateCore)
{
core = CoreInventory.Instance["NES", "NesHawk"];
}
else
{
core = CoreInventory.Instance["NES", "QuickNes"];
}
2014-01-08 03:53:53 +00:00
break;
case "GB":
case "GBC":
if (!Global.Config.GB_AsSGB)
{
core = CoreInventory.Instance["GB", "Gambatte"];
}
else
{
try
{
game.System = "SNES";
game.AddOption("SGB");
var snes = new LibsnesCore(game, rom.FileData, Deterministic, null, nextComm, GetCoreSettings<LibsnesCore>(), GetCoreSyncSettings<LibsnesCore>());
nextEmulator = snes;
}
catch
{
// failed to load SGB bios or game does not support SGB mode.
// To avoid catch-22, disable SGB mode
Global.Config.GB_AsSGB = false;
throw;
}
}
2013-12-29 23:54:40 +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);
break;
case "C64":
2013-12-29 23:54:40 +00:00
var c64 = new C64(nextComm, game, rom.RomData, rom.Extension);
nextEmulator = c64;
break;
case "GBA":
2014-08-24 14:12:31 +00:00
//core = CoreInventory.Instance["GBA", "Meteor"];
core = CoreInventory.Instance["GBA", "VBA-Next"];
//core = CoreInventory.Instance["GBA", "mGBA"];
break;
2014-12-11 08:30:37 +00:00
case "PSX":
nextEmulator = new Octoshock(nextComm, null, null, rom.FileData, GetCoreSettings<Octoshock>(), GetCoreSyncSettings<Octoshock>());
2014-12-11 08:30:37 +00:00
nextEmulator.CoreComm.RomStatusDetails = "PSX etc.";
break;
case "DEBUG":
if (VersionInfo.DeveloperBuild)
{
nextEmulator = LibRetroEmulator.CreateDebug(nextComm, rom.RomData);
}
2013-12-29 23:54:40 +00:00
break;
}
if (core != null)
{
// use coreinventory
nextEmulator = core.Create(nextComm, game, rom.RomData, rom.FileData, Deterministic, GetCoreSettings(core.Type), GetCoreSyncSettings(core.Type));
}
}
if (nextEmulator == null)
{
if (!cancel)
{
DoLoadErrorCallback("No core could load the rom.", null);
}
return false;
}
}
catch (Exception ex)
{
string system = null;
if (game != null)
{
system = game.System;
}
// 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;
// Specific hack here, as we get more cores of the same system, this isn't scalable
if (ex is UnsupportedGameException)
{
if (system == "NES")
{
DoMessageCallback("Unable to use quicknes, using NESHawk instead");
}
return LoadRom(path, nextComm, forceAccurateCore: true);
}
else if (ex is MissingFirmwareException)
{
DoLoadErrorCallback(ex.Message, system, LoadErrorType.MissingFirmware);
}
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);
}
else
{
DoLoadErrorCallback("A core accepted the rom, but threw an exception while loading it:\n\n" + ex, system);
}
return false;
}
2013-12-29 23:54:40 +00:00
Rom = rom;
LoadedEmulator = nextEmulator;
Game = game;
return true;
}
}
}
}