Move most of ToolManager's functionality to new ToolManagerBase, to support testing external tools

This commit is contained in:
SuuperW 2023-10-08 19:47:35 -05:00
parent ae76497aa4
commit 74e210e80b
8 changed files with 817 additions and 514 deletions

View File

@ -109,5 +109,7 @@ namespace BizHawk.Client.Common
/// <remarks>only referenced from TAStudio</remarks>
void UpdateWindowTitle();
public EmuClientApi EmuClient { get; set; }
}
}

View File

@ -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<string>();
UnsupportedCores = unsupportedCores ?? Enumerable.Empty<string>();
}
public ToolAttribute(bool released, string[] supportedSystems) : this(released, supportedSystems, null) {}
public bool Released { get; }
public IEnumerable<string> SupportedSystems { get; }
public IEnumerable<string> 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<string>();
UnsupportedCores = unsupportedCores ?? Enumerable.Empty<string>();
}
public ToolAttribute(bool released, string[] supportedSystems) : this(released, supportedSystems, null) {}
public bool Released { get; }
public IEnumerable<string> SupportedSystems { get; }
public IEnumerable<string> UnsupportedCores { get; }
}
}

View File

@ -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<IToolForm> _tools = new List<IToolForm>();
private IExternalApiProvider ApiProvider
{
get => _apiProvider;
set => _mainFormTools.EmuClient = (EmuClientApi)(_apiProvider = value).GetApi<IEmuClientApi>();
}
/// <summary>
/// Initializes a new instance of the <see cref="ToolManagerBase"/> class.
/// </summary>
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);
}
/// <summary>
/// Loads the tool dialog T (T must implements <see cref="IToolForm"/>) , 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
/// </summary>
/// <param name="toolType">Type of tool you want to load</param>
/// <param name="focus">Define if the tool form has to get the focus or not (Default is true)</param>
/// <returns>An instantiated <see cref="IToolForm"/></returns>
/// <exception cref="ArgumentException">Raised if <paramref name="toolType"/> can't cast into IToolForm </exception>
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);
/// <summary>
/// Loads the tool dialog T (T must implement <see cref="IToolForm"/>) , if it does not exist it will be created, if it is already open, it will be focused
/// </summary>
/// <param name="focus">Define if the tool form has to get the focus or not (Default is true)</param>
/// <param name="toolPath">Path to the .dll of the external tool</param>
/// <typeparam name="T">Type of tool you want to load</typeparam>
/// <returns>An instantiated <see cref="IToolForm"/></returns>
public T Load<T>(bool focus = true, string toolPath = "")
where T : class, IToolForm
{
if (!IsAvailable<T>()) return null;
var existingTool = _tools.OfType<T>().FirstOrDefault();
if (existingTool != null)
{
if (existingTool.IsLoaded)
{
if (focus)
{
existingTool.Show();
existingTool.Focus();
}
return existingTool;
}
_tools.Remove(existingTool);
}
if (CreateInstance<T>(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;
}
/// <summary>Loads the external tool's entry form.</summary>
public IExternalToolForm LoadExternalToolForm(string toolPath, string customFormTypeName, bool focus = true, bool skipExtToolWarning = false)
{
var existingTool = _tools.OfType<IExternalToolForm>().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<string, object> 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<string, object> data, List<PropertyInfo> props)
{
data.Clear();
foreach (var prop in props)
{
data.Add(prop.Name, prop.GetValue(tool, BindingFlags.GetProperty, Type.DefaultBinder, null, CultureInfo.InvariantCulture));
}
}
/// <summary>
/// Determines whether a given IToolForm is already loaded
/// </summary>
/// <typeparam name="T">Type of tool to check</typeparam>
/// <remarks>yo why do we have 4 versions of this, each with slightly different behaviour in edge cases --yoshi</remarks>
public bool IsLoaded<T>() where T : IToolForm
=> _tools.OfType<T>().FirstOrDefault()?.IsActive is true;
public bool IsLoaded(Type toolType)
=> _tools.Find(t => t.GetType() == toolType)?.IsActive is true;
public abstract bool IsOnScreen(Point topLeft);
/// <summary>
/// Returns true if an instance of T exists
/// </summary>
/// <typeparam name="T">Type of tool to check</typeparam>
public bool Has<T>() where T : IToolForm
=> _tools.Exists(static t => t is T && t.IsActive);
/// <returns><see langword="true"/> iff a tool of the given <paramref name="toolType"/> is <see cref="IToolForm.IsActive">active</see></returns>
public bool Has(Type toolType)
=> typeof(IToolForm).IsAssignableFrom(toolType)
&& _tools.Exists(t => toolType.IsInstanceOfType(t) && t.IsActive);
/// <summary>
/// Gets the instance of T, or creates and returns a new instance
/// </summary>
/// <typeparam name="T">Type of tool to get</typeparam>
public IToolForm Get<T>() where T : class, IToolForm
{
return Load<T>(false);
}
/// <summary>
/// returns the instance of <paramref name="toolType"/>, regardless of whether it's loaded,<br/>
/// but doesn't create and load a new instance if it's not found
/// </summary>
/// <remarks>
/// does not check <paramref name="toolType"/> is a class implementing <see cref="IToolForm"/>;<br/>
/// you may pass any class or interface
/// </remarks>
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<Type> AvailableTools { get; }
/// <summary>
/// Calls UpdateValues() on an instance of T, if it exists
/// </summary>
/// <typeparam name="T">Type of tool to update</typeparam>
public void UpdateValues<T>() where T : IToolForm
{
var tool = _tools.OfType<T>().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<IToolForm>();
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);
}
}
/// <summary>
/// Calls Restart() on an instance of T, if it exists
/// </summary>
/// <typeparam name="T">Type of tool to restart</typeparam>
public void Restart<T>() where T : IToolForm
=> _tools.OfType<T>().FirstOrDefault()?.Restart();
/// <summary>
/// Runs AskSave on every tool dialog, false is returned if any tool returns false
/// </summary>
public bool AskSave()
{
if (_config.SuppressAskSave) // User has elected to not be nagged
{
return true;
}
return _tools
.Select(tool => tool.AskSaveChanges())
.All(result => result);
}
/// <summary>
/// If T exists, this call will close the tool, and remove it from memory
/// </summary>
/// <typeparam name="T">Type of tool to close</typeparam>
public void Close<T>() where T : IToolForm
{
var tool = _tools.OfType<T>().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();
}
/// <summary>
/// Create a new instance of an IToolForm and return it
/// </summary>
/// <typeparam name="T">Type of tool you want to create</typeparam>
/// <param name="dllPath">Path .dll for an external tool</param>
/// <returns>New instance of an IToolForm</returns>
private IToolForm CreateInstance<T>(string dllPath)
where T : IToolForm
{
return CreateInstance(typeof(T), dllPath);
}
protected abstract IExternalToolForm CreateInstanceFrom(string dllPath, string toolTypeName);
/// <summary>
/// Create a new instance of an IToolForm and return it
/// </summary>
/// <param name="toolType">Type of tool you want to create</param>
/// <param name="dllPath">Path dll for an external tool</param>
/// <param name="toolTypeName">For external tools, <see cref="Type.FullName"/> of the entry form's type (<paramref name="toolType"/> will be <see cref="IExternalToolForm"/>)</param>
/// <returns>New instance of an IToolForm</returns>
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<string> 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<ToolAttribute>().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<T>() => 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<T>() where T : class, IToolForm, new()
{
T tool = _tools.OfType<T>().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);
}
}

