diff --git a/src/BizHawk.Client.Common/RomLoader.cs b/src/BizHawk.Client.Common/RomLoader.cs index f644de10e2..e4b5d4ca40 100644 --- a/src/BizHawk.Client.Common/RomLoader.cs +++ b/src/BizHawk.Client.Common/RomLoader.cs @@ -423,7 +423,50 @@ namespace BizHawk.Client.Common throw new NotImplementedException("M3U not supported!"); } - private void LoadOther(string path, CoreComm nextComm, bool forceAccurateCore, HawkFile file, out IEmulator nextEmulator, out RomGame rom, out GameInfo game, out bool cancel) + private static string ForcedCoreToCoreName(string forcedCore) + { + // TODO: Is this yet another list of core names really needed? Let's just make the gamedb less dumb + return forcedCore switch + { + "snes9x" => CoreNames.Snes9X, + "bsnes" => CoreNames.Bsnes, + "quicknes" => CoreNames.QuickNes, + "pico" => CoreNames.PicoDrive, + _ => null + }; + } + + private IEmulator MakeCoreFromCoreInventory(string systemId, CoreInventoryParameters cip, string forcedCoreGameDb) + { + _config.PreferredCores.TryGetValue(systemId, out var preferredCore); + var forcedCore = ForcedCoreToCoreName(forcedCoreGameDb); + var cores = CoreInventory.Instance.GetCores(systemId) + .OrderBy(c => + { + if (c.Name == preferredCore) + return (int)CorePriority.UserPreference; + else if (c.Name == forcedCore) + return (int)CorePriority.GameDbPreference; + else + return (int)c.Priority; + }) + .ToList(); + var exceptions = new List(); + foreach (var core in cores) + { + try + { + return core.Create(cip); + } + catch (Exception e) + { + exceptions.Add(e); + } + } + throw new AggregateException("No core could load the ROM", exceptions); + } + + private void LoadOther(string path, CoreComm nextComm, HawkFile file, out IEmulator nextEmulator, out RomGame rom, out GameInfo game, out bool cancel) { cancel = false; rom = new RomGame(file); @@ -463,92 +506,17 @@ namespace BizHawk.Client.Common game = rom.GameInfo; - var isXml = false; - if (file.Extension.ToLowerInvariant() == ".xml") - { - game.System = "SNES"; // other xml has already been handled - isXml = true; - } - nextEmulator = null; if (game.System == null) return; // The user picked nothing in the Core picker - CoreInventory.Core core; switch (game.System) { - case "SNES": - var name = game.ForcedCore?.ToLower() switch - { - "snes9x" => CoreNames.Snes9X, - "bsnes" => CoreNames.Bsnes, - _ => _config.PreferredCores["SNES"] - }; - try - { - core = CoreInventory.Instance["SNES", name]; - } - catch // TODO: CoreInventory should support some sort of trygetvalue - { - // need to get rid of this hack at some point - nextEmulator = new LibsnesCore( - game, - isXml ? null : rom.FileData, - isXml ? rom.FileData : null, - Path.GetDirectoryName(path.SubstringBefore('|')), - nextComm, - GetCoreSettings(), - GetCoreSyncSettings() - ); - return; - } - break; - case "NES": - // apply main spur-of-the-moment switcheroo as lowest priority - var preference = _config.PreferredCores["NES"]; - - // if user has saw fit to override in gamedb, apply that - if (!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 (_config.GbAsSgb) { - if (_config.SgbUseBsnes) - { - game.System = "SNES"; - game.AddOption("SGB", ""); - nextEmulator = new LibsnesCore( - game, - rom.FileData, - null, - null, - nextComm, - GetCoreSettings(), - GetCoreSyncSettings() - ); - return; - } - core = CoreInventory.Instance["SGB", CoreNames.SameBoy]; - } - else - { - core = CoreInventory.Instance["GB", _config.PreferredCores["GB"]]; + game.System = "SGB"; } break; case "ChannelF": @@ -567,17 +535,10 @@ namespace BizHawk.Client.Common ); rom.GameInfo.Name = gameName; return; - case "GEN": - core = CoreInventory.Instance["GEN", game.ForcedCore?.ToLower() == "pico" ? CoreNames.PicoDrive : CoreNames.Gpgx]; - break; default: - core = _config.PreferredCores.TryGetValue(game.System, out var coreName) - ? CoreInventory.Instance[game.System, coreName] - : CoreInventory.Instance[game.System]; break; } - - nextEmulator = core.Create(new CoreInventoryParameters(this) + var cip = new CoreInventoryParameters(this) { Comm = nextComm, Game = game, @@ -593,7 +554,8 @@ namespace BizHawk.Client.Common }, DeterministicEmulationRequested = Deterministic - }); + }; + nextEmulator = MakeCoreFromCoreInventory(game.System, cip, game.ForcedCore); } private void LoadPSF(string path, CoreComm nextComm, HawkFile file, out IEmulator nextEmulator, out RomGame rom, out GameInfo game) @@ -747,7 +709,7 @@ namespace BizHawk.Client.Common } } - public bool LoadRom(string path, CoreComm nextComm, string launchLibretroCore, bool forceAccurateCore = false, int recursiveCount = 0) + public bool LoadRom(string path, CoreComm nextComm, string launchLibretroCore, int recursiveCount = 0) { if (path == null) return false; @@ -862,7 +824,7 @@ namespace BizHawk.Client.Common } else { - LoadOther(path, nextComm, forceAccurateCore, file, out nextEmulator, out rom, out game, out cancel); // must be called after LoadXML because of SNES hacks + LoadOther(path, nextComm, file, out nextEmulator, out rom, out game, out cancel); // must be called after LoadXML because of SNES hacks } break; } @@ -894,7 +856,7 @@ namespace BizHawk.Client.Common DoMessageCallback("Unable to use quicknes, using NESHawk instead"); } - return LoadRom(path, nextComm, launchLibretroCore, true, recursiveCount + 1); + return LoadRom(path, nextComm, launchLibretroCore, recursiveCount + 1); } else if (ex is MissingFirmwareException) { @@ -906,7 +868,7 @@ namespace BizHawk.Client.Common // To avoid catch-22, disable SGB mode _config.GbAsSgb = false; DoMessageCallback("Failed to load a GB rom in SGB mode. Disabling SGB Mode."); - return LoadRom(path, nextComm, launchLibretroCore, false, recursiveCount + 1); + return LoadRom(path, nextComm, launchLibretroCore, recursiveCount + 1); } else if (ex is NoAvailableCoreException) { diff --git a/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/HyperNyma.cs b/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/HyperNyma.cs index f65b7f0037..26dfec084f 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/HyperNyma.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/HyperNyma.cs @@ -16,7 +16,8 @@ namespace BizHawk.Emulation.Cores.Consoles.NEC.PCE { private readonly LibHyperNyma _hyperNyma; - [CoreConstructor(new[] { "PCE", "SGX" })] + [CoreConstructor("PCE", Priority = CorePriority.Low)] + [CoreConstructor("SGX", Priority = CorePriority.Low)] public HyperNyma(GameInfo game, byte[] rom, CoreComm comm, string extension, NymaSettings settings, NymaSyncSettings syncSettings, bool deterministic) : base(comm, "PCE", "PC Engine Controller", settings, syncSettings) diff --git a/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs b/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs index f8fa795684..4d032bc96f 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs @@ -16,7 +16,8 @@ namespace BizHawk.Emulation.Cores.Consoles.NEC.PCE { private readonly LibTurboNyma _turboNyma; - [CoreConstructor(new[] { "PCE", "SGX" })] + [CoreConstructor("PCE")] + [CoreConstructor("SGX")] public TurboNyma(GameInfo game, byte[] rom, CoreComm comm, string extension, NymaSettings settings, NymaSyncSettings syncSettings, bool deterministic) : base(comm, "PCE", "PC Engine Controller", settings, syncSettings) diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs index 457f5a20d6..fbd894a4cd 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs @@ -93,7 +93,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk private static byte[] GBA_override = { 0xFF, 0x00, 0xCD, 0x03, 0x35, 0xAA, 0x31, 0x90, 0x94, 0x00, 0x00, 0x00, 0x00 }; - [CoreConstructor(new[] { "GB", "GBC" })] + [CoreConstructor("GB")] + [CoreConstructor("GBC")] public GBHawk(CoreComm comm, GameInfo game, byte[] rom, /*string gameDbFn,*/ GBSettings settings, GBSyncSettings syncSettings) { var ser = new BasicServiceProvider(this); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index 61cd1c5346..ec3b44d0cd 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -23,7 +23,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy IBoardInfo, IRomInfo, IDebuggable, ISettable, IGameboyCommon, ICycleTiming, ILinkable { - [CoreConstructor(new[] { "GB", "GBC" })] + [CoreConstructor("GB")] + [CoreConstructor("GBC")] public Gameboy(CoreComm comm, GameInfo game, byte[] file, Gameboy.GambatteSettings settings, Gameboy.GambatteSyncSettings syncSettings, bool deterministic) { var ser = new BasicServiceProvider(this); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs index 90cd7909f8..fa759b719d 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -41,6 +41,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy { } [CoreConstructor("GB")] + [CoreConstructor("GBC")] public Sameboy(CoreComm comm, byte[] rom, Settings settings, SyncSettings syncSettings, bool deterministic) : this(rom, comm, false, settings, syncSettings, deterministic) { } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs index e24dd86f21..a0d862fcee 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs @@ -33,7 +33,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES QN.qn_setup_mappers(); } - [CoreConstructor("NES")] + [CoreConstructor("NES", Priority = CorePriority.Low)] public QuickNES(byte[] file, QuickNESSettings settings, QuickNESSyncSettings syncSettings) { FP = OSTailoredCode.IsUnixHost diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs index e393adaf2a..b76179506d 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs @@ -29,6 +29,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES public unsafe partial class LibsnesCore : IEmulator, IVideoProvider, ISaveRam, IStatable, IInputPollable, IRegionable, ICodeDataLogger, IDebuggable, ISettable { + [CoreConstructor("SGB")] + [CoreConstructor("SNES")] + public LibsnesCore(GameInfo game, byte[] rom, CoreComm comm, + LibsnesCore.SnesSettings settings, LibsnesCore.SnesSyncSettings syncSettings) + :this(game, rom, null, null, comm, settings, syncSettings) + {} + public LibsnesCore(GameInfo game, byte[] romData, byte[] xmlData, string baseRomPath, CoreComm comm, LibsnesCore.SnesSettings settings, LibsnesCore.SnesSyncSettings syncSettings) { @@ -47,7 +54,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES CoreComm = comm; byte[] sgbRomData = null; - if (game["SGB"]) + if (game.System == "SGB") { if ((romData[0x143] & 0xc0) == 0xc0) { @@ -112,7 +119,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES romData = newData; } - if (game["SGB"]) + if (game.System == "SGB") { IsSGB = true; SystemId = "SNES"; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubGBHawk/SubGBHawk.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubGBHawk/SubGBHawk.cs index b3a9b8f5b0..2c8b6f2d72 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubGBHawk/SubGBHawk.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubGBHawk/SubGBHawk.cs @@ -14,7 +14,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.SubGBHawk public partial class SubGBHawk : IEmulator, IStatable, IInputPollable, ISettable, IDebuggable { - [CoreConstructor(new[] { "GB", "GBC" })] + [CoreConstructor("GB", Priority = CorePriority.SuperLow)] + [CoreConstructor("GBC", Priority = CorePriority.SuperLow)] public SubGBHawk(CoreComm comm, GameInfo game, byte[] rom, /*string gameDbFn,*/ GBHawk.GBHawk.GBSettings settings, GBHawk.GBHawk.GBSyncSettings syncSettings) { diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.cs index ad48699185..5014ef8b65 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.cs @@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk public partial class SubNESHawk : IEmulator, IStatable, IInputPollable, ISettable { - [CoreConstructor("NES")] + [CoreConstructor("NES", Priority = CorePriority.SuperLow)] public SubNESHawk(CoreComm comm, GameInfo game, byte[] rom, /*string gameDbFn,*/ NES.NES.NESSettings settings, NES.NES.NESSyncSettings syncSettings) { var subNesSettings = (NES.NES.NESSettings)settings ?? new NES.NES.NESSettings(); diff --git a/src/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs b/src/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs index e8b11bda46..c82c2ae553 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs @@ -21,7 +21,8 @@ namespace BizHawk.Emulation.Cores.PCEngine IDebuggable, ISettable, IDriveLight, ICodeDataLogger, IPceGpuView { - [CoreConstructor(new[] { "PCE", "SGX" })] + [CoreConstructor("PCE", Priority = CorePriority.Low)] + [CoreConstructor("SGX", Priority = CorePriority.Low)] public PCEngine(GameInfo game, byte[] rom, PCEngine.PCESettings settings, PCEngine.PCESyncSettings syncSettings) { switch (game.System) diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/PicoDrive/PicoDrive.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/PicoDrive/PicoDrive.cs index 1ba258f1ed..5491733af9 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Sega/PicoDrive/PicoDrive.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/PicoDrive/PicoDrive.cs @@ -19,7 +19,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.PicoDrive private DiscSectorReader _cdReader; private bool _isPal; - [CoreConstructor(new[] { "GEN", "32X" })] + [CoreConstructor("GEN", Priority = CorePriority.Low)] + [CoreConstructor("32X")] public PicoDrive(CoreComm comm, GameInfo game, byte[] rom, bool deterministic, SyncSettings syncSettings) : this(comm, game, rom, null, deterministic, syncSettings) { } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs index bbbd37e66c..354cc31738 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs @@ -23,7 +23,9 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem public partial class SMS : IEmulator, ISoundProvider, ISaveRam, IInputPollable, IRegionable, IDebuggable, ISettable, ICodeDataLogger { - [CoreConstructor(new[] { "SMS", "SG", "GG" })] + [CoreConstructor("SMS")] + [CoreConstructor("SG")] + [CoreConstructor("GG")] public SMS(CoreComm comm, GameInfo game, byte[] rom, SmsSettings settings, SmsSyncSettings syncSettings) { var ser = new BasicServiceProvider(this); diff --git a/src/BizHawk.Emulation.Cores/CoreInventory.cs b/src/BizHawk.Emulation.Cores/CoreInventory.cs index fa3cbe8039..523b91b576 100644 --- a/src/BizHawk.Emulation.Cores/CoreInventory.cs +++ b/src/BizHawk.Emulation.Cores/CoreInventory.cs @@ -28,11 +28,12 @@ namespace BizHawk.Emulation.Cores // If true, this is a new style constructor that takes a CoreLoadParameters object private readonly bool _useCoreLoadParameters; - public Core(string name, Type type, ConstructorInfo ctor) + public Core(string name, Type type, ConstructorInfo ctor, CorePriority priority) { Name = name; Type = type; CTor = ctor; + Priority = priority; var pp = CTor.GetParameters(); if (pp.Length == 1 @@ -65,9 +66,14 @@ namespace BizHawk.Emulation.Cores } } + /// + /// (hopefully) a CoreNames value + /// + /// public string Name { get; } public Type Type { get; } public ConstructorInfo CTor { get; } + public CorePriority Priority { get; } public Type SettingsType { get; } = typeof(object); public Type SyncSettingsType { get; } = typeof(object); @@ -116,53 +122,22 @@ namespace BizHawk.Emulation.Cores } } - private void ProcessConstructor(Type type, string system, CoreAttribute coreAttr, ConstructorInfo cons) + private void ProcessConstructor(Type type, CoreConstructorAttribute consAttr, CoreAttribute coreAttr, ConstructorInfo cons) { - Core core = new Core(coreAttr.CoreName, type, cons); - if (!_systems.TryGetValue(system, out var ss)) + Core core = new Core(coreAttr.CoreName, type, cons, consAttr.Priority); + if (!_systems.TryGetValue(consAttr.System, out var ss)) { ss = new List(); - _systems.Add(system, ss); + _systems.Add(consAttr.System, ss); } ss.Add(core); } - /// - /// find a core matching a particular game.system - /// - public Core this[string system] + public IEnumerable GetCores(string system) { - get - { - List ss = _systems[system]; - if (ss.Count != 1) - { - throw new InvalidOperationException("Ambiguous core selection!"); - } - - return ss[0]; - } - } - - /// - /// find a core matching a particular game.system with a particular CoreAttributes.Name - /// - public Core this[string system, string core] - { - get - { - List ss = _systems[system]; - foreach (Core c in ss) - { - if (c.Name == core) - { - return c; - } - } - - throw new InvalidOperationException("No such core!"); - } + _systems.TryGetValue(system, out var cores); + return cores ?? Enumerable.Empty(); } /// @@ -183,9 +158,9 @@ namespace BizHawk.Emulation.Cores .Where(c => c.GetCustomAttributes(typeof(CoreConstructorAttribute), false).Length > 0); foreach(var con in cons) { - foreach (string system in ((CoreConstructorAttribute)con.GetCustomAttributes(typeof(CoreConstructorAttribute), false)[0]).Systems) + foreach (var consAttr in con.GetCustomAttributes(typeof(CoreConstructorAttribute), false).Cast()) { - ProcessConstructor(typ, system, (CoreAttribute)coreAttr[0], con); + ProcessConstructor(typ, consAttr, (CoreAttribute)coreAttr[0], con); } } } @@ -196,23 +171,45 @@ namespace BizHawk.Emulation.Cores public static readonly CoreInventory Instance = new CoreInventory(new[] { typeof(CoreInventory).Assembly }); } - [AttributeUsage(AttributeTargets.Constructor)] + public enum CorePriority + { + /// + /// The gamedb has requested this core for this game + /// + GameDbPreference = -300, + /// + /// The user has indicated in preferences that this is their favourite core + /// + UserPreference = -200, + + /// + /// A very good core that should be prefered over normal cores. Don't use this? + /// + High = -100, + + /// + /// Most cores should use this + /// + Normal = 0, + /// + /// Experimental, special use, or garbage core + /// + Low = 100, + /// + /// TODO: Do we need this? Does it need a better name? + /// + SuperLow = 200, + } + + [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = true)] public sealed class CoreConstructorAttribute : Attribute { - private readonly List _systems = new List(); - - /// TODO neither array nor is the correct collection to be using here, try / instead - public CoreConstructorAttribute(string[] systems) - { - _systems.AddRange(systems); - } - + public string System { get; } public CoreConstructorAttribute(string system) { - _systems.Add(system); + System = system; } - - public IEnumerable Systems => _systems; + public CorePriority Priority { get; set; } } ///