Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-generate preferred cores from source #4033

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 4 additions & 39 deletions src/BizHawk.Client.Common/config/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,50 +18,15 @@ public class Config
{
public static string ControlDefaultPath => Path.Combine(PathUtils.ExeDirectoryPath, "defctrl.json");

/// <remarks>
/// <c>CoreNames[0]</c> is the default (out-of-the-box) core.<br/>
/// <c>AppliesTo</c> are concatenated to make the submenu's label, and
/// <c>Config.PreferredCores[AppliesTo[0]]</c> (lookup on global <see cref="Config"/> instance) determines which option is shown as checked.<br/>
/// The order within submenus and the order of the submenus themselves are determined by the declaration order here.
/// </remarks>
public static readonly IReadOnlyList<(string[] AppliesTo, string[] CoreNames)> CorePickerUIData = new List<(string[], string[])>
{
([ VSystemID.Raw.A26 ],
[ CoreNames.Atari2600Hawk, CoreNames.Stella ]),
([ VSystemID.Raw.Satellaview ],
[ CoreNames.Bsnes115, CoreNames.SubBsnes115 ]),
([ VSystemID.Raw.GB, VSystemID.Raw.GBC ],
[ CoreNames.Gambatte, CoreNames.Sameboy, CoreNames.GbHawk, CoreNames.SubGbHawk ]),
([ VSystemID.Raw.GBL ],
[ CoreNames.GambatteLink, CoreNames.GBHawkLink, CoreNames.GBHawkLink3x, CoreNames.GBHawkLink4x ]),
([ VSystemID.Raw.SGB ],
[ CoreNames.Gambatte, CoreNames.Bsnes115, CoreNames.SubBsnes115, CoreNames.Bsnes ]),
([ VSystemID.Raw.GEN ],
[ CoreNames.Gpgx, CoreNames.PicoDrive ]),
([ VSystemID.Raw.N64 ],
[ CoreNames.Mupen64Plus, CoreNames.Ares64 ]),
([ VSystemID.Raw.NES ],
[ CoreNames.QuickNes, CoreNames.NesHawk, CoreNames.SubNesHawk ]),
([ VSystemID.Raw.PCE, VSystemID.Raw.PCECD, VSystemID.Raw.SGX, VSystemID.Raw.SGXCD ],
[ CoreNames.TurboNyma, CoreNames.HyperNyma, CoreNames.PceHawk ]),
([ VSystemID.Raw.PSX ],
[ CoreNames.Nymashock, CoreNames.Octoshock ]),
([ VSystemID.Raw.SMS, VSystemID.Raw.GG, VSystemID.Raw.SG ],
[ CoreNames.Gpgx, CoreNames.SMSHawk ]),
([ VSystemID.Raw.SNES ],
[ CoreNames.Snes9X, CoreNames.Bsnes115, CoreNames.SubBsnes115, CoreNames.Faust, CoreNames.Bsnes ]),
([ VSystemID.Raw.TI83 ],
[ CoreNames.Emu83, CoreNames.TI83Hawk ]),
};

public static Dictionary<string, string> GenDefaultCorePreferences()
{
Dictionary<string, string> dict = new();
foreach (var (appliesTo, coreNames) in CorePickerUIData)
foreach(var (systemId, cores) in CoreInventory.Instance.AllCores)
{
var defaultCore = coreNames[0];
foreach (var sysID in appliesTo) dict[sysID] = defaultCore;
if (cores.Count > 1)
dict[systemId] = cores.Find(core => core.Priority == CorePriority.DefaultPreference)?.Name;
}

return dict;
}

Expand Down
14 changes: 8 additions & 6 deletions src/BizHawk.Client.EmuHawk/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,30 +75,32 @@ private void MainForm_Load(object sender, EventArgs e)
}
}

foreach (var (appliesTo, coreNames) in Config.CorePickerUIData)
foreach (var (systemIds, coreNames) in CoreInventory.Instance.SystemGroups
.Where(tuple => tuple.CoreNames.Count >= 2)
.OrderBy(tuple => tuple.SystemIds[0]))
{
var submenu = new ToolStripMenuItem { Text = string.Join(" | ", appliesTo) };
var submenu = new ToolStripMenuItem { Text = string.Join(" | ", systemIds) };
submenu.DropDownItems.AddRange(coreNames.Select(coreName => {
var entry = new ToolStripMenuItem { Text = coreName };
entry.Click += (_, _) =>
{
string currentCoreName = Emulator.Attributes().CoreName;
if (coreName != currentCoreName && coreNames.Contains(currentCoreName)) FlagNeedsReboot();
foreach (string system in appliesTo)
Config.PreferredCores[system] = coreName;
foreach (string systemId in systemIds)
Config.PreferredCores[systemId] = coreName;
};
return (ToolStripItem) entry;
}).ToArray());
submenu.DropDownOpened += (openedSender, _1) =>
{
_ = Config.PreferredCores.TryGetValue(appliesTo[0], out var preferred);
_ = Config.PreferredCores.TryGetValue(systemIds[0], out var preferred);
if (!coreNames.Contains(preferred))
{
// invalid --> default (doing this here rather than when reading config file to allow for hacked-in values, though I'm not sure if that could do anything at the moment --yoshi)
var defaultCore = coreNames[0];
Console.WriteLine($"setting preferred core for {submenu.Text} to {defaultCore} (was {preferred ?? "null"})");
preferred = defaultCore;
foreach (var sysID in appliesTo) Config.PreferredCores[sysID] = preferred;
foreach (var sysID in systemIds) Config.PreferredCores[sysID] = preferred;
}
foreach (ToolStripMenuItem entry in ((ToolStripMenuItem) openedSender).DropDownItems) entry.Checked = entry.Text == preferred;
};
Expand Down
2 changes: 1 addition & 1 deletion src/BizHawk.Emulation.Cores/Calculators/Emu83/Emu83.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ static Emu83()

private readonly TI83Disassembler _disassembler = new();

[CoreConstructor(VSystemID.Raw.TI83)]
[CoreConstructor(VSystemID.Raw.TI83, Priority = CorePriority.DefaultPreference)]
public Emu83(CoreLoadParameters<TI83CommonSettings, object> lp)
{
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal static class RomChecksums
public const string Tapper = "SHA1:E986E1818E747BEB9B33CE4DFF1CDC6B55BDB620";
}

[CoreConstructor(VSystemID.Raw.A26)]
[CoreConstructor(VSystemID.Raw.A26, Priority = CorePriority.DefaultPreference)]
public Atari2600(GameInfo game, byte[] rom, Atari2600.A2600Settings settings, Atari2600.A2600SyncSettings syncSettings)
{
var ser = new BasicServiceProvider(this);
Expand Down
8 changes: 4 additions & 4 deletions src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/HyperNyma.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ public static NymaSettingsInfo CachedSettingsInfo(CoreComm comm)
private readonly LibHyperNyma _hyperNyma;
private readonly bool _hasCds;

[CoreConstructor(VSystemID.Raw.PCE, Priority = CorePriority.Low)]
[CoreConstructor(VSystemID.Raw.SGX, Priority = CorePriority.Low)]
[CoreConstructor(VSystemID.Raw.PCECD, Priority = CorePriority.Low)]
[CoreConstructor(VSystemID.Raw.SGXCD, Priority = CorePriority.Low)]
[CoreConstructor(VSystemID.Raw.PCE)]
[CoreConstructor(VSystemID.Raw.SGX)]
[CoreConstructor(VSystemID.Raw.PCECD)]
[CoreConstructor(VSystemID.Raw.SGXCD)]
public HyperNyma(CoreLoadParameters<NymaSettings, NymaSyncSettings> lp)
: base(lp.Comm, VSystemID.Raw.PCE, "PC Engine Controller", lp.Settings, lp.SyncSettings)
{
Expand Down
8 changes: 4 additions & 4 deletions src/BizHawk.Emulation.Cores/Consoles/NEC/PCE/TurboNyma.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ public static NymaSettingsInfo CachedSettingsInfo(CoreComm comm)
private readonly LibTurboNyma _turboNyma;
private readonly bool _hasCds;

[CoreConstructor(VSystemID.Raw.PCE)]
[CoreConstructor(VSystemID.Raw.SGX)]
[CoreConstructor(VSystemID.Raw.PCECD)]
[CoreConstructor(VSystemID.Raw.SGXCD)]
[CoreConstructor(VSystemID.Raw.PCE, Priority = CorePriority.DefaultPreference)]
[CoreConstructor(VSystemID.Raw.SGX, Priority = CorePriority.DefaultPreference)]
[CoreConstructor(VSystemID.Raw.PCECD, Priority = CorePriority.DefaultPreference)]
[CoreConstructor(VSystemID.Raw.SGXCD, Priority = CorePriority.DefaultPreference)]
public TurboNyma(CoreLoadParameters<NymaSettings, NymaSyncSettings> lp)
: base(lp.Comm, VSystemID.Raw.PCE, "PC Engine Controller", lp.Settings, lp.SyncSettings)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
[ServiceNotApplicable(new[] { typeof(IDriveLight) })]
public partial class BsnesCore : IEmulator, IDebuggable, IVideoProvider, ISaveRam, IStatable, IInputPollable, IRegionable, ISettable<BsnesCore.SnesSettings, BsnesCore.SnesSyncSettings>, IBSNESForGfxDebugger, IBoardInfo
{
[CoreConstructor(VSystemID.Raw.Satellaview)]
[CoreConstructor(VSystemID.Raw.SGB)]
[CoreConstructor(VSystemID.Raw.Satellaview, Priority = CorePriority.DefaultPreference)]
[CoreConstructor(VSystemID.Raw.SGB, Priority = CorePriority.DefaultPreference)]
[CoreConstructor(VSystemID.Raw.SNES)]
public BsnesCore(CoreLoadParameters<SnesSettings, SnesSyncSettings> loadParameters) : this(loadParameters, false) { }
public BsnesCore(CoreLoadParameters<SnesSettings, SnesSyncSettings> loadParameters, bool subframe = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public partial class Gameboy : IInputPollable, IRomInfo, IGameboyCommon, ICycleT
/// <remarks>HACK disables BIOS requirement if the environment looks like a test runner...</remarks>
private static readonly bool TestromsBIOSDisableHack = Type.GetType("Microsoft.VisualStudio.TestTools.UnitTesting.Assert, Microsoft.VisualStudio.TestPlatform.TestFramework") is not null;

[CoreConstructor(VSystemID.Raw.GB)]
[CoreConstructor(VSystemID.Raw.GBC)]
[CoreConstructor(VSystemID.Raw.GB, Priority = CorePriority.DefaultPreference)]
[CoreConstructor(VSystemID.Raw.GBC, Priority = CorePriority.DefaultPreference)]
[CoreConstructor(VSystemID.Raw.SGB)]
public Gameboy(CoreComm comm, IGameInfo game, byte[] file, GambatteSettings settings, GambatteSyncSettings syncSettings, bool deterministic)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
[ServiceNotApplicable(new[] { typeof(IDriveLight) })]
public partial class GambatteLink : ILinkable, ILinkedGameBoyCommon, IRomInfo
{
[CoreConstructor(VSystemID.Raw.GBL)]
[CoreConstructor(VSystemID.Raw.GBL, Priority = CorePriority.DefaultPreference)]
public GambatteLink(CoreLoadParameters<GambatteLinkSettings, GambatteLinkSyncSettings> lp)
{
if (lp.Roms.Count < MIN_PLAYERS || lp.Roms.Count > MAX_PLAYERS)
Expand Down
2 changes: 1 addition & 1 deletion src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public partial class N64 : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPo
/// <param name="file">Rom that should be loaded</param>
/// <param name="rom">rom data with consistent endianness/order</param>
/// <param name="syncSettings">N64SyncSettings object</param>
[CoreConstructor(VSystemID.Raw.N64)]
[CoreConstructor(VSystemID.Raw.N64, Priority = CorePriority.DefaultPreference)]
public N64(GameInfo game, byte[] file, byte[] rom, N64Settings settings, N64SyncSettings syncSettings)
{
if (OSTailoredCode.IsUnixHost) throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ static QuickNES()
QN = BizInvoker.GetInvoker<LibQuickNES>(resolver, CallingConventionAdapters.Native);
}

[CoreConstructor(VSystemID.Raw.NES)]
[CoreConstructor(VSystemID.Raw.NES, Priority = CorePriority.DefaultPreference)]
public QuickNES(byte[] file, QuickNESSettings settings, QuickNESSyncSettings syncSettings)
{
ServiceProvider = new BasicServiceProvider(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class Snes9x : WaterboxCore,
{
private readonly LibSnes9x _core;

[CoreConstructor(VSystemID.Raw.SNES)]
[CoreConstructor(VSystemID.Raw.SNES, Priority = CorePriority.DefaultPreference)]
public Snes9x(CoreLoadParameters<Settings, SyncSettings> loadParameters)
:base(loadParameters.Comm, new Configuration
{
Expand Down
8 changes: 4 additions & 4 deletions src/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ public sealed partial class PCEngine : IEmulator, ISaveRam, IInputPollable, IVid

int IVideoLogicalOffsets.ScreenY => Settings.TopLine;

[CoreConstructor(VSystemID.Raw.PCE, Priority = CorePriority.Low)]
[CoreConstructor(VSystemID.Raw.SGX, Priority = CorePriority.Low)]
[CoreConstructor(VSystemID.Raw.PCECD, Priority = CorePriority.Low)]
[CoreConstructor(VSystemID.Raw.SGXCD, Priority = CorePriority.Low)]
[CoreConstructor(VSystemID.Raw.PCE)]
[CoreConstructor(VSystemID.Raw.SGX)]
[CoreConstructor(VSystemID.Raw.PCECD)]
[CoreConstructor(VSystemID.Raw.SGXCD)]
public PCEngine(CoreLoadParameters<PCESettings, PCESyncSettings> lp)
{
if (lp.Discs.Count == 1 && lp.Roms.Count == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class PicoDrive : WaterboxCore, IDriveLight, IRegionable, ISettable<objec
private readonly DiscSectorReader _cdReader;
private readonly bool _isPal;

[CoreConstructor(VSystemID.Raw.GEN, Priority = CorePriority.Low)]
[CoreConstructor(VSystemID.Raw.GEN)]
[CoreConstructor(VSystemID.Raw.Sega32X)]
public PicoDrive(CoreComm comm, GameInfo game, byte[] rom, bool deterministic, SyncSettings syncSettings)
: this(comm, game, rom, null, deterministic, syncSettings)
Expand Down
8 changes: 4 additions & 4 deletions src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
public partial class GPGX : IEmulator, IVideoProvider, ISaveRam, IStatable, IRegionable,
IInputPollable, IDebuggable, IDriveLight, ICodeDataLogger, IDisassemblable
{
[CoreConstructor(VSystemID.Raw.GEN)]
[CoreConstructor(VSystemID.Raw.SMS)]
[CoreConstructor(VSystemID.Raw.GG)]
[CoreConstructor(VSystemID.Raw.SG)]
[CoreConstructor(VSystemID.Raw.GEN, Priority = CorePriority.DefaultPreference)]
[CoreConstructor(VSystemID.Raw.SMS, Priority = CorePriority.DefaultPreference)]
[CoreConstructor(VSystemID.Raw.GG, Priority = CorePriority.DefaultPreference)]
[CoreConstructor(VSystemID.Raw.SG, Priority = CorePriority.DefaultPreference)]
public GPGX(CoreLoadParameters<GPGXSettings, GPGXSyncSettings> lp)
{
LoadCallback = LoadArchive;
Expand Down
2 changes: 1 addition & 1 deletion src/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Nymashock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public static NymaSettingsInfo CachedSettingsInfo(CoreComm comm)
return _cachedSettingsInfo;
}

[CoreConstructor(VSystemID.Raw.PSX)]
[CoreConstructor(VSystemID.Raw.PSX, Priority = CorePriority.DefaultPreference)]
public Nymashock(CoreLoadParameters<NymaSettings, NymaSyncSettings> lp)
: base(lp.Comm, VSystemID.Raw.PSX, "PSX Front Panel", lp.Settings, lp.SyncSettings)
{
Expand Down
51 changes: 35 additions & 16 deletions src/BizHawk.Emulation.Cores/CoreInventory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Reflection;
using System.Runtime.ExceptionServices;

using BizHawk.Common;
using BizHawk.Common.CollectionExtensions;
using BizHawk.Emulation.Common;

Expand All @@ -14,10 +15,14 @@ namespace BizHawk.Emulation.Cores
public class CoreInventory
{
private readonly Dictionary<string, List<Core>> _systems = new Dictionary<string, List<Core>>();
private readonly List<(List<string>, List<string>)> _systemGroups = [ ];

/// <summary>keys are system IDs; values are core/ctor info for all that system's cores</summary>
public IReadOnlyDictionary<string, List<Core>> AllCores => _systems;

// list of system ids groups; each system id in the group shares the same core choices
public IReadOnlyList<(List<string> SystemIds, List<string> CoreNames)> SystemGroups => _systemGroups;

public readonly IReadOnlyCollection<Core> SystemsFlat;

public class Core
Expand Down Expand Up @@ -157,7 +162,7 @@ public IEnumerable<Core> GetCores(string system)
/// <summary>
/// create a core inventory, collecting all IEmulators from some assemblies
/// </summary>
public CoreInventory(IEnumerable<IEnumerable<Type>> assys)
public CoreInventory(IEnumerable<IEnumerable<Type>> assemblies)
{
var systemsFlat = new Dictionary<Type, Core>();
void ProcessConstructor(Type type, CoreConstructorAttribute consAttr, CoreAttribute coreAttr, ConstructorInfo cons)
Expand All @@ -166,27 +171,41 @@ void ProcessConstructor(Type type, CoreConstructorAttribute consAttr, CoreAttrib
_systems.GetValueOrPutNew(consAttr.System).Add(core);
systemsFlat[type] = core;
}
foreach (var assy in assys)
foreach (var type in assemblies.SelectMany(assembly => assembly).OrderBy(type => type.AssemblyQualifiedName))
{
foreach (var typ in assy)
if (!type.IsAbstract && type.GetInterfaces().Contains(typeof(IEmulator)))
{
if (!typ.IsAbstract && typ.GetInterfaces().Contains(typeof(IEmulator)))
var coreAttr = type.GetCustomAttributes(typeof(CoreAttribute), false);
if (coreAttr.Length != 1)
throw new InvalidOperationException($"{nameof(IEmulator)} {type} without {nameof(CoreAttribute)}s!");
var cons = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
.Where(c => c.GetCustomAttributes(typeof(CoreConstructorAttribute), false).Length > 0);
foreach(var con in cons)
{
var coreAttr = typ.GetCustomAttributes(typeof(CoreAttribute), false);
if (coreAttr.Length != 1)
throw new InvalidOperationException($"{nameof(IEmulator)} {typ} without {nameof(CoreAttribute)}s!");
var cons = typ.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
.Where(c => c.GetCustomAttributes(typeof(CoreConstructorAttribute), false).Length > 0);
foreach(var con in cons)
foreach (var consAttr in con.GetCustomAttributes(typeof(CoreConstructorAttribute), false).Cast<CoreConstructorAttribute>())
{
foreach (var consAttr in con.GetCustomAttributes(typeof(CoreConstructorAttribute), false).Cast<CoreConstructorAttribute>())
{
ProcessConstructor(typ, consAttr, (CoreAttribute)coreAttr[0], con);
}
ProcessConstructor(type, consAttr, (CoreAttribute)coreAttr[0], con);
}
}
}
}
foreach (var (systemId, cores) in _systems)
{
var coreNames = cores.Select(core => core.Name).ToList();
bool found = false;
foreach (var (systemIds, existingCores) in _systemGroups)
{
if (existingCores.SequenceEqual(coreNames))
{
systemIds.Add(systemId);
found = true;
break;
}
}

if (!found)
_systemGroups.Add(([ systemId ], coreNames));
}
SystemsFlat = systemsFlat.Values;
}

Expand All @@ -205,9 +224,9 @@ public enum CorePriority
UserPreference = -200,

/// <summary>
/// A very good core that should be preferred over normal cores. Don't use this?
/// The default core for a system when no other preferences exist. Must be set once per system
/// </summary>
High = -100,
DefaultPreference = -100,

/// <summary>
/// Most cores should use this
Expand Down
Loading