diff --git a/src/BizHawk.Client.Common/IMainFormForTools.cs b/src/BizHawk.Client.Common/IMainFormForTools.cs index 453e772b5e..1d4ca05ff9 100644 --- a/src/BizHawk.Client.Common/IMainFormForTools.cs +++ b/src/BizHawk.Client.Common/IMainFormForTools.cs @@ -109,5 +109,7 @@ namespace BizHawk.Client.Common /// only referenced from TAStudio void UpdateWindowTitle(); + + public EmuClientApi EmuClient { get; set; } } } diff --git a/src/BizHawk.Client.EmuHawk/ToolAttribute.cs b/src/BizHawk.Client.Common/ToolAttribute.cs similarity index 92% rename from src/BizHawk.Client.EmuHawk/ToolAttribute.cs rename to src/BizHawk.Client.Common/ToolAttribute.cs index 1f22ffac89..7f9e608f1b 100644 --- a/src/BizHawk.Client.EmuHawk/ToolAttribute.cs +++ b/src/BizHawk.Client.Common/ToolAttribute.cs @@ -1,25 +1,25 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace BizHawk.Client.EmuHawk -{ - [AttributeUsage(AttributeTargets.Class)] - public sealed class ToolAttribute : Attribute - { - public ToolAttribute(bool released, string[] supportedSystems, string[] unsupportedCores) - { - Released = released; - SupportedSystems = supportedSystems ?? Enumerable.Empty(); - UnsupportedCores = unsupportedCores ?? Enumerable.Empty(); - } - - public ToolAttribute(bool released, string[] supportedSystems) : this(released, supportedSystems, null) {} - - public bool Released { get; } - - public IEnumerable SupportedSystems { get; } - - public IEnumerable UnsupportedCores { get; } - } -} +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BizHawk.Client.Common +{ + [AttributeUsage(AttributeTargets.Class)] + public sealed class ToolAttribute : Attribute + { + public ToolAttribute(bool released, string[] supportedSystems, string[] unsupportedCores) + { + Released = released; + SupportedSystems = supportedSystems ?? Enumerable.Empty(); + UnsupportedCores = unsupportedCores ?? Enumerable.Empty(); + } + + public ToolAttribute(bool released, string[] supportedSystems) : this(released, supportedSystems, null) {} + + public bool Released { get; } + + public IEnumerable SupportedSystems { get; } + + public IEnumerable UnsupportedCores { get; } + } +} diff --git a/src/BizHawk.Client.Common/tools/ToolManagerBase.cs b/src/BizHawk.Client.Common/tools/ToolManagerBase.cs new file mode 100644 index 0000000000..634f2a1d46 --- /dev/null +++ b/src/BizHawk.Client.Common/tools/ToolManagerBase.cs @@ -0,0 +1,600 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.ComponentModel; + +using BizHawk.Common; +using BizHawk.Common.CollectionExtensions; +using BizHawk.Common.ReflectionExtensions; +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.Common +{ + public abstract class ToolManagerBase : IToolManager + { + protected readonly IMainFormForTools _mainFormTools; + private readonly IMainFormForApi _mainFormApi; + protected Config _config; + protected readonly DisplayManagerBase _displayManager; + private readonly ExternalToolManager _extToolManager; + protected readonly InputManager _inputManager; + private IExternalApiProvider _apiProvider; + protected IEmulator _emulator; + protected readonly IMovieSession _movieSession; + protected IGameInfo _game; + + // TODO: merge ToolHelper code where logical + // For instance, add an IToolForm property called UsesCheats, so that a UpdateCheatRelatedTools() method can update all tools of this type + // Also a UsesRam, and similar method + private readonly List _tools = new List(); + + private IExternalApiProvider ApiProvider + { + get => _apiProvider; + set => _mainFormTools.EmuClient = (EmuClientApi)(_apiProvider = value).GetApi(); + } + + /// + /// Initializes a new instance of the class. + /// + public ToolManagerBase( + IMainFormForTools owner, + IMainFormForApi mainFormApi, + Config config, + DisplayManagerBase displayManager, + ExternalToolManager extToolManager, + InputManager inputManager, + IEmulator emulator, + IMovieSession movieSession, + IGameInfo game) + { + _mainFormTools = owner; + _mainFormApi = mainFormApi; + _config = config; + _displayManager = displayManager; + _extToolManager = extToolManager; + _inputManager = inputManager; + _emulator = emulator; + _movieSession = movieSession; + _game = game; + ApiProvider = ApiManager.Restart(_emulator.ServiceProvider, _mainFormApi, _displayManager, _inputManager, _movieSession, this, _config, _emulator, _game); + } + + /// + /// Loads the tool dialog T (T must implements ) , if it does not exist it will be created, if it is already open, it will be focused + /// This method should be used only if you can't use the generic one + /// + /// Type of tool you want to load + /// Define if the tool form has to get the focus or not (Default is true) + /// An instantiated + /// Raised if can't cast into IToolForm + public IToolForm Load(Type toolType, bool focus = true) + { + if (!typeof(IToolForm).IsAssignableFrom(toolType)) + { + throw new ArgumentException(message: $"Type {toolType.Name} does not implement {nameof(IToolForm)}.", paramName: nameof(toolType)); + } + var mi = typeof(ToolManagerBase).GetMethod(nameof(Load), new[] { typeof(bool), typeof(string) })!.MakeGenericMethod(toolType); + return (IToolForm)mi.Invoke(this, new object[] { focus, "" }); + } + + // If the form inherits ToolFormBase, it will set base properties such as Tools, Config, etc + protected abstract void SetBaseProperties(IToolForm form); + + + protected abstract void SetFormParent(IToolForm form); + + /// + /// Loads the tool dialog T (T must implement ) , if it does not exist it will be created, if it is already open, it will be focused + /// + /// Define if the tool form has to get the focus or not (Default is true) + /// Path to the .dll of the external tool + /// Type of tool you want to load + /// An instantiated + public T Load(bool focus = true, string toolPath = "") + where T : class, IToolForm + { + if (!IsAvailable()) return null; + + var existingTool = _tools.OfType().FirstOrDefault(); + if (existingTool != null) + { + if (existingTool.IsLoaded) + { + if (focus) + { + existingTool.Show(); + existingTool.Focus(); + } + + return existingTool; + } + + _tools.Remove(existingTool); + } + + if (CreateInstance(toolPath) is not T newTool) return null; + + SetFormParent(newTool); + if (!ServiceInjector.UpdateServices(_emulator.ServiceProvider, newTool)) return null; //TODO pass `true` for `mayCache` when from EmuHawk assembly + SetBaseProperties(newTool); + var toolTypeName = typeof(T).FullName!; + // auto settings + if (newTool is IToolFormAutoConfig autoConfigTool) + { + AttachSettingHooks(autoConfigTool, _config.CommonToolSettings.GetValueOrPutNew(toolTypeName)); + } + // custom settings + if (HasCustomConfig(newTool)) + { + InstallCustomConfig(newTool, _config.CustomToolSettings.GetValueOrPutNew(toolTypeName)); + } + + newTool.Restart(); + newTool.Show(); + return newTool; + } + + /// Loads the external tool's entry form. + public IExternalToolForm LoadExternalToolForm(string toolPath, string customFormTypeName, bool focus = true, bool skipExtToolWarning = false) + { + var existingTool = _tools.OfType().FirstOrDefault(t => t.GetType().Assembly.Location == toolPath); + if (existingTool != null) + { + if (existingTool.IsActive) + { + if (focus) + { + existingTool.Show(); + existingTool.Focus(); + } + return existingTool; + } + + _tools.Remove(existingTool); + } + + var newTool = (IExternalToolForm)CreateInstance(typeof(IExternalToolForm), toolPath, customFormTypeName, skipExtToolWarning: skipExtToolWarning); + if (newTool == null) return null; + SetFormParent(newTool); + if (!(ServiceInjector.UpdateServices(_emulator.ServiceProvider, newTool) && ApiInjector.UpdateApis(ApiProvider, newTool))) return null; + SetBaseProperties(newTool); + // auto settings + if (newTool is IToolFormAutoConfig autoConfigTool) + { + AttachSettingHooks(autoConfigTool, _config.CommonToolSettings.GetValueOrPutNew(customFormTypeName)); + } + // custom settings + if (HasCustomConfig(newTool)) + { + InstallCustomConfig(newTool, _config.CustomToolSettings.GetValueOrPutNew(customFormTypeName)); + } + + newTool.Restart(); + newTool.Show(); + return newTool; + } + + public void AutoLoad() + { + var genericSettings = _config.CommonToolSettings + .Where(kvp => kvp.Value.AutoLoad) + .Select(kvp => kvp.Key); + + var customSettings = _config.CustomToolSettings + .Where(list => list.Value.Any(kvp => kvp.Value is ToolDialogSettings settings && settings.AutoLoad)) + .Select(kvp => kvp.Key); + + var typeNames = genericSettings.Concat(customSettings); + + foreach (var typename in typeNames) + { + // this type resolution might not be sufficient. more investigation is needed + Type t = Type.GetType(typename); + if (t == null) + { + Console.WriteLine("BENIGN: Couldn't find type {0}", typename); + } + else + { + if (!IsLoaded(t)) + { + Load(t, false); + } + } + } + } + + protected abstract void AttachSettingHooks(IToolFormAutoConfig tool, ToolDialogSettings settings); + + private static bool HasCustomConfig(IToolForm tool) + { + return tool.GetType().GetPropertiesWithAttrib(typeof(ConfigPersistAttribute)).Any(); + } + + protected abstract void SetFormClosingEvent(IToolForm form, Action action); + + private void InstallCustomConfig(IToolForm tool, Dictionary data) + { + Type type = tool.GetType(); + var props = type.GetPropertiesWithAttrib(typeof(ConfigPersistAttribute)).ToList(); + if (props.Count == 0) + { + return; + } + + foreach (var prop in props) + { + if (data.TryGetValue(prop.Name, out var val)) + { + if (val is string str && prop.PropertyType != typeof(string)) + { + // if a type has a TypeConverter, and that converter can convert to string, + // that will be used in place of object markup by JSON.NET + + // but that doesn't work with $type metadata, and JSON.NET fails to fall + // back on regular object serialization when needed. so try to undo a TypeConverter + // operation here + var converter = TypeDescriptor.GetConverter(prop.PropertyType); + val = converter.ConvertFromString(null, CultureInfo.InvariantCulture, str); + } + else if (val is not bool && prop.PropertyType.IsPrimitive) + { + // numeric constants are similarly hosed + val = Convert.ChangeType(val, prop.PropertyType, CultureInfo.InvariantCulture); + } + + prop.SetValue(tool, val, null); + } + } + + SetFormClosingEvent(tool, () => SaveCustomConfig(tool, data, props)); + } + + private static void SaveCustomConfig(IToolForm tool, Dictionary data, List props) + { + data.Clear(); + foreach (var prop in props) + { + data.Add(prop.Name, prop.GetValue(tool, BindingFlags.GetProperty, Type.DefaultBinder, null, CultureInfo.InvariantCulture)); + } + } + + /// + /// Determines whether a given IToolForm is already loaded + /// + /// Type of tool to check + /// yo why do we have 4 versions of this, each with slightly different behaviour in edge cases --yoshi + public bool IsLoaded() where T : IToolForm + => _tools.OfType().FirstOrDefault()?.IsActive is true; + + public bool IsLoaded(Type toolType) + => _tools.Find(t => t.GetType() == toolType)?.IsActive is true; + + public abstract bool IsOnScreen(Point topLeft); + + /// + /// Returns true if an instance of T exists + /// + /// Type of tool to check + public bool Has() where T : IToolForm + => _tools.Exists(static t => t is T && t.IsActive); + + /// iff a tool of the given is active + public bool Has(Type toolType) + => typeof(IToolForm).IsAssignableFrom(toolType) + && _tools.Exists(t => toolType.IsInstanceOfType(t) && t.IsActive); + + /// + /// Gets the instance of T, or creates and returns a new instance + /// + /// Type of tool to get + public IToolForm Get() where T : class, IToolForm + { + return Load(false); + } + + /// + /// returns the instance of , regardless of whether it's loaded,
+ /// but doesn't create and load a new instance if it's not found + ///
+ /// + /// does not check is a class implementing ;
+ /// you may pass any class or interface + ///
+ public IToolForm/*?*/ LazyGet(Type toolType) + => _tools.Find(t => toolType.IsAssignableFrom(t.GetType())); + + private static PropertyInfo/*?*/ _PInfo_FormBase_WindowTitleStatic = null; + + protected abstract bool CaptureIconAndName(object tool, Type toolType, ref Image/*?*/ icon, ref string/*?*/ name); + + private void CaptureIconAndName(object tool, Type toolType) + { + Image/*?*/ icon = null; + string/*?*/ name = null; + CaptureIconAndName(tool, toolType, ref icon, ref name); + } + + public abstract (Image/*?*/ Icon, string Name) GetIconAndNameFor(Type toolType); + + public abstract IEnumerable AvailableTools { get; } + + /// + /// Calls UpdateValues() on an instance of T, if it exists + /// + /// Type of tool to update + public void UpdateValues() where T : IToolForm + { + var tool = _tools.OfType().FirstOrDefault(); + if (tool?.IsActive is true) + { + tool.UpdateValues(ToolFormUpdateType.General); + } + } + + protected abstract void MaybeClearCheats(); + + public void Restart(Config config, IEmulator emulator, IGameInfo game) + { + _config = config; + _emulator = emulator; + _game = game; + ApiProvider = ApiManager.Restart(_emulator.ServiceProvider, _mainFormApi, _displayManager, _inputManager, _movieSession, this, _config, _emulator, _game); + + MaybeClearCheats(); + + var unavailable = new List(); + + foreach (var tool in _tools) + { + SetBaseProperties(tool); + if (ServiceInjector.UpdateServices(_emulator.ServiceProvider, tool) + && (tool is not IExternalToolForm || ApiInjector.UpdateApis(ApiProvider, tool))) + { + if (tool.IsActive) tool.Restart(); + } + else + { + unavailable.Add(tool); + if (tool is IExternalToolForm) ApiInjector.ClearApis(tool); + } + } + + foreach (var tool in unavailable) + { + tool.Close(); + _tools.Remove(tool); + } + } + + /// + /// Calls Restart() on an instance of T, if it exists + /// + /// Type of tool to restart + public void Restart() where T : IToolForm + => _tools.OfType().FirstOrDefault()?.Restart(); + + /// + /// Runs AskSave on every tool dialog, false is returned if any tool returns false + /// + public bool AskSave() + { + if (_config.SuppressAskSave) // User has elected to not be nagged + { + return true; + } + + return _tools + .Select(tool => tool.AskSaveChanges()) + .All(result => result); + } + + /// + /// If T exists, this call will close the tool, and remove it from memory + /// + /// Type of tool to close + public void Close() where T : IToolForm + { + var tool = _tools.OfType().FirstOrDefault(); + if (tool != null) + { + tool.Close(); + _tools.Remove(tool); + } + } + + public void Close(Type toolType) + { + var tool = _tools.Find(toolType.IsInstanceOfType); + if (tool != null) + { + tool.Close(); + _tools.Remove(tool); + } + } + + public void Close() + { + _tools.ForEach(t => t.Close()); + _tools.Clear(); + } + + /// + /// Create a new instance of an IToolForm and return it + /// + /// Type of tool you want to create + /// Path .dll for an external tool + /// New instance of an IToolForm + private IToolForm CreateInstance(string dllPath) + where T : IToolForm + { + return CreateInstance(typeof(T), dllPath); + } + + protected abstract IExternalToolForm CreateInstanceFrom(string dllPath, string toolTypeName); + + /// + /// Create a new instance of an IToolForm and return it + /// + /// Type of tool you want to create + /// Path dll for an external tool + /// For external tools, of the entry form's type ( will be ) + /// New instance of an IToolForm + private IToolForm CreateInstance(Type toolType, string dllPath, string toolTypeName = null, bool skipExtToolWarning = false) + { + IToolForm tool; + + // Specific case for custom tools + // TODO: Use AppDomain in order to be able to unload the assembly + // Hard stuff as we need a proxy object that inherit from MarshalByRefObject. + if (toolType == typeof(IExternalToolForm)) + { + if (!skipExtToolWarning) + { + if (!_mainFormTools.ShowMessageBox2( + "Are you sure want to load this external tool?\r\nAccept ONLY if you trust the source and if you know what you're doing. In any other case, choose no.", + "Confirm loading", + EMsgBoxIcon.Question)) + { + return null; + } + } + + try + { + //tool = Activator.CreateInstanceFrom(dllPath, toolTypeName ?? "BizHawk.Client.EmuHawk.CustomMainForm").Unwrap() as IExternalToolForm; + tool = CreateInstanceFrom(dllPath, toolTypeName); + if (tool == null) + { + _mainFormTools.ShowMessageBox($"It seems that the object CustomMainForm does not implement {nameof(IExternalToolForm)}. Please review the code.", "No, no, no. Wrong Way !", EMsgBoxIcon.Warning); + return null; + } + } + catch (MissingMethodException) + { + _mainFormTools.ShowMessageBox("It seems that the object CustomMainForm does not have a public default constructor. Please review the code.", "No, no, no. Wrong Way !", EMsgBoxIcon.Warning); + return null; + } + catch (TypeLoadException) + { + _mainFormTools.ShowMessageBox("It seems that the object CustomMainForm does not exists. Please review the code.", "No, no, no. Wrong Way !", EMsgBoxIcon.Warning); + return null; + } + } + else + { + tool = (IToolForm)Activator.CreateInstance(toolType); + } + CaptureIconAndName(tool, toolType); + // Add to our list of tools + _tools.Add(tool); + return tool; + } + + public void UpdateToolsBefore() + { + foreach (var tool in _tools) + { + if (tool.IsActive) + { + tool.UpdateValues(ToolFormUpdateType.PreFrame); + } + } + } + + public void UpdateToolsAfter() + { + foreach (var tool in _tools) + { + if (tool.IsActive) + { + tool.UpdateValues(ToolFormUpdateType.PostFrame); + } + } + } + + public void FastUpdateBefore() + { + foreach (var tool in _tools) + { + if (tool.IsActive) + { + tool.UpdateValues(ToolFormUpdateType.FastPreFrame); + } + } + } + + public void FastUpdateAfter() + { + foreach (var tool in _tools) + { + if (tool.IsActive) + { + tool.UpdateValues(ToolFormUpdateType.FastPostFrame); + } + } + } + + protected abstract IList PossibleToolTypeNames { get; } + + public bool IsAvailable(Type tool) + { + if (!ServiceInjector.IsAvailable(_emulator.ServiceProvider, tool)) return false; + if (typeof(IExternalToolForm).IsAssignableFrom(tool) && !ApiInjector.IsAvailable(ApiProvider, tool)) return false; + if (!PossibleToolTypeNames.Contains(tool.AssemblyQualifiedName) && !_extToolManager.PossibleExtToolTypeNames.Contains(tool.AssemblyQualifiedName)) return false; // not a tool + + ToolAttribute attr = tool.GetCustomAttributes(false).OfType().SingleOrDefault(); + if (attr == null) + { + return true; // no ToolAttribute on given type -> assumed all supported + } + + return !attr.UnsupportedCores.Contains(_emulator.Attributes().CoreName) // not unsupported + && (!attr.SupportedSystems.Any() || attr.SupportedSystems.Contains(_emulator.SystemId)); // supported (no supported list -> assumed all supported) + } + + public bool IsAvailable() => IsAvailable(typeof(T)); + + // Note: Referencing these properties creates an instance of the tool and persists it. They should be referenced by type if this is not desired + + protected T GetTool() where T : class, IToolForm, new() + { + T tool = _tools.OfType().FirstOrDefault(); + if (tool != null) + { + if (tool.IsActive) + { + return tool; + } + + _tools.Remove(tool); + } + tool = new T(); + CaptureIconAndName(tool, typeof(T)); + _tools.Add(tool); + return tool; + } + + public abstract void LoadRamWatch(bool loadDialog); + + public string GenerateDefaultCheatFilename() + { + var path = _config.PathEntries.CheatsAbsolutePath(_game.System); + + var f = new FileInfo(path); + if (f.Directory != null && f.Directory.Exists == false) + { + f.Directory.Create(); + } + + return Path.Combine(path, $"{_game.FilesystemSafeName()}.cht"); + } + + public abstract void UpdateCheatRelatedTools(object sender, CheatCollection.CheatListEventArgs e); + } +} diff --git a/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs b/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs index 980a5b81b2..7b36715bad 100644 --- a/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs +++ b/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Drawing; using System.Globalization; -using System.IO; using System.Linq; using System.Reflection; using System.ComponentModel; @@ -10,36 +9,21 @@ using System.Windows.Forms; using BizHawk.Client.Common; using BizHawk.Common; -using BizHawk.Common.CollectionExtensions; using BizHawk.Common.ReflectionExtensions; using BizHawk.Emulation.Common; using BizHawk.WinForms.Controls; namespace BizHawk.Client.EmuHawk { - public class ToolManager : IToolManager + public class ToolManager : ToolManagerBase, IToolManager { - private readonly MainForm _owner; - private Config _config; - private readonly DisplayManagerBase _displayManager; - private readonly ExternalToolManager _extToolManager; - private readonly InputManager _inputManager; - private IExternalApiProvider _apiProvider; - private IEmulator _emulator; - private readonly IMovieSession _movieSession; - private IGameInfo _game; + private readonly MainForm _ownerForm; // TODO: merge ToolHelper code where logical // For instance, add an IToolForm property called UsesCheats, so that a UpdateCheatRelatedTools() method can update all tools of this type // Also a UsesRam, and similar method private readonly List _tools = new List(); - private IExternalApiProvider ApiProvider - { - get => _apiProvider; - set => _owner.EmuClient = (EmuClientApi) (_apiProvider = value).GetApi(); - } - /// /// Initializes a new instance of the class. /// @@ -51,168 +35,19 @@ namespace BizHawk.Client.EmuHawk InputManager inputManager, IEmulator emulator, IMovieSession movieSession, - IGameInfo game) + IGameInfo game) : base(owner, owner, config, displayManager, extToolManager, inputManager, emulator, movieSession, game) { - - - _owner = owner; - _config = config; - _displayManager = displayManager; - _extToolManager = extToolManager; - _inputManager = inputManager; - _emulator = emulator; - _movieSession = movieSession; - _game = game; - ApiProvider = ApiManager.Restart(_emulator.ServiceProvider, _owner, _displayManager, _inputManager, _movieSession, this, _config, _emulator, _game); - } - - /// - /// Loads the tool dialog T (T must implements ) , if it does not exist it will be created, if it is already open, it will be focused - /// This method should be used only if you can't use the generic one - /// - /// Type of tool you want to load - /// Define if the tool form has to get the focus or not (Default is true) - /// An instantiated - /// Raised if can't cast into IToolForm - public IToolForm Load(Type toolType, bool focus = true) - { - if (!typeof(IToolForm).IsAssignableFrom(toolType)) - { - throw new ArgumentException(message: $"Type {toolType.Name} does not implement {nameof(IToolForm)}.", paramName: nameof(toolType)); - } - var mi = typeof(ToolManager).GetMethod(nameof(Load), new[] { typeof(bool), typeof(string) })!.MakeGenericMethod(toolType); - return (IToolForm) mi.Invoke(this, new object[] { focus, "" }); + _ownerForm = owner; } // If the form inherits ToolFormBase, it will set base properties such as Tools, Config, etc - private void SetBaseProperties(IToolForm form) + protected override void SetBaseProperties(IToolForm form) { if (form is not FormBase f) return; f.Config = _config; if (form is not ToolFormBase tool) return; - tool.SetToolFormBaseProps(_displayManager, _inputManager, _owner, _movieSession, this, _game); - } - - /// - /// Loads the tool dialog T (T must implement ) , if it does not exist it will be created, if it is already open, it will be focused - /// - /// Define if the tool form has to get the focus or not (Default is true) - /// Path to the .dll of the external tool - /// Type of tool you want to load - /// An instantiated - public T Load(bool focus = true, string toolPath = "") - where T : class, IToolForm - { - if (!IsAvailable()) return null; - - var existingTool = _tools.OfType().FirstOrDefault(); - if (existingTool != null) - { - if (existingTool.IsLoaded) - { - if (focus) - { - existingTool.Show(); - existingTool.Focus(); - } - - return existingTool; - } - - _tools.Remove(existingTool); - } - - if (CreateInstance(toolPath) is not T newTool) return null; - - if (newTool is Form form) form.Owner = _owner; - if (!ServiceInjector.UpdateServices(_emulator.ServiceProvider, newTool)) return null; //TODO pass `true` for `mayCache` when from EmuHawk assembly - SetBaseProperties(newTool); - var toolTypeName = typeof(T).FullName!; - // auto settings - if (newTool is IToolFormAutoConfig autoConfigTool) - { - AttachSettingHooks(autoConfigTool, _config.CommonToolSettings.GetValueOrPutNew(toolTypeName)); - } - // custom settings - if (HasCustomConfig(newTool)) - { - InstallCustomConfig(newTool, _config.CustomToolSettings.GetValueOrPutNew(toolTypeName)); - } - - newTool.Restart(); - newTool.Show(); - return newTool; - } - - /// Loads the external tool's entry form. - public IExternalToolForm LoadExternalToolForm(string toolPath, string customFormTypeName, bool focus = true, bool skipExtToolWarning = false) - { - var existingTool = _tools.OfType().FirstOrDefault(t => t.GetType().Assembly.Location == toolPath); - if (existingTool != null) - { - if (existingTool.IsActive) - { - if (focus) - { - existingTool.Show(); - existingTool.Focus(); - } - return existingTool; - } - - _tools.Remove(existingTool); - } - - var newTool = (IExternalToolForm) CreateInstance(typeof(IExternalToolForm), toolPath, customFormTypeName, skipExtToolWarning: skipExtToolWarning); - if (newTool == null) return null; - if (newTool is Form form) form.Owner = _owner; - if (!(ServiceInjector.UpdateServices(_emulator.ServiceProvider, newTool) && ApiInjector.UpdateApis(ApiProvider, newTool))) return null; - SetBaseProperties(newTool); - // auto settings - if (newTool is IToolFormAutoConfig autoConfigTool) - { - AttachSettingHooks(autoConfigTool, _config.CommonToolSettings.GetValueOrPutNew(customFormTypeName)); - } - // custom settings - if (HasCustomConfig(newTool)) - { - InstallCustomConfig(newTool, _config.CustomToolSettings.GetValueOrPutNew(customFormTypeName)); - } - - newTool.Restart(); - newTool.Show(); - return newTool; - } - - public void AutoLoad() - { - var genericSettings = _config.CommonToolSettings - .Where(kvp => kvp.Value.AutoLoad) - .Select(kvp => kvp.Key); - - var customSettings = _config.CustomToolSettings - .Where(list => list.Value.Any(kvp => kvp.Value is ToolDialogSettings settings && settings.AutoLoad)) - .Select(kvp => kvp.Key); - - var typeNames = genericSettings.Concat(customSettings); - - foreach (var typename in typeNames) - { - // this type resolution might not be sufficient. more investigation is needed - Type t = Type.GetType(typename); - if (t == null) - { - Console.WriteLine("BENIGN: Couldn't find type {0}", typename); - } - else - { - if (!IsLoaded(t)) - { - Load(t, false); - } - } - } + tool.SetToolFormBaseProps(_displayManager, _inputManager, _mainFormTools, _movieSession, this, _game); } private void RefreshSettings(Form form, ToolStripItemCollection menu, ToolDialogSettings settings, int idx) @@ -234,7 +69,7 @@ namespace BizHawk.Client.EmuHawk ((ToolStripMenuItem)menu[idx + 3]).Checked = settings.AutoLoad; // do we need to do this OnShown() as well? - form.Owner = settings.FloatingWindow ? null : _owner; + form.Owner = settings.FloatingWindow ? null : _ownerForm; } private void AddCloseButton(ToolStripMenuItem subMenu, Form form) @@ -255,7 +90,7 @@ namespace BizHawk.Client.EmuHawk subMenu.DropDownItems.Add(closeMenuItem); } - private void AttachSettingHooks(IToolFormAutoConfig tool, ToolDialogSettings settings) + protected override void AttachSettingHooks(IToolFormAutoConfig tool, ToolDialogSettings settings) { var form = (Form)tool; ToolStripItemCollection dest = null; @@ -356,7 +191,7 @@ namespace BizHawk.Client.EmuHawk bool val = !((ToolStripMenuItem)o).Checked; settings.FloatingWindow = val; ((ToolStripMenuItem)o).Checked = val; - form.Owner = val ? null : _owner; + form.Owner = val ? null : _ownerForm; }; dest[idx + 3].Click += (o, e) => { @@ -426,55 +261,12 @@ namespace BizHawk.Client.EmuHawk } } - /// - /// Determines whether a given IToolForm is already loaded - /// - /// Type of tool to check - /// yo why do we have 4 versions of this, each with slightly different behaviour in edge cases --yoshi - public bool IsLoaded() where T : IToolForm - => _tools.OfType().FirstOrDefault()?.IsActive is true; - - public bool IsLoaded(Type toolType) - => _tools.Find(t => t.GetType() == toolType)?.IsActive is true; - - public bool IsOnScreen(Point topLeft) + public override bool IsOnScreen(Point topLeft) { return Screen.AllScreens.Any( screen => screen.WorkingArea.Contains(topLeft)); } - /// - /// Returns true if an instance of T exists - /// - /// Type of tool to check - public bool Has() where T : IToolForm - => _tools.Exists(static t => t is T && t.IsActive); - - /// iff a tool of the given is active - public bool Has(Type toolType) - => typeof(IToolForm).IsAssignableFrom(toolType) - && _tools.Exists(t => toolType.IsInstanceOfType(t) && t.IsActive); - - /// - /// Gets the instance of T, or creates and returns a new instance - /// - /// Type of tool to get - public IToolForm Get() where T : class, IToolForm - { - return Load(false); - } - - /// - /// returns the instance of , regardless of whether it's loaded,
- /// but doesn't create and load a new instance if it's not found - ///
- /// - /// does not check is a class implementing ;
- /// you may pass any class or interface - ///
- public IToolForm/*?*/ LazyGet(Type toolType) - => _tools.Find(t => toolType.IsAssignableFrom(t.GetType())); - internal static readonly IDictionary IconAndNameCache = new Dictionary { [typeof(LogWindow)] = (LogWindow.ToolIcon.ToBitmap(), "Log Window"), // can't do this lazily, see https://github.com/TASEmulators/BizHawk/issues/2741#issuecomment-1421014589 @@ -485,7 +277,7 @@ namespace BizHawk.Client.EmuHawk private static PropertyInfo PInfo_FormBase_WindowTitleStatic => _PInfo_FormBase_WindowTitleStatic ??= typeof(FormBase).GetProperty("WindowTitleStatic", BindingFlags.NonPublic | BindingFlags.Instance); - private static bool CaptureIconAndName(object tool, Type toolType, ref Image/*?*/ icon, ref string/*?*/ name) + protected override bool CaptureIconAndName(object tool, Type toolType, ref Image/*?*/ icon, ref string/*?*/ name) { if (IconAndNameCache.ContainsKey(toolType)) return true; Form winform = null; @@ -515,14 +307,14 @@ namespace BizHawk.Client.EmuHawk return false; } - private static void CaptureIconAndName(object tool, Type toolType) + private void CaptureIconAndName(object tool, Type toolType) { Image/*?*/ icon = null; string/*?*/ name = null; CaptureIconAndName(tool, toolType, ref icon, ref name); } - public (Image/*?*/ Icon, string Name) GetIconAndNameFor(Type toolType) + public override (Image/*?*/ Icon, string Name) GetIconAndNameFor(Type toolType) { if (IconAndNameCache.TryGetValue(toolType, out var tuple)) return tuple; Image/*?*/ icon = null; @@ -538,262 +330,23 @@ namespace BizHawk.Client.EmuHawk string.IsNullOrWhiteSpace(name) ? toolType.Name : name); } - public IEnumerable AvailableTools => EmuHawk.ReflectionCache.Types + public override IEnumerable AvailableTools => EmuHawk.ReflectionCache.Types .Where(t => !t.IsInterface && typeof(IToolForm).IsAssignableFrom(t) && IsAvailable(t)); - /// - /// Calls UpdateValues() on an instance of T, if it exists - /// - /// Type of tool to update - public void UpdateValues() where T : IToolForm + protected override void MaybeClearCheats() { - var tool = _tools.OfType().FirstOrDefault(); - if (tool?.IsActive is true) - { - tool.UpdateValues(ToolFormUpdateType.General); - } - } - - public void Restart(Config config, IEmulator emulator, IGameInfo game) - { - _config = config; - _emulator = emulator; - _game = game; - ApiProvider = ApiManager.Restart(_emulator.ServiceProvider, _owner, _displayManager, _inputManager, _movieSession, this, _config, _emulator, _game); - // If Cheat tool is loaded, restarting will restart the list too anyway if (!Has()) { - _owner.CheatList.NewList(GenerateDefaultCheatFilename(), autosave: true); - } - - var unavailable = new List(); - - foreach (var tool in _tools) - { - SetBaseProperties(tool); - if (ServiceInjector.UpdateServices(_emulator.ServiceProvider, tool) - && (tool is not IExternalToolForm || ApiInjector.UpdateApis(ApiProvider, tool))) - { - if (tool.IsActive) tool.Restart(); - } - else - { - unavailable.Add(tool); - if (tool is IExternalToolForm) ApiInjector.ClearApis(tool); - } - } - - foreach (var tool in unavailable) - { - tool.Close(); - _tools.Remove(tool); + _mainFormTools.CheatList.NewList(GenerateDefaultCheatFilename(), autosave: true); } } - /// - /// Calls Restart() on an instance of T, if it exists - /// - /// Type of tool to restart - public void Restart() where T : IToolForm - => _tools.OfType().FirstOrDefault()?.Restart(); - - /// - /// Runs AskSave on every tool dialog, false is returned if any tool returns false - /// - public bool AskSave() + protected override IExternalToolForm CreateInstanceFrom(string dllPath, string toolTypeName) { - if (_config.SuppressAskSave) // User has elected to not be nagged - { - return true; - } - - return _tools - .Select(tool => tool.AskSaveChanges()) - .All(result => result); + return Activator.CreateInstanceFrom(dllPath, toolTypeName ?? "BizHawk.Client.EmuHawk.CustomMainForm").Unwrap() as IExternalToolForm; } - /// - /// If T exists, this call will close the tool, and remove it from memory - /// - /// Type of tool to close - public void Close() where T : IToolForm - { - var tool = _tools.OfType().FirstOrDefault(); - if (tool != null) - { - tool.Close(); - _tools.Remove(tool); - } - } - - public void Close(Type toolType) - { - var tool = _tools.Find(toolType.IsInstanceOfType); - if (tool != null) - { - tool.Close(); - _tools.Remove(tool); - } - } - - public void Close() - { - _tools.ForEach(t => t.Close()); - _tools.Clear(); - } - - /// - /// Create a new instance of an IToolForm and return it - /// - /// Type of tool you want to create - /// Path .dll for an external tool - /// New instance of an IToolForm - private IToolForm CreateInstance(string dllPath) - where T : IToolForm - { - return CreateInstance(typeof(T), dllPath); - } - - /// - /// Create a new instance of an IToolForm and return it - /// - /// Type of tool you want to create - /// Path dll for an external tool - /// For external tools, of the entry form's type ( will be ) - /// New instance of an IToolForm - private IToolForm CreateInstance(Type toolType, string dllPath, string toolTypeName = null, bool skipExtToolWarning = false) - { - IToolForm tool; - - // Specific case for custom tools - // TODO: Use AppDomain in order to be able to unload the assembly - // Hard stuff as we need a proxy object that inherit from MarshalByRefObject. - if (toolType == typeof(IExternalToolForm)) - { - if (!skipExtToolWarning) - { - if (!_owner.ShowMessageBox2( - "Are you sure want to load this external tool?\r\nAccept ONLY if you trust the source and if you know what you're doing. In any other case, choose no.", - "Confirm loading", - EMsgBoxIcon.Question)) - { - return null; - } - } - - try - { - tool = Activator.CreateInstanceFrom(dllPath, toolTypeName ?? "BizHawk.Client.EmuHawk.CustomMainForm").Unwrap() as IExternalToolForm; - if (tool == null) - { - _owner.ShowMessageBox($"It seems that the object CustomMainForm does not implement {nameof(IExternalToolForm)}. Please review the code.", "No, no, no. Wrong Way !", EMsgBoxIcon.Warning); - return null; - } - } - catch (MissingMethodException) - { - _owner.ShowMessageBox("It seems that the object CustomMainForm does not have a public default constructor. Please review the code.", "No, no, no. Wrong Way !", EMsgBoxIcon.Warning); - return null; - } - catch (TypeLoadException) - { - _owner.ShowMessageBox("It seems that the object CustomMainForm does not exists. Please review the code.", "No, no, no. Wrong Way !", EMsgBoxIcon.Warning); - return null; - } - } - else - { - tool = (IToolForm)Activator.CreateInstance(toolType); - } - CaptureIconAndName(tool, toolType); - // Add to our list of tools - _tools.Add(tool); - return tool; - } - - public void UpdateToolsBefore() - { - foreach (var tool in _tools) - { - if (tool.IsActive) - { - tool.UpdateValues(ToolFormUpdateType.PreFrame); - } - } - } - - public void UpdateToolsAfter() - { - foreach (var tool in _tools) - { - if (tool.IsActive) - { - tool.UpdateValues(ToolFormUpdateType.PostFrame); - } - } - } - - public void FastUpdateBefore() - { - foreach (var tool in _tools) - { - if (tool.IsActive) - { - tool.UpdateValues(ToolFormUpdateType.FastPreFrame); - } - } - } - - public void FastUpdateAfter() - { - foreach (var tool in _tools) - { - if (tool.IsActive) - { - tool.UpdateValues(ToolFormUpdateType.FastPostFrame); - } - } - } - - private static readonly IList PossibleToolTypeNames = EmuHawk.ReflectionCache.Types.Select(t => t.AssemblyQualifiedName).ToList(); - - public bool IsAvailable(Type tool) - { - if (!ServiceInjector.IsAvailable(_emulator.ServiceProvider, tool)) return false; - if (typeof(IExternalToolForm).IsAssignableFrom(tool) && !ApiInjector.IsAvailable(ApiProvider, tool)) return false; - if (!PossibleToolTypeNames.Contains(tool.AssemblyQualifiedName) && !_extToolManager.PossibleExtToolTypeNames.Contains(tool.AssemblyQualifiedName)) return false; // not a tool - - ToolAttribute attr = tool.GetCustomAttributes(false).OfType().SingleOrDefault(); - if (attr == null) - { - return true; // no ToolAttribute on given type -> assumed all supported - } - - return !attr.UnsupportedCores.Contains(_emulator.Attributes().CoreName) // not unsupported - && (!attr.SupportedSystems.Any() || attr.SupportedSystems.Contains(_emulator.SystemId)); // supported (no supported list -> assumed all supported) - } - - public bool IsAvailable() => IsAvailable(typeof(T)); - - // Note: Referencing these properties creates an instance of the tool and persists it. They should be referenced by type if this is not desired - - private T GetTool() where T : class, IToolForm, new() - { - T tool = _tools.OfType().FirstOrDefault(); - if (tool != null) - { - if (tool.IsActive) - { - return tool; - } - - _tools.Remove(tool); - } - tool = new T(); - CaptureIconAndName(tool, typeof(T)); - _tools.Add(tool); - return tool; - } + protected override IList PossibleToolTypeNames { get; } = EmuHawk.ReflectionCache.Types.Select(t => t.AssemblyQualifiedName).ToList(); public RamWatch RamWatch => GetTool(); @@ -809,7 +362,7 @@ namespace BizHawk.Client.EmuHawk public TAStudio TAStudio => GetTool(); - public void LoadRamWatch(bool loadDialog) + public override void LoadRamWatch(bool loadDialog) { if (IsLoaded() && !_config.DisplayRamWatch) { @@ -832,20 +385,7 @@ namespace BizHawk.Client.EmuHawk } } - public string GenerateDefaultCheatFilename() - { - var path = _config.PathEntries.CheatsAbsolutePath(_game.System); - - var f = new FileInfo(path); - if (f.Directory != null && f.Directory.Exists == false) - { - f.Directory.Create(); - } - - return Path.Combine(path, $"{_game.FilesystemSafeName()}.cht"); - } - - public void UpdateCheatRelatedTools(object sender, CheatCollection.CheatListEventArgs e) + public override void UpdateCheatRelatedTools(object sender, CheatCollection.CheatListEventArgs e) { if (!_emulator.HasMemoryDomains()) { @@ -857,7 +397,17 @@ namespace BizHawk.Client.EmuHawk UpdateValues(); UpdateValues(); - _owner.UpdateCheatStatus(); + _ownerForm.UpdateCheatStatus(); + } + + protected override void SetFormParent(IToolForm form) + { + if (form is Form formform) formform.Owner = _ownerForm; + } + + protected override void SetFormClosingEvent(IToolForm form, Action action) + { + ((Form)form).FormClosing += (o, e) => action(); } } } diff --git a/src/BizHawk.Tests/Client.Common/Api/ExternalToolTests.cs b/src/BizHawk.Tests/Client.Common/Api/ExternalToolTests.cs index 0efc11acf1..a160549cf1 100644 --- a/src/BizHawk.Tests/Client.Common/Api/ExternalToolTests.cs +++ b/src/BizHawk.Tests/Client.Common/Api/ExternalToolTests.cs @@ -2,7 +2,9 @@ using System.Linq; using System.Reflection; using BizHawk.Client.Common; - +using BizHawk.Emulation.Common; +using BizHawk.Tests.Implementations; +using BizHawk.Tests.Mocks; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace BizHawk.Tests.Client.Common.Api @@ -10,26 +12,47 @@ namespace BizHawk.Tests.Client.Common.Api [TestClass] public class ExternalToolTests { + private Config config = new(); + [ClassInitialize] public static void TestInitialize(TestContext context) { // Move our .dll to a directory by itself, so that the ExternalToolManager will only find us. string asmName = Assembly.GetExecutingAssembly().Location; Directory.CreateDirectory("extTools"); - File.Copy(asmName, "extTools/ExternalToolTests.dll"); + File.Copy(asmName, "extTools/ExternalToolTests.dll", true); + } + + [TestInitialize] + public void TestSetup() + { + config.PathEntries.Paths.Find( + static (e) => string.Equals(e.Type, "External Tools", System.StringComparison.Ordinal) + )!.Path = "./extTools"; } [TestMethod] public void TestExternalToolIsFound() { - Config config = new Config(); - config.PathEntries.Paths.Find((e) => string.Equals(e.Type, "External Tools", System.StringComparison.Ordinal))!.Path = "./extTools"; - string t = config.PathEntries[PathEntryCollection.GLOBAL, "External Tools"].Path; ExternalToolManager manager = new ExternalToolManager(config, () => ("", ""), (p1, p2, p3) => true); Assert.IsTrue(manager.ToolStripItems.Count != 0); var item = manager.ToolStripItems.First(static (info) => info.Text == "TEST"); Assert.AreEqual("TEST", item.Text); } + + [TestMethod] + public void TestExternalToolIsCalled() + { + IMainFormForApi mainFormApi = new MockMainFormForApi(new NullEmulator()); + DisplayManagerBase displayManager = new TestDisplayManager(mainFormApi.Emulator); + TestToolManager toolManager = new TestToolManager(mainFormApi, config, displayManager); + + TestExternalAPI externalApi = toolManager.Load(); + Assert.AreEqual(0, externalApi.frameCount); + toolManager.UpdateToolsBefore(); + toolManager.UpdateToolsAfter(); + Assert.AreEqual(1, externalApi.frameCount); + } } } diff --git a/src/BizHawk.Tests/Implementations/TestExternalAPI.cs b/src/BizHawk.Tests/Implementations/TestExternalAPI.cs index 41f43a3ba3..5f8dbd5279 100644 --- a/src/BizHawk.Tests/Implementations/TestExternalAPI.cs +++ b/src/BizHawk.Tests/Implementations/TestExternalAPI.cs @@ -6,10 +6,10 @@ namespace BizHawk.Tests.Implementations public class TestExternalAPI : IExternalToolForm { public ApiContainer? _maybeAPIContainer { get; set; } - private ApiContainer APIs + internal ApiContainer APIs => _maybeAPIContainer!; - private int frameCount = 0; + internal int frameCount = 0; public bool IsActive => true; diff --git a/src/BizHawk.Tests/Implementations/TestToolManager.cs b/src/BizHawk.Tests/Implementations/TestToolManager.cs new file mode 100644 index 0000000000..6de05ce549 --- /dev/null +++ b/src/BizHawk.Tests/Implementations/TestToolManager.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Reflection; +using BizHawk.Client.Common; +using BizHawk.Tests.Mocks; + +namespace BizHawk.Tests.Implementations +{ + internal class TestToolManager : ToolManagerBase + { + public TestToolManager(IMainFormForApi mainFormApi, Config config, DisplayManagerBase displayManager) + : base(new MockMainFormForTools(), + mainFormApi, + config, + displayManager, + new ExternalToolManager(config, () => ("", ""), (p1, p2, p3) => false), + null, + mainFormApi.Emulator, + mainFormApi.MovieSession, + null) + { } + + + protected override IList PossibleToolTypeNames { get; } = ReflectionCache.Types + .Where(static (t) => typeof(IExternalApi).IsAssignableFrom(t)) + .Select(static (t) => t.AssemblyQualifiedName!) + .ToList(); + protected override bool CaptureIconAndName(object tool, Type toolType, ref Image? icon, ref string name) + { + ExternalToolAttribute? eta = toolType.GetCustomAttribute(); + if (eta == null) + throw new NotImplementedException(); // not an external tool + + icon = null; + name = eta.Name; + return true; + } + + protected override void SetFormParent(IToolForm form) { } + protected override void SetBaseProperties(IToolForm form) { } + + public override IEnumerable AvailableTools => throw new NotImplementedException(); + + public override (Image Icon, string Name) GetIconAndNameFor(Type toolType) => throw new NotImplementedException(); + public override bool IsOnScreen(Point topLeft) => throw new NotImplementedException(); + public override void LoadRamWatch(bool loadDialog) => throw new NotImplementedException(); + public override void UpdateCheatRelatedTools(object sender, CheatCollection.CheatListEventArgs e) => throw new NotImplementedException(); + protected override void AttachSettingHooks(IToolFormAutoConfig tool, ToolDialogSettings settings) => throw new NotImplementedException(); + + protected override IExternalToolForm CreateInstanceFrom(string dllPath, string toolTypeName) => throw new NotImplementedException(); + protected override void MaybeClearCheats() => throw new NotImplementedException(); + protected override void SetFormClosingEvent(IToolForm form, Action action) => throw new NotImplementedException(); + } +} diff --git a/src/BizHawk.Tests/Mocks/MockMainFormForTools.cs b/src/BizHawk.Tests/Mocks/MockMainFormForTools.cs new file mode 100644 index 0000000000..dafac87937 --- /dev/null +++ b/src/BizHawk.Tests/Mocks/MockMainFormForTools.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Bizware.BizwareGL; +using BizHawk.Client.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Tests.Mocks +{ + internal class MockMainFormForTools : IMainFormForTools + { + public EmuClientApi? EmuClient { get ; set; } + + public CheatCollection CheatList => throw new NotImplementedException(); + + public string CurrentlyOpenRom => throw new NotImplementedException(); + + public LoadRomArgs CurrentlyOpenRomArgs => throw new NotImplementedException(); + + public bool EmulatorPaused => throw new NotImplementedException(); + + public FirmwareManager FirmwareManager => throw new NotImplementedException(); + + public bool GameIsClosing => throw new NotImplementedException(); + + public bool HoldFrameAdvance { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public bool InvisibleEmulation { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public bool IsSeeking => throw new NotImplementedException(); + + public bool IsTurboing => throw new NotImplementedException(); + + public int? PauseOnFrame { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public bool PressRewind { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public bool BlockFrameAdvance { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public event Action? OnPauseChanged; + + public void AddOnScreenMessage(string message, int? duration = null) => throw new NotImplementedException(); + public BitmapBuffer CaptureOSD() => throw new NotImplementedException(); + public void DisableRewind() => throw new NotImplementedException(); + public void EnableRewind(bool enabled) => throw new NotImplementedException(); + public bool EnsureCoreIsAccurate() => throw new NotImplementedException(); + public void FrameAdvance() => throw new NotImplementedException(); + public void FrameBufferResized() => throw new NotImplementedException(); + public bool LoadQuickSave(int slot, bool suppressOSD = false) => throw new NotImplementedException(); + public bool LoadRom(string path, LoadRomArgs args) => throw new NotImplementedException(); + public BitmapBuffer MakeScreenshotImage() => throw new NotImplementedException(); + public void MaybePauseFromMenuOpened() => throw new NotImplementedException(); + public void MaybeUnpauseFromMenuClosed() => throw new NotImplementedException(); + public void PauseEmulator() => throw new NotImplementedException(); + public void RelinquishControl(IControlMainform master) => throw new NotImplementedException(); + public void SeekFrameAdvance() => throw new NotImplementedException(); + public void SetMainformMovieInfo() => throw new NotImplementedException(); + public IReadOnlyList? ShowFileMultiOpenDialog(IDialogParent dialogParent, string? filterStr, ref int filterIndex, string initDir, bool discardCWDChange = false, string? initFileName = null, bool maySelectMultiple = false, string? windowTitle = null) => throw new NotImplementedException(); + public string? ShowFileSaveDialog(IDialogParent dialogParent, bool discardCWDChange, string? fileExt, string? filterStr, string initDir, string? initFileName, bool muteOverwriteWarning) => throw new NotImplementedException(); + public void ShowMessageBox(IDialogParent? owner, string text, string? caption = null, EMsgBoxIcon? icon = null) => throw new NotImplementedException(); + public bool ShowMessageBox2(IDialogParent? owner, string text, string? caption = null, EMsgBoxIcon? icon = null, bool useOKCancel = false) => throw new NotImplementedException(); + public bool? ShowMessageBox3(IDialogParent? owner, string text, string? caption = null, EMsgBoxIcon? icon = null) => throw new NotImplementedException(); + public bool StartNewMovie(IMovie movie, bool record) => throw new NotImplementedException(); + public void StartSound() => throw new NotImplementedException(); + public void StopSound() => throw new NotImplementedException(); + public void TakeBackControl() => throw new NotImplementedException(); + public void Throttle() => throw new NotImplementedException(); + public void TogglePause() => throw new NotImplementedException(); + public void UnpauseEmulator() => throw new NotImplementedException(); + public void Unthrottle() => throw new NotImplementedException(); + public void UpdateDumpInfo(RomStatus? newStatus = null) => throw new NotImplementedException(); + public void UpdateStatusSlots() => throw new NotImplementedException(); + public void UpdateWindowTitle() => throw new NotImplementedException(); + } +}