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)); } } } /// /// Generate a from an /// external tool dll. /// The assembly must have in its /// assembly attributes /// /// File that will be reflected /// A new ; assembly path can be found in the Tag property /// For the moment, you could only load a dll that have a form (which implements ) private static ToolStripMenuItem GenerateToolTipFromFileName(string fileName) { ToolStripMenuItem item = null; try { if (!OSTailoredCode.IsUnixHost) MotWHack.RemoveMOTW(fileName); var externalToolFile = Assembly.LoadFrom(fileName); object[] attributes = externalToolFile.GetCustomAttributes(typeof(BizHawkExternalToolAttribute), false); if (attributes != null && attributes.Count() == 1) { BizHawkExternalToolAttribute attribute = (BizHawkExternalToolAttribute)attributes[0]; item = new ToolStripMenuItem(attribute.Name) { ToolTipText = attribute.Description }; if (attribute.IconResourceName != "") { Stream s = externalToolFile.GetManifestResourceStream($"{externalToolFile.GetName().Name}.{attribute.IconResourceName}"); if (s != null) { item.Image = new Bitmap(s); } } var customFormType = externalToolFile.GetTypes().FirstOrDefault(t => t != null && t.FullName == "BizHawk.Client.EmuHawk.CustomMainForm"); if (customFormType == null) { item.ToolTipText = "Does not have a CustomMainForm"; item.Enabled = false; } item.Tag = fileName; attributes = externalToolFile.GetCustomAttributes(typeof(BizHawkExternalToolUsageAttribute), false); if (attributes != null && attributes.Length == 1) { BizHawkExternalToolUsageAttribute attribute2 = (BizHawkExternalToolUsageAttribute)attributes[0]; if(Global.Emulator.SystemId == "NULL" && attribute2.ToolUsage != BizHawkExternalToolUsage.Global) { item.ToolTipText = "This tool doesn't work if nothing is loaded"; item.Enabled = false; } else if(attribute2.ToolUsage == BizHawkExternalToolUsage.EmulatorSpecific && Global.Emulator.SystemId != ClientApi.SystemIdConverter.ConvertBack(attribute2.System)) { item.ToolTipText = "This tool doesn't work for current system"; item.Enabled = false; } else if (attribute2.ToolUsage == BizHawkExternalToolUsage.GameSpecific && Global.Game.Hash != attribute2.GameHash) { item.ToolTipText = "This tool doesn't work for current game"; item.Enabled = false; } } } else { item = new ToolStripMenuItem(externalToolFile.GetName().Name) { ToolTipText = "BizHawkExternalTool attribute hasn't been found", Enabled = false }; } } catch (BadImageFormatException) { item = new ToolStripMenuItem(fileName); item.ToolTipText = "This is not an assembly"; item.Enabled = false; } #if DEBUG //I added special debug stuff to get additional information. Don't think it can be useful for released versions catch (ReflectionTypeLoadException ex) { foreach (Exception e in ex.LoaderExceptions) { Debug.WriteLine(e.Message); } item.ToolTipText = "Something goes wrong while trying to load"; item.Enabled = false; } #else catch (ReflectionTypeLoadException) { item.ToolTipText = "Something goes wrong while trying to load"; item.Enabled = false; } #endif 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 } }