BizHawk/BizHawk.Client.ApiHawk/Classes/ExternalToolManager.cs

174 lines
6.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Common;
namespace BizHawk.Client.ApiHawk
{
/// <summary>
/// This static class handle all ExternalTools
/// </summary>
public static class ExternalToolManager
{
#region Fields
private static readonly FileSystemWatcher DirectoryMonitor;
private static readonly List<ToolStripMenuItem> MenuItems = new List<ToolStripMenuItem>();
#endregion
#region cTor(s)
/// <summary>
/// Initialization
/// </summary>
static ExternalToolManager()
{
if(!Directory.Exists(Global.Config.PathEntries["Global", "External Tools"].Path))
{
Directory.CreateDirectory(Global.Config.PathEntries["Global", "External Tools"].Path);
}
DirectoryMonitor = new FileSystemWatcher(Global.Config.PathEntries["Global", "External Tools"].Path, "*.dll")
{
IncludeSubdirectories = false
, NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName
, Filter = "*.dll"
};
DirectoryMonitor.Created += DirectoryMonitor_Created;
DirectoryMonitor.EnableRaisingEvents = true;
ClientApi.RomLoaded += delegate { BuildToolStrip(); };
BuildToolStrip();
}
#endregion
#region Methods
/// <summary>
/// Build the ToolStrip menu
/// </summary>
private static void BuildToolStrip()
{
MenuItems.Clear();
if (Directory.Exists(DirectoryMonitor.Path))
{
DirectoryInfo dInfo = new DirectoryInfo(DirectoryMonitor.Path);
foreach (FileInfo fi in dInfo.GetFiles("*.dll"))
{
MenuItems.Add(GenerateToolTipFromFileName(fi.FullName));
}
}
}
/// <summary>Generates a <see cref="ToolStripMenuItem"/> from an assembly at <paramref name="fileName"/> containing an external tool.</summary>
/// <returns>
/// a <see cref="ToolStripMenuItem"/> with its <see cref="ToolStripItem.Tag"/> containing a <c>(string, string)</c>;
/// the first is the assembly path (<paramref name="fileName"/>) and the second is the <see cref="Type.FullName"/> of the entry point form's type
/// </returns>
private static ToolStripMenuItem GenerateToolTipFromFileName(string fileName)
{
if (fileName == null) throw new Exception();
var item = new ToolStripMenuItem(Path.GetFileName(fileName)) { Enabled = false };
try
{
if (!OSTailoredCode.IsUnixHost) MotWHack.RemoveMOTW(fileName);
var externalToolFile = Assembly.LoadFrom(fileName);
var entryPoint = externalToolFile.GetTypes()
.SingleOrDefault(t => typeof(IExternalToolForm).IsAssignableFrom(t) && t.GetCustomAttributes().OfType<ExternalToolAttribute>().Any());
#pragma warning disable CS0618
if (entryPoint == null) throw new ExternalToolAttribute.MissingException(externalToolFile.GetCustomAttributes().OfType<BizHawkExternalToolAttribute>().Any());
#pragma warning restore CS0618
var allAttrs = entryPoint.GetCustomAttributes().ToList();
var applicabilityAttrs = allAttrs.OfType<ExternalToolApplicabilityAttributeBase>().ToList();
if (applicabilityAttrs.Count > 1) throw new ExternalToolApplicabilityAttributeBase.DuplicateException();
var toolAttribute = allAttrs.OfType<ExternalToolAttribute>().First();
var embeddedIconAttr = allAttrs.OfType<ExternalToolEmbeddedIconAttribute>().FirstOrDefault();
if (embeddedIconAttr != null)
{
var rawIcon = externalToolFile.GetManifestResourceStream(embeddedIconAttr.ResourcePath);
if (rawIcon != null) item.Image = new Bitmap(rawIcon);
}
item.Text = toolAttribute.Name;
item.Tag = (externalToolFile.Location, entryPoint.FullName); // Tag set => no errors (show custom icon even when disabled)
if (applicabilityAttrs.Count == 1)
{
var system = ClientApi.SystemIdConverter.Convert(Global.Emulator.SystemId);
if (applicabilityAttrs[0].NotApplicableTo(system))
{
item.ToolTipText = system == CoreSystem.Null
? "This tool doesn't work when no rom is loaded"
: "This tool doesn't work with this system";
return item;
}
if (applicabilityAttrs[0].NotApplicableTo(Global.Game.Hash, system))
{
item.ToolTipText = "This tool doesn't work with this game";
return item;
}
}
item.Enabled = true;
if (!string.IsNullOrWhiteSpace(toolAttribute.Description)) item.ToolTipText = toolAttribute.Description;
return item;
}
catch (Exception e)
{
#if DEBUG
if (e is ReflectionTypeLoadException rtle)
{
foreach (var e1 in rtle.LoaderExceptions) Debug.WriteLine(e1.Message);
}
#endif
item.ToolTipText = e switch
{
BadImageFormatException _ => "This assembly can't be loaded, probably because it's corrupt or targets an incompatible .NET runtime.",
ExternalToolApplicabilityAttributeBase.DuplicateException _ => "The IExternalToolForm has conflicting applicability attributes.",
ExternalToolAttribute.MissingException e1 => e1.OldAttributeFound
? "The assembly doesn't contain a class implementing IExternalToolForm and annotated with [ExternalTool].\nHowever, the assembly itself is annotated with [BizHawkExternalTool], which is now deprecated. Has the tool been updated since BizHawk 2.4?"
: "The assembly doesn't contain a class implementing IExternalToolForm and annotated with [ExternalTool].",
ReflectionTypeLoadException _ => "Something went wrong while trying to load the assembly.",
_ => $"An exception of type {e.GetType().FullName} was thrown while trying to load the assembly and look for an IExternalToolForm:\n{e.Message}"
};
}
return item;
}
/// <summary>
/// This event is raised when we add a dll file into
/// the external tools path.
/// It will automatically load the assembly and add it into the list
/// </summary>
/// <param name="sender">Object that raised the event</param>
/// <param name="e">Event arguments</param>
private static void DirectoryMonitor_Created(object sender, FileSystemEventArgs e)
{
MenuItems.Add(GenerateToolTipFromFileName(e.FullPath));
}
#endregion
#region Properties
/// <summary>
/// Gets a prebuild <see cref="ToolStripMenuItem"/>
/// This list auto-updated by the <see cref="ExternalToolManager"/> itself
/// </summary>
public static IEnumerable<ToolStripMenuItem> ToolStripMenu => MenuItems;
#endregion
}
}