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 } }