View File

@ -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<IToolForm> _tools = new List<IToolForm>();
private IExternalApiProvider ApiProvider
{
get => _apiProvider;
set => _owner.EmuClient = (EmuClientApi) (_apiProvider = value).GetApi<IEmuClientApi>();
}
/// <summary>
/// Initializes a new instance of the <see cref="ToolManager"/> class.
/// </summary>
@ -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);
}
/// <summary>
/// Loads the tool dialog T (T must implements <see cref="IToolForm"/>) , 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
/// </summary>
/// <param name="toolType">Type of tool you want to load</param>
/// <param name="focus">Define if the tool form has to get the focus or not (Default is true)</param>
/// <returns>An instantiated <see cref="IToolForm"/></returns>
/// <exception cref="ArgumentException">Raised if <paramref name="toolType"/> can't cast into IToolForm </exception>
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);
}
/// <summary>
/// Loads the tool dialog T (T must implement <see cref="IToolForm"/>) , if it does not exist it will be created, if it is already open, it will be focused
/// </summary>
/// <param name="focus">Define if the tool form has to get the focus or not (Default is true)</param>
/// <param name="toolPath">Path to the .dll of the external tool</param>
/// <typeparam name="T">Type of tool you want to load</typeparam>
/// <returns>An instantiated <see cref="IToolForm"/></returns>
public T Load<T>(bool focus = true, string toolPath = "")
where T : class, IToolForm
{
if (!IsAvailable<T>()) return null;
var existingTool = _tools.OfType<T>().FirstOrDefault();
if (existingTool != null)
{
if (existingTool.IsLoaded)
{
if (focus)
{
existingTool.Show();
existingTool.Focus();
}
return existingTool;
}
_tools.Remove(existingTool);
}
if (CreateInstance<T>(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;
}
/// <summary>Loads the external tool's entry form.</summary>
public IExternalToolForm LoadExternalToolForm(string toolPath, string customFormTypeName, bool focus = true, bool skipExtToolWarning = false)
{
var existingTool = _tools.OfType<IExternalToolForm>().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
}
}
/// <summary>
/// Determines whether a given IToolForm is already loaded
/// </summary>
/// <typeparam name="T">Type of tool to check</typeparam>
/// <remarks>yo why do we have 4 versions of this, each with slightly different behaviour in edge cases --yoshi</remarks>
public bool IsLoaded<T>() where T : IToolForm
=> _tools.OfType<T>().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));
}
/// <summary>
/// Returns true if an instance of T exists
/// </summary>
/// <typeparam name="T">Type of tool to check</typeparam>
public bool Has<T>() where T : IToolForm
=> _tools.Exists(static t => t is T && t.IsActive);
/// <returns><see langword="true"/> iff a tool of the given <paramref name="toolType"/> is <see cref="IToolForm.IsActive">active</see></returns>
public bool Has(Type toolType)
=> typeof(IToolForm).IsAssignableFrom(toolType)
&& _tools.Exists(t => toolType.IsInstanceOfType(t) && t.IsActive);
/// <summary>
/// Gets the instance of T, or creates and returns a new instance
/// </summary>
/// <typeparam name="T">Type of tool to get</typeparam>
public IToolForm Get<T>() where T : class, IToolForm
{
return Load<T>(false);
}
/// <summary>
/// returns the instance of <paramref name="toolType"/>, regardless of whether it's loaded,<br/>
/// but doesn't create and load a new instance if it's not found
/// </summary>
/// <remarks>
/// does not check <paramref name="toolType"/> is a class implementing <see cref="IToolForm"/>;<br/>
/// you may pass any class or interface
/// </remarks>
public IToolForm/*?*/ LazyGet(Type toolType)
=> _tools.Find(t => toolType.IsAssignableFrom(t.GetType()));
internal static readonly IDictionary<Type, (Image/*?*/ Icon, string Name)> IconAndNameCache = new Dictionary<Type, (Image/*?*/ Icon, string Name)>
{
[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<Type> AvailableTools => EmuHawk.ReflectionCache.Types
public override IEnumerable<Type> AvailableTools => EmuHawk.ReflectionCache.Types
.Where(t => !t.IsInterface && typeof(IToolForm).IsAssignableFrom(t) && IsAvailable(t));
/// <summary>
/// Calls UpdateValues() on an instance of T, if it exists
/// </summary>
/// <typeparam name="T">Type of tool to update</typeparam>
public void UpdateValues<T>() where T : IToolForm
protected override void MaybeClearCheats()
{
var tool = _tools.OfType<T>().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<Cheats>())
{
_owner.CheatList.NewList(GenerateDefaultCheatFilename(), autosave: true);
}
var unavailable = new List<IToolForm>();
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);
}
}
/// <summary>
/// Calls Restart() on an instance of T, if it exists
/// </summary>
/// <typeparam name="T">Type of tool to restart</typeparam>
public void Restart<T>() where T : IToolForm
=> _tools.OfType<T>().FirstOrDefault()?.Restart();
/// <summary>
/// Runs AskSave on every tool dialog, false is returned if any tool returns false
/// </summary>
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;
}
/// <summary>
/// If T exists, this call will close the tool, and remove it from memory
/// </summary>
/// <typeparam name="T">Type of tool to close</typeparam>
public void Close<T>() where T : IToolForm
{
var tool = _tools.OfType<T>().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();
}
/// <summary>
/// Create a new instance of an IToolForm and return it
/// </summary>
/// <typeparam name="T">Type of tool you want to create</typeparam>
/// <param name="dllPath">Path .dll for an external tool</param>
/// <returns>New instance of an IToolForm</returns>
private IToolForm CreateInstance<T>(string dllPath)
where T : IToolForm
{
return CreateInstance(typeof(T), dllPath);
}
/// <summary>
/// Create a new instance of an IToolForm and return it
/// </summary>
/// <param name="toolType">Type of tool you want to create</param>
/// <param name="dllPath">Path dll for an external tool</param>
/// <param name="toolTypeName">For external tools, <see cref="Type.FullName"/> of the entry form's type (<paramref name="toolType"/> will be <see cref="IExternalToolForm"/>)</param>
/// <returns>New instance of an IToolForm</returns>
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<string> 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<ToolAttribute>().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<T>() => 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<T>() where T : class, IToolForm, new()
{
T tool = _tools.OfType<T>().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<string> PossibleToolTypeNames { get; } = EmuHawk.ReflectionCache.Types.Select(t => t.AssemblyQualifiedName).ToList();
public RamWatch RamWatch => GetTool<RamWatch>();
@ -809,7 +362,7 @@ namespace BizHawk.Client.EmuHawk
public TAStudio TAStudio => GetTool<TAStudio>();
public void LoadRamWatch(bool loadDialog)
public override void LoadRamWatch(bool loadDialog)
{
if (IsLoaded<RamWatch>() && !_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<HexEditor>();
UpdateValues<Cheats>();
_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();
}
}
}

View File

@ -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<TestExternalAPI>();
Assert.AreEqual(0, externalApi.frameCount);
toolManager.UpdateToolsBefore();
toolManager.UpdateToolsAfter();
Assert.AreEqual(1, externalApi.frameCount);
}
}
}

View File

@ -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;

View File

@ -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<string> 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<ExternalToolAttribute>();
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<Type> 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();
}
}

View File

@ -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<bool>? 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<string>? 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();
}
}