Move most of ToolManager's functionality to new ToolManagerBase, to support testing external tools
This commit is contained in:
parent
ae76497aa4
commit
74e210e80b
|
@ -109,5 +109,7 @@ namespace BizHawk.Client.Common
|
||||||
|
|
||||||
/// <remarks>only referenced from TAStudio</remarks>
|
/// <remarks>only referenced from TAStudio</remarks>
|
||||||
void UpdateWindowTitle();
|
void UpdateWindowTitle();
|
||||||
|
|
||||||
|
public EmuClientApi EmuClient { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace BizHawk.Client.EmuHawk
|
namespace BizHawk.Client.Common
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public sealed class ToolAttribute : Attribute
|
public sealed class ToolAttribute : Attribute
|
||||||
{
|
{
|
||||||
public ToolAttribute(bool released, string[] supportedSystems, string[] unsupportedCores)
|
public ToolAttribute(bool released, string[] supportedSystems, string[] unsupportedCores)
|
||||||
{
|
{
|
||||||
Released = released;
|
Released = released;
|
||||||
SupportedSystems = supportedSystems ?? Enumerable.Empty<string>();
|
SupportedSystems = supportedSystems ?? Enumerable.Empty<string>();
|
||||||
UnsupportedCores = unsupportedCores ?? Enumerable.Empty<string>();
|
UnsupportedCores = unsupportedCores ?? Enumerable.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ToolAttribute(bool released, string[] supportedSystems) : this(released, supportedSystems, null) {}
|
public ToolAttribute(bool released, string[] supportedSystems) : this(released, supportedSystems, null) {}
|
||||||
|
|
||||||
public bool Released { get; }
|
public bool Released { get; }
|
||||||
|
|
||||||
public IEnumerable<string> SupportedSystems { get; }
|
public IEnumerable<string> SupportedSystems { get; }
|
||||||
|
|
||||||
public IEnumerable<string> UnsupportedCores { get; }
|
public IEnumerable<string> UnsupportedCores { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
@ -10,36 +9,21 @@ using System.Windows.Forms;
|
||||||
|
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
using BizHawk.Common.CollectionExtensions;
|
|
||||||
using BizHawk.Common.ReflectionExtensions;
|
using BizHawk.Common.ReflectionExtensions;
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
using BizHawk.WinForms.Controls;
|
using BizHawk.WinForms.Controls;
|
||||||
|
|
||||||
namespace BizHawk.Client.EmuHawk
|
namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
public class ToolManager : IToolManager
|
public class ToolManager : ToolManagerBase, IToolManager
|
||||||
{
|
{
|
||||||
private readonly MainForm _owner;
|
private readonly MainForm _ownerForm;
|
||||||
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;
|
|
||||||
|
|
||||||
// TODO: merge ToolHelper code where logical
|
// 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
|
// 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
|
// Also a UsesRam, and similar method
|
||||||
private readonly List<IToolForm> _tools = new List<IToolForm>();
|
private readonly List<IToolForm> _tools = new List<IToolForm>();
|
||||||
|
|
||||||
private IExternalApiProvider ApiProvider
|
|
||||||
{
|
|
||||||
get => _apiProvider;
|
|
||||||
set => _owner.EmuClient = (EmuClientApi) (_apiProvider = value).GetApi<IEmuClientApi>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ToolManager"/> class.
|
/// Initializes a new instance of the <see cref="ToolManager"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -51,168 +35,19 @@ namespace BizHawk.Client.EmuHawk
|
||||||
InputManager inputManager,
|
InputManager inputManager,
|
||||||
IEmulator emulator,
|
IEmulator emulator,
|
||||||
IMovieSession movieSession,
|
IMovieSession movieSession,
|
||||||
IGameInfo game)
|
IGameInfo game) : base(owner, owner, config, displayManager, extToolManager, inputManager, emulator, movieSession, game)
|
||||||
{
|
{
|
||||||
|
_ownerForm = owner;
|
||||||
|
|
||||||
_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, "" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the form inherits ToolFormBase, it will set base properties such as Tools, Config, etc
|
// 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;
|
if (form is not FormBase f) return;
|
||||||
|
|
||||||
f.Config = _config;
|
f.Config = _config;
|
||||||
if (form is not ToolFormBase tool) return;
|
if (form is not ToolFormBase tool) return;
|
||||||
tool.SetToolFormBaseProps(_displayManager, _inputManager, _owner, _movieSession, this, _game);
|
tool.SetToolFormBaseProps(_displayManager, _inputManager, _mainFormTools, _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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshSettings(Form form, ToolStripItemCollection menu, ToolDialogSettings settings, int idx)
|
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;
|
((ToolStripMenuItem)menu[idx + 3]).Checked = settings.AutoLoad;
|
||||||
|
|
||||||
// do we need to do this OnShown() as well?
|
// 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)
|
private void AddCloseButton(ToolStripMenuItem subMenu, Form form)
|
||||||
|
@ -255,7 +90,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
subMenu.DropDownItems.Add(closeMenuItem);
|
subMenu.DropDownItems.Add(closeMenuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AttachSettingHooks(IToolFormAutoConfig tool, ToolDialogSettings settings)
|
protected override void AttachSettingHooks(IToolFormAutoConfig tool, ToolDialogSettings settings)
|
||||||
{
|
{
|
||||||
var form = (Form)tool;
|
var form = (Form)tool;
|
||||||
ToolStripItemCollection dest = null;
|
ToolStripItemCollection dest = null;
|
||||||
|
@ -356,7 +191,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
bool val = !((ToolStripMenuItem)o).Checked;
|
bool val = !((ToolStripMenuItem)o).Checked;
|
||||||
settings.FloatingWindow = val;
|
settings.FloatingWindow = val;
|
||||||
((ToolStripMenuItem)o).Checked = val;
|
((ToolStripMenuItem)o).Checked = val;
|
||||||
form.Owner = val ? null : _owner;
|
form.Owner = val ? null : _ownerForm;
|
||||||
};
|
};
|
||||||
dest[idx + 3].Click += (o, e) =>
|
dest[idx + 3].Click += (o, e) =>
|
||||||
{
|
{
|
||||||
|
@ -426,55 +261,12 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public override bool IsOnScreen(Point topLeft)
|
||||||
/// 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)
|
|
||||||
{
|
{
|
||||||
return Screen.AllScreens.Any(
|
return Screen.AllScreens.Any(
|
||||||
screen => screen.WorkingArea.Contains(topLeft));
|
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)>
|
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
|
[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
|
private static PropertyInfo PInfo_FormBase_WindowTitleStatic
|
||||||
=> _PInfo_FormBase_WindowTitleStatic ??= typeof(FormBase).GetProperty("WindowTitleStatic", BindingFlags.NonPublic | BindingFlags.Instance);
|
=> _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;
|
if (IconAndNameCache.ContainsKey(toolType)) return true;
|
||||||
Form winform = null;
|
Form winform = null;
|
||||||
|
@ -515,14 +307,14 @@ namespace BizHawk.Client.EmuHawk
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CaptureIconAndName(object tool, Type toolType)
|
private void CaptureIconAndName(object tool, Type toolType)
|
||||||
{
|
{
|
||||||
Image/*?*/ icon = null;
|
Image/*?*/ icon = null;
|
||||||
string/*?*/ name = null;
|
string/*?*/ name = null;
|
||||||
CaptureIconAndName(tool, toolType, ref icon, ref name);
|
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;
|
if (IconAndNameCache.TryGetValue(toolType, out var tuple)) return tuple;
|
||||||
Image/*?*/ icon = null;
|
Image/*?*/ icon = null;
|
||||||
|
@ -538,262 +330,23 @@ namespace BizHawk.Client.EmuHawk
|
||||||
string.IsNullOrWhiteSpace(name) ? toolType.Name : name);
|
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));
|
.Where(t => !t.IsInterface && typeof(IToolForm).IsAssignableFrom(t) && IsAvailable(t));
|
||||||
|
|
||||||
/// <summary>
|
protected override void MaybeClearCheats()
|
||||||
/// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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>())
|
if (!Has<Cheats>())
|
||||||
{
|
{
|
||||||
_owner.CheatList.NewList(GenerateDefaultCheatFilename(), autosave: true);
|
_mainFormTools.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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override IExternalToolForm CreateInstanceFrom(string dllPath, string toolTypeName)
|
||||||
/// 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 Activator.CreateInstanceFrom(dllPath, toolTypeName ?? "BizHawk.Client.EmuHawk.CustomMainForm").Unwrap() as IExternalToolForm;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _tools
|
|
||||||
.Select(tool => tool.AskSaveChanges())
|
|
||||||
.All(result => result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override IList<string> PossibleToolTypeNames { get; } = EmuHawk.ReflectionCache.Types.Select(t => t.AssemblyQualifiedName).ToList();
|
||||||
/// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RamWatch RamWatch => GetTool<RamWatch>();
|
public RamWatch RamWatch => GetTool<RamWatch>();
|
||||||
|
|
||||||
|
@ -809,7 +362,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
public TAStudio TAStudio => GetTool<TAStudio>();
|
public TAStudio TAStudio => GetTool<TAStudio>();
|
||||||
|
|
||||||
public void LoadRamWatch(bool loadDialog)
|
public override void LoadRamWatch(bool loadDialog)
|
||||||
{
|
{
|
||||||
if (IsLoaded<RamWatch>() && !_config.DisplayRamWatch)
|
if (IsLoaded<RamWatch>() && !_config.DisplayRamWatch)
|
||||||
{
|
{
|
||||||
|
@ -832,20 +385,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GenerateDefaultCheatFilename()
|
public override void UpdateCheatRelatedTools(object sender, CheatCollection.CheatListEventArgs e)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
if (!_emulator.HasMemoryDomains())
|
if (!_emulator.HasMemoryDomains())
|
||||||
{
|
{
|
||||||
|
@ -857,7 +397,17 @@ namespace BizHawk.Client.EmuHawk
|
||||||
UpdateValues<HexEditor>();
|
UpdateValues<HexEditor>();
|
||||||
UpdateValues<Cheats>();
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using BizHawk.Tests.Implementations;
|
||||||
|
using BizHawk.Tests.Mocks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
namespace BizHawk.Tests.Client.Common.Api
|
namespace BizHawk.Tests.Client.Common.Api
|
||||||
|
@ -10,26 +12,47 @@ namespace BizHawk.Tests.Client.Common.Api
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class ExternalToolTests
|
public class ExternalToolTests
|
||||||
{
|
{
|
||||||
|
private Config config = new();
|
||||||
|
|
||||||
[ClassInitialize]
|
[ClassInitialize]
|
||||||
public static void TestInitialize(TestContext context)
|
public static void TestInitialize(TestContext context)
|
||||||
{
|
{
|
||||||
// Move our .dll to a directory by itself, so that the ExternalToolManager will only find us.
|
// Move our .dll to a directory by itself, so that the ExternalToolManager will only find us.
|
||||||
string asmName = Assembly.GetExecutingAssembly().Location;
|
string asmName = Assembly.GetExecutingAssembly().Location;
|
||||||
Directory.CreateDirectory("extTools");
|
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]
|
[TestMethod]
|
||||||
public void TestExternalToolIsFound()
|
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);
|
ExternalToolManager manager = new ExternalToolManager(config, () => ("", ""), (p1, p2, p3) => true);
|
||||||
|
|
||||||
Assert.IsTrue(manager.ToolStripItems.Count != 0);
|
Assert.IsTrue(manager.ToolStripItems.Count != 0);
|
||||||
var item = manager.ToolStripItems.First(static (info) => info.Text == "TEST");
|
var item = manager.ToolStripItems.First(static (info) => info.Text == "TEST");
|
||||||
Assert.AreEqual("TEST", item.Text);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@ namespace BizHawk.Tests.Implementations
|
||||||
public class TestExternalAPI : IExternalToolForm
|
public class TestExternalAPI : IExternalToolForm
|
||||||
{
|
{
|
||||||
public ApiContainer? _maybeAPIContainer { get; set; }
|
public ApiContainer? _maybeAPIContainer { get; set; }
|
||||||
private ApiContainer APIs
|
internal ApiContainer APIs
|
||||||
=> _maybeAPIContainer!;
|
=> _maybeAPIContainer!;
|
||||||
|
|
||||||
private int frameCount = 0;
|
internal int frameCount = 0;
|
||||||
|
|
||||||
public bool IsActive => true;
|
public bool IsActive => true;
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue