diff --git a/src/BizHawk.Client.Common/RomLoader.cs b/src/BizHawk.Client.Common/RomLoader.cs index 7735769a36..694887a7f1 100644 --- a/src/BizHawk.Client.Common/RomLoader.cs +++ b/src/BizHawk.Client.Common/RomLoader.cs @@ -1,24 +1,16 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using BizHawk.Common; -using BizHawk.Common.StringExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores; -using BizHawk.Emulation.Cores.Arcades.MAME; +using BizHawk.Emulation.Cores.Libretro; using BizHawk.Emulation.Cores.Calculators; -using BizHawk.Emulation.Cores.Computers.AmstradCPC; using BizHawk.Emulation.Cores.Computers.AppleII; using BizHawk.Emulation.Cores.Computers.Commodore64; -using BizHawk.Emulation.Cores.Computers.SinclairSpectrum; -using BizHawk.Emulation.Cores.Consoles.ChannelF; -using BizHawk.Emulation.Cores.Consoles.NEC.PCFX; using BizHawk.Emulation.Cores.Consoles.Sega.gpgx; -using BizHawk.Emulation.Cores.Consoles.Sega.Saturn; -using BizHawk.Emulation.Cores.Libretro; using BizHawk.Emulation.Cores.Nintendo.Gameboy; using BizHawk.Emulation.Cores.Nintendo.GBHawkLink; using BizHawk.Emulation.Cores.Nintendo.GBHawkLink3x; @@ -27,10 +19,14 @@ using BizHawk.Emulation.Cores.Nintendo.SNES; using BizHawk.Emulation.Cores.PCEngine; using BizHawk.Emulation.Cores.Sega.GGHawkLink; using BizHawk.Emulation.Cores.Sony.PSX; +using BizHawk.Emulation.Cores.Computers.SinclairSpectrum; +using BizHawk.Emulation.Cores.Arcades.MAME; using BizHawk.Emulation.DiscSystem; -using ICSharpCode.SharpZipLib.Zip.Compression; -using ICSharpCode.SharpZipLib.Zip.Compression.Streams; +using BizHawk.Emulation.Cores.Consoles.Sega.Saturn; +using BizHawk.Emulation.Cores.Consoles.NEC.PCFX; +using BizHawk.Emulation.Cores.Computers.AmstradCPC; +using BizHawk.Emulation.Cores.Consoles.ChannelF; namespace BizHawk.Client.Common { @@ -216,712 +212,6 @@ namespace BizHawk.Client.Common return discs; } - private string SystemFromDiscType(DiscType dt, string fileExt) - { - switch (dt) - { - default: - case DiscType.SonyPSX: - return "PSX"; - case DiscType.SegaSaturn: - return "SAT"; - case DiscType.SonyPSP: - return "PSP"; - case DiscType.MegaCD: - return "GEN"; - case DiscType.PCFX: - return "PCFX"; - case DiscType.TurboCD: - return "PCECD"; - - case DiscType.Amiga: - case DiscType.CDi: - case DiscType.Dreamcast: - case DiscType.GameCube: - case DiscType.NeoGeoCD: - case DiscType.Panasonic3DO: - case DiscType.Playdia: - case DiscType.Wii: - throw new NoAvailableCoreException(dt.ToString()); // no supported emulator core for these (yet) - - case DiscType.AudioDisc: - case DiscType.UnknownCDFS: - case DiscType.UnknownFormat: - return PreferredPlatformIsDefined(fileExt) - ? Global.Config.PreferredPlatformsForExtensions[fileExt] - : "NULL"; - } - } - - private bool TryLoadFromDiscFormatRom(ref (RomGame Rom, IEmulator NextEmulator, GameInfo Game) result, string path, CoreComm nextComm, HawkFile file, string fileExt) - { - if (file.IsArchive) - { - throw new InvalidOperationException("Can't load CD files from archives!"); - } - - //--- load the disc in a context which will let us abort if it's going to take too long - var discMountJob = new DiscMountJob { IN_FromPath = path, IN_SlowLoadAbortThreshold = 8 }; - discMountJob.Run(); - - if (discMountJob.OUT_SlowLoadAborted) - { - DoLoadErrorCallback("This disc would take too long to load. Run it through DiscoHawk first, or find a new rip because this one is probably junk", "", LoadErrorType.DiscError); - return false; - } - - if (discMountJob.OUT_ErrorLevel) - { - throw new InvalidOperationException($"\r\n{discMountJob.OUT_Log}"); - } - - var disc = discMountJob.OUT_Disc; - - // ----------- - // TODO - use more sophisticated IDer - var discType = new DiscIdentifier(disc).DetectDiscType(); - var discHash = discType == DiscType.SonyPSX - ? new DiscHasher(disc).Calculate_PSX_BizIDHash().ToString("X8") - : new DiscHasher(disc).OldHash(); - result.Game = Database.CheckDatabase(discHash) ?? new GameInfo - { - Name = Path.GetFileNameWithoutExtension(file.Name), - Hash = discHash, - System = SystemFromDiscType(new DiscIdentifier(disc).DetectDiscType(), fileExt) // try to use our wizard methods - }; - - switch (result.Game.System) - { - case "GEN": - result.NextEmulator = new GPGX( - nextComm, - result.Game, - null, - new[] { disc }, - GetCoreSettings(), - GetCoreSyncSettings() - ); - break; - case "SAT": - result.NextEmulator = new Saturnus( - nextComm, - new[] { disc }, - Deterministic, - (Saturnus.Settings) GetCoreSettings(), - (Saturnus.SyncSettings) GetCoreSyncSettings() - ); - break; - case "PSX": - result.NextEmulator = new Octoshock( - nextComm, - new List { disc }, - new List { Path.GetFileNameWithoutExtension(path) }, - null, - GetCoreSettings(), - GetCoreSyncSettings(), - result.Game.IsRomStatusBad() || result.Game.Status == RomStatus.NotInDatabase - ? "Disc could not be identified as known-good. Look for a better rip." - : string.Join("\n", - $"Disc was identified (99.99% confidently) as known good with disc id hash CRC32:{discHash}", - "Nonetheless it could be an unrecognized romhack or patched version.", - $"According to redump.org, the ideal hash for entire disc is: CRC32:{result.Game.GetStringValue("dh")}", - "The file you loaded hasn't been hashed entirely (it would take too long)", - "Compare it with the full hash calculated by the PSX menu's Hash Discs tool" - ) - ); - break; - case "PCFX": - result.NextEmulator = new Tst( - nextComm, - new[] { disc }, - (Tst.Settings) GetCoreSettings(), - (Tst.SyncSettings) GetCoreSyncSettings() - ); - break; - case "PCE": - case "PCECD": - result.NextEmulator = new PCEngine( - nextComm, - result.Game, - disc, - GetCoreSettings(), - GetCoreSyncSettings() - ); - break; - } - return true; - } - - private bool TryLoadFromM3UFormatRom(ref (RomGame Rom, IEmulator NextEmulator, GameInfo Game) result, string path, CoreComm nextComm, HawkFile file) - { - // 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)); - var discs = new List(); - var discNames = new List(); - var sw = new StringWriter(); - foreach (var e in m3u.Entries) - { - var disc = DiscType.SonyPSX.Create(e.Path, str => DoLoadErrorCallback(str, "PSX", LoadErrorType.DiscError)); - var discName = Path.GetFileNameWithoutExtension(e.Path); - discNames.Add(discName); - discs.Add(disc); - - sw.WriteLine(Path.GetFileName(e.Path)); - - var discHash = new DiscHasher(disc).Calculate_PSX_BizIDHash().ToString("X8"); - var game = Database.CheckDatabase(discHash); - if (game == null || game.IsRomStatusBad() || game.Status == RomStatus.NotInDatabase) - { - sw.WriteLine("Disc could not be identified as known-good. Look for a better rip."); - } - else - { - sw.WriteLine($"Disc was identified (99.99% confidently) as known good with disc id hash CRC32:{discHash}"); - sw.WriteLine("Nonetheless it could be an unrecognized romhack or patched version."); - sw.WriteLine($"According to redump.org, the ideal hash for entire disc is: CRC32:{game.GetStringValue("dh")}"); - sw.WriteLine("The file you loaded hasn't been hashed entirely (it would take too long)"); - sw.WriteLine("Compare it with the full hash calculated by the PSX menu's Hash Discs tool"); - } - - sw.WriteLine("-------------------------"); - } - - result.NextEmulator = new Octoshock( - nextComm, - discs, - discNames, - null, - GetCoreSettings(), - GetCoreSyncSettings(), - sw.ToString() - ); - result.Game = new GameInfo - { - Name = Path.GetFileNameWithoutExtension(file.Name), - System = "PSX" - }; - return true; - } - - private bool TryLoadFromMiscFormatRom(ref (RomGame Rom, IEmulator NextEmulator, GameInfo Game) result, ref bool cancel, string path, CoreComm nextComm, bool forceAccurateCore, HawkFile file, string fileExt) - { - result.Rom = new RomGame(file); - - // hacky for now - if (fileExt == ".exe") - { - result.Rom.GameInfo.System = "PSX"; - } - else if (fileExt == ".nsf") - { - result.Rom.GameInfo.System = "NES"; - } - - Console.WriteLine(result.Rom.GameInfo.System); - - if (string.IsNullOrEmpty(result.Rom.GameInfo.System)) - { - // Has the user picked a preference for this extension? - var romExt = result.Rom.Extension.ToLowerInvariant(); - if (PreferredPlatformIsDefined(romExt)) - { - result.Rom.GameInfo.System = Global.Config.PreferredPlatformsForExtensions[romExt]; - } - else if (ChoosePlatform != null) - { - var platform = ChoosePlatform(result.Rom); - if (!string.IsNullOrEmpty(platform)) - { - result.Rom.GameInfo.System = platform; - } - else - { - cancel = true; - } - } - } - - result.Game = result.Rom.GameInfo; - - // other xml has already been handled - var isXml = string.Equals(file.Extension, ".xml", StringComparison.InvariantCultureIgnoreCase); - if (isXml) result.Game.System = "SNES"; - - CoreInventory.Core core = null; - - switch (result.Game.System) - { - default: - core = CoreInventory.Instance[result.Game.System]; - break; - - case null: - // The user picked nothing in the Core picker - break; - case "83P": - var ti83Bios = nextComm.CoreFileProvider.GetFirmware("TI83", "Rom", true); - //TODO make the TI-83 a proper firmware file - var ti83BiosPath = Global.FirmwareManager.Request(Global.Config.PathEntries, Global.Config.FirmwareUserSpecifications, "TI83", "Rom"); - using (var ti83AsHawkFile = new HawkFile(ti83BiosPath)) - { - var ti83BiosAsRom = new RomGame(ti83AsHawkFile); - var ti83 = new TI83(ti83BiosAsRom.GameInfo, ti83Bios, GetCoreSettings()); - ti83.LinkPort.SendFileToCalc(File.OpenRead(path.SubstringBefore('|')), false); - result.NextEmulator = ti83; - } - break; - case "SNES": - var useSnes9x = Global.Config.PreferredCores["SNES"] == CoreNames.Snes9X; - if (Global.Config.CoreForcingViaGameDb && !string.IsNullOrEmpty(result.Game.ForcedCore)) - { - var forced = result.Game.ForcedCore.ToLowerInvariant(); - if (forced == "snes9x") useSnes9x = true; - else if (forced == "bsnes") useSnes9x = false; - } - if (useSnes9x) - { - core = CoreInventory.Instance["SNES", CoreNames.Snes9X]; - } - else - { - //HACK need to get rid of this at some point - result.NextEmulator = new LibsnesCore( - result.Game, - isXml ? null : result.Rom.FileData, - isXml ? result.Rom.FileData : null, - Path.GetDirectoryName(path.SubstringBefore('|')), // since we are just getting the directory path, it's safe to remove the archive sub-file (everything after '|') - nextComm, - GetCoreSettings(), - GetCoreSyncSettings() - ); - } - break; - case "NES": - // apply main spur-of-the-moment switcheroo as lowest priority - string preference = Global.Config.PreferredCores["NES"]; - - // if user has saw fit to override in gamedb, apply that - if (Global.Config.CoreForcingViaGameDb && !string.IsNullOrEmpty(result.Game.ForcedCore)) - { - preference = result.Game.ForcedCore.ToLower() switch - { - "quicknes" => CoreNames.QuickNes, - _ => CoreNames.NesHawk - }; - } - - // but only neshawk is accurate - if (forceAccurateCore) - { - preference = CoreNames.NesHawk; - } - - core = CoreInventory.Instance["NES", preference]; - break; - - case "GB": - case "GBC": - if (!Global.Config.GbAsSgb) - { - core = CoreInventory.Instance["GB", Global.Config.PreferredCores["GB"]]; - } - else - { - if (Global.Config.SgbUseBsnes) - { - result.Game.System = "SNES"; - result.Game.AddOption("SGB"); - result.NextEmulator = new LibsnesCore( - result.Game, - result.Rom.FileData, - null, - null, - nextComm, - GetCoreSettings(), - GetCoreSyncSettings() - ); - } - else - { - core = CoreInventory.Instance["SGB", CoreNames.SameBoy]; - } - } - break; - case "C64": - result.NextEmulator = new C64( - nextComm, - new[] { result.Rom.FileData }, - result.Rom.GameInfo, - GetCoreSettings(), - GetCoreSyncSettings() - ); - break; - case "ZXSpectrum": - result.NextEmulator = new ZXSpectrum( - nextComm, - new[] { result.Rom.RomData }, - new List { result.Rom.GameInfo }, - GetCoreSettings(), - GetCoreSyncSettings(), - Deterministic - ); - break; - case "ChannelF": - result.NextEmulator = new ChannelF( - nextComm, - result.Game, - result.Rom.FileData, - GetCoreSettings(), - GetCoreSyncSettings() - ); - break; - case "AmstradCPC": - result.NextEmulator = new AmstradCPC( - nextComm, - Enumerable.Repeat(result.Rom.RomData, 1), - Enumerable.Repeat(result.Rom.GameInfo, 1).ToList(), - GetCoreSettings(), - GetCoreSyncSettings() - ); - break; - case "PSX": - result.NextEmulator = new Octoshock( - nextComm, - null, - null, - result.Rom.FileData, - GetCoreSettings(), - GetCoreSyncSettings(), - "PSX etc." - ); - break; - case "Arcade": - result.NextEmulator = new MAME( - file.Directory, - file.CanonicalName, - GetCoreSyncSettings(), - out var gameName - ); - result.Rom.GameInfo.Name = gameName; - break; - case "GEN": - core = CoreInventory.Instance["GEN", - Global.Config.CoreForcingViaGameDb && result.Game.ForcedCore?.ToLower() == "pico" - ? CoreNames.PicoDrive - : CoreNames.Gpgx - ]; - break; - case "32X": - core = CoreInventory.Instance["GEN", CoreNames.PicoDrive]; - break; - } - - if (core != null) - { - // use CoreInventory - result.NextEmulator = core.Create( - nextComm, - result.Game, - result.Rom.RomData, - result.Rom.FileData, - Deterministic, - GetCoreSettings(core.Type), - GetCoreSyncSettings(core.Type) - ); - } - - return true; - } - - private bool TryLoadFromPSFFormatRom(ref (RomGame Rom, IEmulator NextEmulator, GameInfo Game) result, string path, CoreComm nextComm, HawkFile file) - { - var psf = new PSF(); - psf.Load(path, (instream, size) => - { - var ret = new MemoryStream(); - new InflaterInputStream(instream, new Inflater(false)).CopyTo(ret); - return ret.ToArray(); - }); - result.NextEmulator = new Octoshock( - nextComm, - psf, - GetCoreSettings(), - GetCoreSyncSettings() - ); - // total garbage, this - result.Rom = new RomGame(file); - result.Game = result.Rom.GameInfo; - return true; - } - - private bool TryLoadFromXMLFormatRom(ref (RomGame Rom, IEmulator NextEmulator, GameInfo Game) result, string path, CoreComm nextComm, HawkFile file) - { - try - { - var xmlGame = XmlGame.Create(file); // if load fails, are we supposed to retry as a bsnes XML???????? - result.Game = xmlGame.GI; - - switch (result.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"); - if (Global.Config.PreferredCores["GB"] == CoreNames.GbHawk) - { - result.NextEmulator = new GBHawkLink( - nextComm, - left, - leftBytes, - right, - rightBytes, - GetCoreSettings(), - GetCoreSyncSettings()); - } - else - { - result.NextEmulator = new GambatteLink( - nextComm, - left, - leftBytes, - right, - rightBytes, - GetCoreSettings(), - GetCoreSyncSettings(), - Deterministic); - } - - // other stuff todo - break; - case "GB3x": - var leftBytes3x = xmlGame.Assets.First().Value; - var centerBytes3x = xmlGame.Assets.Skip(1).First().Value; - var rightBytes3x = xmlGame.Assets.Skip(2).First().Value; - - var left3x = Database.GetGameInfo(leftBytes3x, "left.gb"); - var center3x = Database.GetGameInfo(centerBytes3x, "center.gb"); - var right3x = Database.GetGameInfo(rightBytes3x, "right.gb"); - - result.NextEmulator = new GBHawkLink3x( - nextComm, - left3x, - leftBytes3x, - center3x, - centerBytes3x, - right3x, - rightBytes3x, - GetCoreSettings(), - GetCoreSyncSettings()); - - break; - case "GB4x": - var A_Bytes4x = xmlGame.Assets.First().Value; - var B_Bytes4x = xmlGame.Assets.Skip(1).First().Value; - var C_Bytes4x = xmlGame.Assets.Skip(2).First().Value; - var D_Bytes4x = xmlGame.Assets.Skip(3).First().Value; - - var A_4x = Database.GetGameInfo(A_Bytes4x, "A.gb"); - var B_4x = Database.GetGameInfo(B_Bytes4x, "B.gb"); - var C_4x = Database.GetGameInfo(C_Bytes4x, "C.gb"); - var D_4x = Database.GetGameInfo(D_Bytes4x, "D.gb"); - - result.NextEmulator = new GBHawkLink4x( - nextComm, - A_4x, - A_Bytes4x, - B_4x, - B_Bytes4x, - C_4x, - C_Bytes4x, - D_4x, - D_Bytes4x, - GetCoreSettings(), - GetCoreSyncSettings()); - - break; - case "AppleII": - var roms = xmlGame.Assets.Select(a => a.Value); - result.NextEmulator = new AppleII( - nextComm, - roms, - (AppleII.Settings)GetCoreSettings()); - break; - case "C64": - result.NextEmulator = new C64( - nextComm, - xmlGame.Assets.Select(a => a.Value), - GameInfo.NullInstance, - (C64.C64Settings)GetCoreSettings(), - (C64.C64SyncSettings)GetCoreSyncSettings()); - break; - case "ZXSpectrum": - - var zxGI = new List(); - foreach (var a in xmlGame.Assets) - { - zxGI.Add(new GameInfo { Name = Path.GetFileNameWithoutExtension(a.Key) }); - } - - result.NextEmulator = new ZXSpectrum( - nextComm, - xmlGame.Assets.Select(a => a.Value), - zxGI, - (ZXSpectrum.ZXSpectrumSettings)GetCoreSettings(), - (ZXSpectrum.ZXSpectrumSyncSettings)GetCoreSyncSettings(), - Deterministic); - break; - case "AmstradCPC": - - var cpcGI = new List(); - foreach (var a in xmlGame.Assets) - { - cpcGI.Add(new GameInfo { Name = Path.GetFileNameWithoutExtension(a.Key) }); - } - - result.NextEmulator = new AmstradCPC( - nextComm, - xmlGame.Assets.Select(a => a.Value), - cpcGI, - (AmstradCPC.AmstradCPCSettings)GetCoreSettings(), - (AmstradCPC.AmstradCPCSyncSettings)GetCoreSyncSettings()); - break; - case "PSX": - var entries = xmlGame.AssetFullPaths; - var discs = new List(); - var discNames = new List(); - var sw = new StringWriter(); - foreach (var e in entries) - { - var disc = DiscType.SonyPSX.Create(e, str => { DoLoadErrorCallback(str, "PSX", LoadErrorType.DiscError); }); - - var discName = Path.GetFileNameWithoutExtension(e); - discNames.Add(discName); - discs.Add(disc); - - sw.WriteLine("{0}", Path.GetFileName(e)); - - string discHash = new DiscHasher(disc).Calculate_PSX_BizIDHash().ToString("X8"); - result.Game = Database.CheckDatabase(discHash); - if (result.Game == null || result.Game.IsRomStatusBad() || result.Game.Status == RomStatus.NotInDatabase) - { - sw.WriteLine("Disc could not be identified as known-good. Look for a better rip."); - } - else - { - sw.WriteLine($"Disc was identified (99.99% confidently) as known good with disc id hash CRC32:{discHash}"); - sw.WriteLine("Nonetheless it could be an unrecognized romhack or patched version."); - sw.WriteLine($"According to redump.org, the ideal hash for entire disc is: CRC32:{result.Game.GetStringValue("dh")}"); - sw.WriteLine("The file you loaded hasn't been hashed entirely (it would take too long)"); - sw.WriteLine("Compare it with the full hash calculated by the PSX menu's Hash Discs tool"); - } - - sw.WriteLine("-------------------------"); - } - - // todo: copy pasta from PSX .cue section - result.NextEmulator = new Octoshock(nextComm, discs, discNames, null, GetCoreSettings(), GetCoreSyncSettings(), sw.ToString()); - result.Game = new GameInfo - { - Name = Path.GetFileNameWithoutExtension(file.Name), - System = "PSX" - }; - break; - case "SAT": - var saturnDiscs = DiscsFromXml(xmlGame, "SAT", DiscType.SegaSaturn); - if (!saturnDiscs.Any()) - { - return false; - } - - result.NextEmulator = new Saturnus(nextComm, saturnDiscs, Deterministic, - (Saturnus.Settings)GetCoreSettings(), (Saturnus.SyncSettings)GetCoreSyncSettings()); - break; - case "PCFX": - var pcfxDiscs = DiscsFromXml(xmlGame, "PCFX", DiscType.PCFX); - if (!pcfxDiscs.Any()) - { - return false; - } - - result.NextEmulator = new Tst(nextComm, pcfxDiscs, - (Tst.Settings)GetCoreSettings(), (Tst.SyncSettings)GetCoreSyncSettings()); - break; - case "GEN": - var genDiscs = DiscsFromXml(xmlGame, "GEN", DiscType.MegaCD); - var romBytes = xmlGame.Assets - .Where(a => !Disc.IsValidExtension(a.Key)) - .Select(a => a.Value) - .FirstOrDefault(); - if (!genDiscs.Any() && romBytes == null) - { - return false; - } - result.NextEmulator = new GPGX(nextComm, result.Game, romBytes, genDiscs, GetCoreSettings(), GetCoreSyncSettings()); - break; - case "Game Gear": - var leftBytesGG = xmlGame.Assets.First().Value; - var rightBytesGG = xmlGame.Assets.Skip(1).First().Value; - - var leftGG = Database.GetGameInfo(leftBytesGG, "left.gg"); - var rightGG = Database.GetGameInfo(rightBytesGG, "right.gg"); - - result.NextEmulator = new GGHawkLink( - nextComm, - leftGG, - leftBytesGG, - rightGG, - rightBytesGG, - GetCoreSettings(), - GetCoreSyncSettings()); - break; - default: - return false; - } - return true; - } - catch (Exception ex) - { - try - { - //HACK need to get rid of this at some point - result.Rom = new RomGame(file); - result.Game = result.Rom.GameInfo; - result.Game.System = "SNES"; - result.NextEmulator = new LibsnesCore( - result.Game, - null, - result.Rom.FileData, - Path.GetDirectoryName(path.SubstringBefore('|')), // since we are just getting the directory path, it's safe to remove the archive sub-file (everything after '|') - nextComm, - GetCoreSettings(), - GetCoreSyncSettings() - ); - return true; - } - catch - { - DoLoadErrorCallback(ex.ToString(), "DGB", LoadErrorType.Xml); - return false; - } - } - } - public bool LoadRom(string path, CoreComm nextComm, string launchLibretroCore, bool forceAccurateCore = false, int recursiveCount = 0) { if (recursiveCount > 1) // hack to stop recursive calls from endlessly rerunning if we can't load it @@ -930,26 +220,43 @@ namespace BizHawk.Client.Common return false; } - if (path == null) return false; + bool cancel = false; - using var file = new HawkFile(); // I'm almost certain that we'll see NREs unless the below call to Open happens, so I deprecated this ctor as a nag --yoshi + if (path == null) + { + return false; + } + + using var file = new HawkFile(); // I'm almost certain that we'll see NREs unless Open or Parse is called, so I deprecated this ctor as a nag --yoshi + // only try mounting a file if a filename was given if (!string.IsNullOrEmpty(path)) { - // only try mounting a file if a filename was given - if (OpenAdvanced is OpenAdvanced_MAME) file.NonArchiveExtensions = new[] { ".zip", ".7z" }; // MAME uses these extensions for arcade ROMs, but also accepts all sorts of variations of archives, folders, and files. if we let archive loader handle this, it won't know where to stop, since it'd require MAME's ROM database (which contains ROM names and blob hashes) to look things up, and even then it might be confused by archive/folder structure. so assume the user provides the proper ROM directly, and handle possible errors later + // MAME uses these extensions for arcade ROMs, but also accepts all sorts of variations of archives, folders, and files. if we let archive loader handle this, it won't know where to stop, since it'd require MAME's ROM database (which contains ROM names and blob hashes) to look things up, and even then it might be confused by archive/folder structure + // so assume the user provides the proper ROM directly, and handle possible errors later + if (OpenAdvanced is OpenAdvanced_MAME) + { + file.NonArchiveExtensions = new[] { ".zip", ".7z" }; + } + file.Open(path); - if (!file.Exists) return false; // if the provided file doesn't even exist, give up! - } - else - { - Debug.WriteLine("CanonicalFullPath getter is about to be called on an uninitialised HawkFile"); + + // if the provided file doesn't even exist, give up! + if (!file.Exists) + { + return false; + } } CanonicalFullPath = file.CanonicalFullPath; - (RomGame Rom, IEmulator NextEmulator, GameInfo Game) result = (null, null, null); + IEmulator nextEmulator = null; + RomGame rom = null; + GameInfo game = null; + try { + string ext = null; + if (OpenAdvanced is OpenAdvanced_Libretro) { // kind of dirty.. we need to stash this, and then we can unstash it in a moment, in case the core doesn't fail @@ -957,20 +264,17 @@ namespace BizHawk.Client.Common // must be done before LoadNoGame (which triggers retro_init and the paths to be consumed by the core) // game name == name of core - Global.Game = result.Game = new GameInfo - { - Name = Path.GetFileNameWithoutExtension(launchLibretroCore), - System = "Libretro" - }; - var retro = new LibretroCore(nextComm, result.Game, launchLibretroCore); - result.NextEmulator = retro; + string codePathPart = Path.GetFileNameWithoutExtension(launchLibretroCore); + Global.Game = game = new GameInfo { Name = codePathPart, System = "Libretro" }; + var retro = new LibretroCore(nextComm, game, launchLibretroCore); + nextEmulator = retro; if (retro.Description.SupportsNoGame && string.IsNullOrEmpty(path)) { // if we are allowed to run NoGame and we don't have a game, boot up the core that way - var ret = retro.LoadNoGame(); - + bool ret = retro.LoadNoGame(); Global.Game = oldGame; + if (!ret) { DoLoadErrorCallback("LibretroNoGame failed to load. This is weird", "Libretro"); @@ -982,28 +286,41 @@ namespace BizHawk.Client.Common { bool ret; + // if the core requires an archive file, then try passing the filename of the archive + // (but do we ever need to actually load the contents of the archive file into ram?) if (retro.Description.NeedsArchives) { - // if the core requires an archive file, then try passing the filename of the archive - // (but do we ever need to actually load the contents of the archive file into ram?) - if (file.IsArchiveMember) throw new InvalidOperationException("Should not have bound file member for libretro block_extract core"); + if (file.IsArchiveMember) + { + throw new InvalidOperationException("Should not have bound file member for libretro block_extract core"); + } + ret = retro.LoadPath(file.FullPathWithoutMember); } else { // otherwise load the data or pass the filename, as requested. but.. + if (retro.Description.NeedsRomAsPath && file.IsArchiveMember) + { + throw new InvalidOperationException("Cannot pass archive member to libretro needs_fullpath core"); + } + if (retro.Description.NeedsRomAsPath) { - if (file.IsArchiveMember) throw new InvalidOperationException("Cannot pass archive member to libretro needs_fullpath core"); ret = retro.LoadPath(file.FullPathWithoutMember); } else { - ret = HandleArchiveBinding(file) && retro.LoadData(file.ReadAllBytes(), file.Name); + ret = HandleArchiveBinding(file); + if (ret) + { + ret = retro.LoadData(file.ReadAllBytes(), file.Name); + } } } Global.Game = oldGame; + if (!ret) { DoLoadErrorCallback("Libretro failed to load the given file. This is probably due to a core/content mismatch. Moreover, the process is now likely to be hosed. We suggest you restart the program.", "Libretro"); @@ -1014,10 +331,8 @@ namespace BizHawk.Client.Common } else { - // not libretro => do extension checking - var fileExt = file.Extension.ToLowerInvariant(); - - // at this point, file is either assigned to the ROM path, if it exists, or is empty and CoreComm is not a libretro core + // at this point, file is either assigned to the ROM path, if it exists, + // or is empty and CoreComm is not a libretro core // so, we still need to check path here before continuing if (string.IsNullOrEmpty(path)) { @@ -1025,74 +340,731 @@ namespace BizHawk.Client.Common return false; } - // do the archive binding we had to skip - if (!HandleArchiveBinding(file)) return false; + // if not libretro: + // do extension checking + ext = file.Extension.ToLowerInvariant(); - var cancel = false; -#if true - bool success; - if (fileExt == ".m3u") success = TryLoadFromM3UFormatRom(ref result, path, nextComm, file); - else if (Disc.IsValidExtension(fileExt)) success = TryLoadFromDiscFormatRom(ref result, path, nextComm, file, fileExt); - else success = fileExt switch - { - ".xml" => TryLoadFromXMLFormatRom(ref result, path, nextComm, file), - ".psf" => TryLoadFromPSFFormatRom(ref result, path, nextComm, file), - ".minipsf" => TryLoadFromPSFFormatRom(ref result, path, nextComm, file), - _ => TryLoadFromMiscFormatRom(ref result, ref cancel, path, nextComm, forceAccurateCore, file, fileExt) - }; -#else //TODO by changing the execution order, you can make the above look much nicer --yoshi - var success = fileExt switch + // do the archive binding we had to skip + if (!HandleArchiveBinding(file)) { - ".m3u" => TryLoadFromM3UFormatRom(ref result, path, nextComm, file), - ".minipsf" => TryLoadFromPSFFormatRom(ref result, path, nextComm, file), - ".psf" => TryLoadFromPSFFormatRom(ref result, path, nextComm, file), - ".xml" => TryLoadFromXMLFormatRom(ref result, path, nextComm, file), - _ => Disc.IsValidExtension(fileExt) - ? TryLoadFromDiscFormatRom(ref result, path, nextComm, file, fileExt) - : TryLoadFromMiscFormatRom(ref result, ref cancel, path, nextComm, forceAccurateCore, file, fileExt) - }; -#endif - if (!success) return false; - if (result.NextEmulator == null) - { - if (!cancel) DoLoadErrorCallback("No core could load the rom.", null); return false; } } - Rom = result.Rom; - LoadedEmulator = result.NextEmulator; - Game = result.Game; - return true; + if (string.IsNullOrEmpty(ext)) + { + } + else 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)); + var discs = new List(); + var discNames = new List(); + var sw = new StringWriter(); + foreach (var e in m3u.Entries) + { + var disc = DiscType.SonyPSX.Create(e.Path, str => { DoLoadErrorCallback(str, "PSX", LoadErrorType.DiscError); }); + var discName = Path.GetFileNameWithoutExtension(e.Path); + discNames.Add(discName); + discs.Add(disc); + + sw.WriteLine("{0}", Path.GetFileName(e.Path)); + + string discHash = new DiscHasher(disc).Calculate_PSX_BizIDHash().ToString("X8"); + game = Database.CheckDatabase(discHash); + if (game == null || game.IsRomStatusBad() || game.Status == RomStatus.NotInDatabase) + { + sw.WriteLine("Disc could not be identified as known-good. Look for a better rip."); + } + else + { + sw.WriteLine("Disc was identified (99.99% confidently) as known good with disc id hash CRC32:{0:X8}", discHash); + sw.WriteLine("Nonetheless it could be an unrecognized romhack or patched version."); + sw.WriteLine("According to redump.org, the ideal hash for entire disc is: CRC32:{0:X8}", game.GetStringValue("dh")); + sw.WriteLine("The file you loaded hasn't been hashed entirely (it would take too long)"); + sw.WriteLine("Compare it with the full hash calculated by the PSX menu's Hash Discs tool"); + } + + sw.WriteLine("-------------------------"); + } + + nextEmulator = new Octoshock(nextComm, discs, discNames, null, GetCoreSettings(), GetCoreSyncSettings(), sw.ToString()); + game = new GameInfo + { + Name = Path.GetFileNameWithoutExtension(file.Name), + System = "PSX" + }; + } + else if (Disc.IsValidExtension(ext)) + { + if (file.IsArchive) + { + throw new InvalidOperationException("Can't load CD files from archives!"); + } + + //--- load the disc in a context which will let us abort if it's going to take too long + var discMountJob = new DiscMountJob { IN_FromPath = path, IN_SlowLoadAbortThreshold = 8 }; + discMountJob.Run(); + + if (discMountJob.OUT_SlowLoadAborted) + { + DoLoadErrorCallback("This disc would take too long to load. Run it through DiscoHawk first, or find a new rip because this one is probably junk", "", LoadErrorType.DiscError); + return false; + } + + if (discMountJob.OUT_ErrorLevel) + { + throw new InvalidOperationException($"\r\n{discMountJob.OUT_Log}"); + } + + var disc = discMountJob.OUT_Disc; + + // ----------- + // TODO - use more sophisticated IDer + var discType = new DiscIdentifier(disc).DetectDiscType(); + var discHash = discType == DiscType.SonyPSX + ? new DiscHasher(disc).Calculate_PSX_BizIDHash().ToString("X8") + : new DiscHasher(disc).OldHash(); + + game = Database.CheckDatabase(discHash); + if (game == null) + { + // try to use our wizard methods + game = new GameInfo { Name = Path.GetFileNameWithoutExtension(file.Name), Hash = discHash }; + + var dt = new DiscIdentifier(disc).DetectDiscType(); + + switch (dt) + { + 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.PCFX: + game.System = "PCFX"; + break; + case DiscType.TurboCD: + game.System = "PCECD"; + break; + + case DiscType.Amiga: + case DiscType.CDi: + case DiscType.Dreamcast: + case DiscType.GameCube: + case DiscType.NeoGeoCD: + case DiscType.Panasonic3DO: + case DiscType.Playdia: + case DiscType.Wii: + // no supported emulator core for these (yet) + game.System = dt.ToString(); + throw new NoAvailableCoreException(dt.ToString()); + + case DiscType.AudioDisc: + case DiscType.UnknownCDFS: + case DiscType.UnknownFormat: + game.System = PreferredPlatformIsDefined(ext) + ? Global.Config.PreferredPlatformsForExtensions[ext] + : "NULL"; + break; + } + } + + switch (game.System) + { + case "NULL": + nextEmulator = null; + break; + case "GEN": + var genesis = new GPGX(nextComm, game, null, new[] { disc }, GetCoreSettings(), GetCoreSyncSettings()); + nextEmulator = genesis; + break; + case "SAT": + nextEmulator = new Saturnus(nextComm, new[] { disc }, Deterministic, + (Saturnus.Settings)GetCoreSettings(), (Saturnus.SyncSettings)GetCoreSyncSettings()); + break; + case "PSX": + string romDetails; + if (game.IsRomStatusBad() || game.Status == RomStatus.NotInDatabase) + { + romDetails = "Disc could not be identified as known-good. Look for a better rip."; + } + else + { + var sw = new StringWriter(); + sw.WriteLine("Disc was identified (99.99% confidently) as known good with disc id hash CRC32:{0:X8}", discHash); + sw.WriteLine("Nonetheless it could be an unrecognized romhack or patched version."); + sw.WriteLine("According to redump.org, the ideal hash for entire disc is: CRC32:{0:X8}", game.GetStringValue("dh")); + sw.WriteLine("The file you loaded hasn't been hashed entirely (it would take too long)"); + sw.WriteLine("Compare it with the full hash calculated by the PSX menu's Hash Discs tool"); + romDetails = sw.ToString(); + } + + nextEmulator = new Octoshock(nextComm, new List(new[] { disc }), new List(new[] { Path.GetFileNameWithoutExtension(path) }), null, GetCoreSettings(), GetCoreSyncSettings(), romDetails); + break; + case "PCFX": + nextEmulator = new Tst(nextComm, new[] { disc }, + (Tst.Settings)GetCoreSettings(), (Tst.SyncSettings)GetCoreSyncSettings()); + break; + case "PCE": + case "PCECD": + nextEmulator = new PCEngine(nextComm, game, disc, GetCoreSettings(), GetCoreSyncSettings()); + break; + } + } + else if (file.Extension.ToLowerInvariant() == ".xml") + { + try + { + 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"); + if (Global.Config.PreferredCores["GB"] == CoreNames.GbHawk) + { + nextEmulator = new GBHawkLink( + nextComm, + left, + leftBytes, + right, + rightBytes, + GetCoreSettings(), + GetCoreSyncSettings()); + } + else + { + nextEmulator = new GambatteLink( + nextComm, + left, + leftBytes, + right, + rightBytes, + GetCoreSettings(), + GetCoreSyncSettings(), + Deterministic); + } + + // other stuff todo + break; + case "GB3x": + var leftBytes3x = xmlGame.Assets.First().Value; + var centerBytes3x = xmlGame.Assets.Skip(1).First().Value; + var rightBytes3x = xmlGame.Assets.Skip(2).First().Value; + + var left3x = Database.GetGameInfo(leftBytes3x, "left.gb"); + var center3x = Database.GetGameInfo(centerBytes3x, "center.gb"); + var right3x = Database.GetGameInfo(rightBytes3x, "right.gb"); + + nextEmulator = new GBHawkLink3x( + nextComm, + left3x, + leftBytes3x, + center3x, + centerBytes3x, + right3x, + rightBytes3x, + GetCoreSettings(), + GetCoreSyncSettings()); + + break; + case "GB4x": + var A_Bytes4x = xmlGame.Assets.First().Value; + var B_Bytes4x = xmlGame.Assets.Skip(1).First().Value; + var C_Bytes4x = xmlGame.Assets.Skip(2).First().Value; + var D_Bytes4x = xmlGame.Assets.Skip(3).First().Value; + + var A_4x = Database.GetGameInfo(A_Bytes4x, "A.gb"); + var B_4x = Database.GetGameInfo(B_Bytes4x, "B.gb"); + var C_4x = Database.GetGameInfo(C_Bytes4x, "C.gb"); + var D_4x = Database.GetGameInfo(D_Bytes4x, "D.gb"); + + nextEmulator = new GBHawkLink4x( + nextComm, + A_4x, + A_Bytes4x, + B_4x, + B_Bytes4x, + C_4x, + C_Bytes4x, + D_4x, + D_Bytes4x, + GetCoreSettings(), + GetCoreSyncSettings()); + + break; + case "AppleII": + var roms = xmlGame.Assets.Select(a => a.Value); + nextEmulator = new AppleII( + nextComm, + roms, + (AppleII.Settings)GetCoreSettings()); + break; + case "C64": + nextEmulator = new C64( + nextComm, + xmlGame.Assets.Select(a => a.Value), + GameInfo.NullInstance, + (C64.C64Settings)GetCoreSettings(), + (C64.C64SyncSettings)GetCoreSyncSettings()); + break; + case "ZXSpectrum": + + var zxGI = new List(); + foreach (var a in xmlGame.Assets) + { + zxGI.Add(new GameInfo { Name = Path.GetFileNameWithoutExtension(a.Key) }); + } + + nextEmulator = new ZXSpectrum( + nextComm, + xmlGame.Assets.Select(a => a.Value), + zxGI, + (ZXSpectrum.ZXSpectrumSettings)GetCoreSettings(), + (ZXSpectrum.ZXSpectrumSyncSettings)GetCoreSyncSettings(), + Deterministic); + break; + case "AmstradCPC": + + var cpcGI = new List(); + foreach (var a in xmlGame.Assets) + { + cpcGI.Add(new GameInfo { Name = Path.GetFileNameWithoutExtension(a.Key) }); + } + + nextEmulator = new AmstradCPC( + nextComm, + xmlGame.Assets.Select(a => a.Value), + cpcGI, + (AmstradCPC.AmstradCPCSettings)GetCoreSettings(), + (AmstradCPC.AmstradCPCSyncSettings)GetCoreSyncSettings()); + break; + case "PSX": + var entries = xmlGame.AssetFullPaths; + var discs = new List(); + var discNames = new List(); + var sw = new StringWriter(); + foreach (var e in entries) + { + var disc = DiscType.SonyPSX.Create(e, str => { DoLoadErrorCallback(str, "PSX", LoadErrorType.DiscError); }); + + var discName = Path.GetFileNameWithoutExtension(e); + discNames.Add(discName); + discs.Add(disc); + + sw.WriteLine("{0}", Path.GetFileName(e)); + + string discHash = new DiscHasher(disc).Calculate_PSX_BizIDHash().ToString("X8"); + game = Database.CheckDatabase(discHash); + if (game == null || game.IsRomStatusBad() || game.Status == RomStatus.NotInDatabase) + { + sw.WriteLine("Disc could not be identified as known-good. Look for a better rip."); + } + else + { + sw.WriteLine("Disc was identified (99.99% confidently) as known good with disc id hash CRC32:{0:X8}", discHash); + sw.WriteLine("Nonetheless it could be an unrecognized romhack or patched version."); + sw.WriteLine("According to redump.org, the ideal hash for entire disc is: CRC32:{0:X8}", game.GetStringValue("dh")); + sw.WriteLine("The file you loaded hasn't been hashed entirely (it would take too long)"); + sw.WriteLine("Compare it with the full hash calculated by the PSX menu's Hash Discs tool"); + } + + sw.WriteLine("-------------------------"); + } + + // todo: copy pasta from PSX .cue section + nextEmulator = new Octoshock(nextComm, discs, discNames, null, GetCoreSettings(), GetCoreSyncSettings(), sw.ToString()); + game = new GameInfo + { + Name = Path.GetFileNameWithoutExtension(file.Name), + System = "PSX" + }; + break; + case "SAT": + var saturnDiscs = DiscsFromXml(xmlGame, "SAT", DiscType.SegaSaturn); + if (!saturnDiscs.Any()) + { + return false; + } + + nextEmulator = new Saturnus(nextComm, saturnDiscs, Deterministic, + (Saturnus.Settings)GetCoreSettings(), (Saturnus.SyncSettings)GetCoreSyncSettings()); + break; + case "PCFX": + var pcfxDiscs = DiscsFromXml(xmlGame, "PCFX", DiscType.PCFX); + if (!pcfxDiscs.Any()) + { + return false; + } + + nextEmulator = new Tst(nextComm, pcfxDiscs, + (Tst.Settings)GetCoreSettings(), (Tst.SyncSettings)GetCoreSyncSettings()); + break; + case "GEN": + var genDiscs = DiscsFromXml(xmlGame, "GEN", DiscType.MegaCD); + var romBytes = xmlGame.Assets + .Where(a => !Disc.IsValidExtension(a.Key)) + .Select(a => a.Value) + .FirstOrDefault(); + if (!genDiscs.Any() && romBytes == null) + { + return false; + } + nextEmulator = new GPGX(nextComm, game, romBytes, genDiscs, GetCoreSettings(), GetCoreSyncSettings()); + break; + case "Game Gear": + var leftBytesGG = xmlGame.Assets.First().Value; + var rightBytesGG = xmlGame.Assets.Skip(1).First().Value; + + var leftGG = Database.GetGameInfo(leftBytesGG, "left.gg"); + var rightGG = Database.GetGameInfo(rightBytesGG, "right.gg"); + + nextEmulator = new GGHawkLink( + nextComm, + leftGG, + leftBytesGG, + rightGG, + rightBytesGG, + GetCoreSettings(), + GetCoreSyncSettings()); + break; + default: + return false; + } + } + catch (Exception ex) + { + try + { + // need to get rid of this hack at some point + rom = new RomGame(file); + var basePath = Path.GetDirectoryName(path.Replace("|", "")); // Dirty hack to get around archive filenames (since we are just getting the directory path, it is safe to mangle the filename + byte[] xmlData = rom.FileData; + + game = rom.GameInfo; + game.System = "SNES"; + + var snes = new LibsnesCore(game, null, xmlData, basePath, nextComm, GetCoreSettings(), GetCoreSyncSettings()); + nextEmulator = snes; + } + catch + { + DoLoadErrorCallback(ex.ToString(), "DGB", LoadErrorType.Xml); + return false; + } + } + } + else if (file.Extension.ToLowerInvariant() == ".psf" || file.Extension.ToLowerInvariant() == ".minipsf") + { + Func cbDeflater = (Stream instream, int size) => + { + var inflater = new ICSharpCode.SharpZipLib.Zip.Compression.Inflater(false); + var iis = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream(instream, inflater); + MemoryStream ret = new MemoryStream(); + iis.CopyTo(ret); + return ret.ToArray(); + }; + PSF psf = new PSF(); + psf.Load(path, cbDeflater); + nextEmulator = new Octoshock(nextComm, psf, GetCoreSettings(), GetCoreSyncSettings()); + + // total garbage, this + rom = new RomGame(file); + game = rom.GameInfo; + } + else + { + rom = new RomGame(file); + + // hacky for now + if (file.Extension.ToLowerInvariant() == ".exe") + { + rom.GameInfo.System = "PSX"; + } + else if (file.Extension.ToLowerInvariant() == ".nsf") + { + rom.GameInfo.System = "NES"; + } + + Console.WriteLine(rom.GameInfo.System); + + if (string.IsNullOrEmpty(rom.GameInfo.System)) + { + // Has the user picked a preference for this extension? + if (PreferredPlatformIsDefined(rom.Extension.ToLowerInvariant())) + { + rom.GameInfo.System = Global.Config.PreferredPlatformsForExtensions[rom.Extension.ToLowerInvariant()]; + } + else if (ChoosePlatform != null) + { + var result = ChoosePlatform(rom); + if (!string.IsNullOrEmpty(result)) + { + rom.GameInfo.System = result; + } + else + { + cancel = true; + } + } + } + + game = rom.GameInfo; + + var isXml = false; + + // other xml has already been handled + if (file.Extension.ToLowerInvariant() == ".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; + case "83P": + var ti83Bios = nextComm.CoreFileProvider.GetFirmware("TI83", "Rom", true); + + // TODO: make the ti-83 a proper firmware file + var ti83BiosPath = Global.FirmwareManager.Request(Global.Config.PathEntries, Global.Config.FirmwareUserSpecifications, "TI83", "Rom"); + using (var ti83AsHawkFile = new HawkFile(ti83BiosPath)) + { + var ti83BiosAsRom = new RomGame(ti83AsHawkFile); + var ti83 = new TI83(ti83BiosAsRom.GameInfo, ti83Bios, GetCoreSettings()); + ti83.LinkPort.SendFileToCalc(File.OpenRead(path.Split('|').First()), false); + nextEmulator = ti83; + } + + break; + case "SNES": + bool useSnes9x = Global.Config.PreferredCores["SNES"] == CoreNames.Snes9X; + if (Global.Config.CoreForcingViaGameDb && !string.IsNullOrEmpty(game.ForcedCore)) + { + if (game.ForcedCore.ToLower() == "snes9x") + { + useSnes9x = true; + } + else if (game.ForcedCore.ToLower() == "bsnes") + { + useSnes9x = false; + } + } + + if (useSnes9x) + { + core = CoreInventory.Instance["SNES", CoreNames.Snes9X]; + } + else + { + // need to get rid of this hack at some point + var basePath = Path.GetDirectoryName(path.Replace("|", "")); // 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, xmlData, basePath, nextComm, GetCoreSettings(), GetCoreSyncSettings()); + nextEmulator = snes; + } + + break; + case "NES": + { + // apply main spur-of-the-moment switcheroo as lowest priority + string preference = Global.Config.PreferredCores["NES"]; + + // if user has saw fit to override in gamedb, apply that + if (Global.Config.CoreForcingViaGameDb && !string.IsNullOrEmpty(game.ForcedCore)) + { + preference = game.ForcedCore.ToLower() switch + { + "quicknes" => CoreNames.QuickNes, + _ => CoreNames.NesHawk + }; + } + + // but only neshawk is accurate + if (forceAccurateCore) + { + preference = CoreNames.NesHawk; + } + + core = CoreInventory.Instance["NES", preference]; + } + break; + + case "GB": + case "GBC": + if (!Global.Config.GbAsSgb) + { + core = CoreInventory.Instance["GB", Global.Config.PreferredCores["GB"]]; + } + else + { + if (Global.Config.SgbUseBsnes) + { + game.System = "SNES"; + game.AddOption("SGB"); + var snes = new LibsnesCore(game, rom.FileData, null, null, nextComm, GetCoreSettings(), GetCoreSyncSettings()); + nextEmulator = snes; + } + else + { + core = CoreInventory.Instance["SGB", CoreNames.SameBoy]; + } + } + break; + case "C64": + var c64 = new C64(nextComm, Enumerable.Repeat(rom.FileData, 1), rom.GameInfo, GetCoreSettings(), GetCoreSyncSettings()); + nextEmulator = c64; + break; + case "ZXSpectrum": + var zx = new ZXSpectrum(nextComm, + Enumerable.Repeat(rom.RomData, 1), + Enumerable.Repeat(rom.GameInfo, 1).ToList(), + GetCoreSettings(), + GetCoreSyncSettings(), + Deterministic); + nextEmulator = zx; + break; + case "ChannelF": + nextEmulator = new ChannelF(nextComm, game, rom.FileData, GetCoreSettings(), GetCoreSyncSettings()); + break; + case "AmstradCPC": + var cpc = new AmstradCPC(nextComm, Enumerable.Repeat(rom.RomData, 1), Enumerable.Repeat(rom.GameInfo, 1).ToList(), GetCoreSettings(), GetCoreSyncSettings()); + nextEmulator = cpc; + break; + case "PSX": + nextEmulator = new Octoshock(nextComm, null, null, rom.FileData, GetCoreSettings(), GetCoreSyncSettings(), "PSX etc."); + break; + case "Arcade": + nextEmulator = new MAME(file.Directory, file.CanonicalName, GetCoreSyncSettings(), out var gameName); + rom.GameInfo.Name = gameName; + break; + case "GEN": + if (Global.Config.CoreForcingViaGameDb && game.ForcedCore?.ToLower() == "pico") + { + core = CoreInventory.Instance["GEN", CoreNames.PicoDrive]; + } + else + { + core = CoreInventory.Instance["GEN", CoreNames.Gpgx]; + } + + break; + case "32X": + core = CoreInventory.Instance["GEN", CoreNames.PicoDrive]; + 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) { - while (ex.InnerException != null) ex = ex.InnerException; // 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 - var system = result.Game?.System; //TODO We wouldn't need to pass the `result` tuple around if this could be passed via the exceptions' ctors (difficult since this var is used in the default case below). Passing the tuple around isn't the worst thing, but it prevents the TryLoadFrom*FormatRom helpers from being pure (without side-effects). --yoshi - switch (ex) + string system = null; + if (game != null) { - case UnsupportedGameException _: - // Specific hack here, as we get more cores of the same system, this isn't scalable - if (system == "NES") DoMessageCallback("Unable to use quicknes, using NESHawk instead"); - return LoadRom(path, nextComm, launchLibretroCore, true, recursiveCount + 1); - case MissingFirmwareException mfe: - DoLoadErrorCallback(mfe.Message, system, path, Deterministic, LoadErrorType.MissingFirmware); - break; - case CGBNotSupportedException _: - // failed to load SGB bios or game does not support SGB mode. To avoid catch-22, disable SGB mode - Global.Config.GbAsSgb = false; - DoMessageCallback("Failed to load a GB rom in SGB mode. Disabling SGB Mode."); - return LoadRom(path, nextComm, launchLibretroCore, false, recursiveCount + 1); - case NoAvailableCoreException nace: - // handle exceptions thrown by the new detected systems that BizHawk does not have cores for - DoLoadErrorCallback($"{nace.Message}\n\n{nace}", system); - break; - default: - DoLoadErrorCallback($"A core accepted the rom, but threw an exception while loading it:\n\n{ex}", system); - break; + 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, launchLibretroCore, true, recursiveCount + 1); + } + + if (ex is MissingFirmwareException) + { + DoLoadErrorCallback(ex.Message, system, path, Deterministic, LoadErrorType.MissingFirmware); + } + else if (ex is CGBNotSupportedException) + { + // failed to load SGB bios or game does not support SGB mode. + // To avoid catch-22, disable SGB mode + Global.Config.GbAsSgb = false; + DoMessageCallback("Failed to load a GB rom in SGB mode. Disabling SGB Mode."); + return LoadRom(path, nextComm, launchLibretroCore, false, recursiveCount + 1); + } + + // handle exceptions thrown by the new detected systems that BizHawk does not have cores for + else if (ex is NoAvailableCoreException) + { + DoLoadErrorCallback($"{ex.Message}\n\n{ex}", system); + } + + else + { + DoLoadErrorCallback($"A core accepted the rom, but threw an exception while loading it:\n\n{ex}", system); + } + return false; } + + Rom = rom; + LoadedEmulator = nextEmulator; + Game = game; + return true; } } }