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
{
///
/// This static class handle all ExternalTools
///
public static class ExternalToolManager
{
#region Fields
private static readonly FileSystemWatcher DirectoryMonitor;
private static readonly List MenuItems = new List();
#endregion
#region cTor(s)
///
/// Initialization
///
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
///
/// Build the ToolStrip menu
///
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));
}
}
}
/// Generates a from an assembly at containing an external tool.
///
/// a with its containing a (string, string);
/// the first is the assembly path () and the second is the of the entry point form's type
///
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().Any());
#pragma warning disable CS0618
if (entryPoint == null) throw new ExternalToolAttribute.MissingException(externalToolFile.GetCustomAttributes().OfType().Any());
#pragma warning restore CS0618
var allAttrs = entryPoint.GetCustomAttributes().ToList();
var applicabilityAttrs = allAttrs.OfType().ToList();
if (applicabilityAttrs.Count > 1) throw new ExternalToolApplicabilityAttributeBase.DuplicateException();
var toolAttribute = allAttrs.OfType().First();
var embeddedIconAttr = allAttrs.OfType().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;
}
///
/// 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
///
/// Object that raised the event
/// Event arguments
private static void DirectoryMonitor_Created(object sender, FileSystemEventArgs e)
{
MenuItems.Add(GenerateToolTipFromFileName(e.FullPath));
}
#endregion
#region Properties
///
/// Gets a prebuild
/// This list auto-updated by the itself
///
public static IEnumerable ToolStripMenu => MenuItems;
#endregion
}
}