using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using static PKHeX.WinForms.PluginLoadSetting; namespace PKHeX.WinForms; public static class PluginLoader { public static IEnumerable LoadPlugins(string pluginPath, PluginLoadSetting loadSetting) where T : class { var dllFileNames = !Directory.Exists(pluginPath) ? Array.Empty() // Don't immediately return, as we may be loading plugins merged with this .exe : Directory.EnumerateFiles(pluginPath, "*.dll", SearchOption.AllDirectories); var assemblies = GetAssemblies(dllFileNames, loadSetting); var pluginTypes = GetPluginsOfType(assemblies); return LoadPlugins(pluginTypes); } private static IEnumerable LoadPlugins(IEnumerable pluginTypes) where T : class { foreach (var t in pluginTypes) { T? activate; try { activate = (T?)Activator.CreateInstance(t); } catch (Exception ex) { Debug.WriteLine($"Unable to load plugin [{t.Name}]: {t.FullName}"); Debug.WriteLine(ex.Message); continue; } if (activate != null) yield return activate; } } private static IEnumerable GetAssemblies(IEnumerable dllFileNames, PluginLoadSetting loadSetting) { var loadMethod = GetPluginLoadMethod(loadSetting); foreach (var file in dllFileNames) { Assembly x; try { x = loadMethod(file); } catch (Exception ex) { Debug.WriteLine($"Unable to load plugin from file: {file}"); Debug.WriteLine(ex.Message); continue; } yield return x; } if (loadSetting.IsMerged()) yield return Assembly.GetExecutingAssembly(); // load merged too } private static Func GetPluginLoadMethod(PluginLoadSetting pls) => pls switch { LoadFrom or LoadFromMerged => Assembly.LoadFrom, LoadFile or LoadFileMerged => Assembly.LoadFile, UnsafeLoadFrom or UnsafeMerged => Assembly.UnsafeLoadFrom, _ => throw new IndexOutOfRangeException($"PluginLoadSetting: {pls} method not defined."), }; public static bool IsMerged(this PluginLoadSetting loadSetting) => loadSetting is LoadFromMerged or LoadFileMerged or UnsafeMerged; private static IEnumerable GetPluginsOfType(IEnumerable assemblies) { var interfaceTypeName = typeof(T).FullName; if (interfaceTypeName is null) return Array.Empty(); return assemblies.SelectMany(z => GetPluginTypes(z, interfaceTypeName)); } private static IEnumerable GetPluginTypes(Assembly z, string interfaceTypeName) { try { // Handle Costura merged plugin dll's; need to Attach for them to correctly retrieve their dependencies. var assemblyLoaderType = z.GetType("Costura.AssemblyLoader", false); var attachMethod = assemblyLoaderType?.GetMethod("Attach", BindingFlags.Static | BindingFlags.Public); attachMethod?.Invoke(null, Array.Empty()); var types = z.GetTypes(); return types.Where(type => IsTypePlugin(type, interfaceTypeName)); } // User plugins can be out of date, with mismatching API surfaces. catch (Exception ex) { Debug.WriteLine($"Unable to load plugin [{interfaceTypeName}]: {z.FullName}"); Debug.WriteLine(ex.Message); if (ex is not ReflectionTypeLoadException rtle) return Array.Empty(); foreach (var le in rtle.LoaderExceptions) { if (le is not null) Debug.WriteLine(le.Message); } return Array.Empty(); } } private static bool IsTypePlugin(Type type, string interfaceTypeName) { if (type.IsInterface || type.IsAbstract) return false; if (type.GetInterface(interfaceTypeName) == null) return false; return true; } }