mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-09-08 17:07:00 -05:00
Merge branch 'master' of https://github.com/kwsch/PKHeX
This commit is contained in:
@@ -47,7 +47,7 @@ public Main()
|
||||
startup.ReadSettings(Settings.Startup);
|
||||
startup.ReadTemplateIfNoEntity(TemplatePath);
|
||||
|
||||
if (Settings.Startup.PluginLoadMethod != PluginLoadSetting.DontLoad)
|
||||
if (Settings.Startup.PluginLoadEnable)
|
||||
FormLoadPlugins();
|
||||
|
||||
FormLoadInitialFiles(startup);
|
||||
@@ -274,7 +274,7 @@ private void FormLoadPlugins()
|
||||
#endif
|
||||
try
|
||||
{
|
||||
Plugins.AddRange(PluginLoader.LoadPlugins<IPlugin>(PluginPath, Settings.Startup.PluginLoadMethod));
|
||||
Plugins.AddRange(PluginLoader.LoadPlugins<IPlugin>(PluginPath, Settings.Startup.PluginLoadMerged));
|
||||
}
|
||||
catch (InvalidCastException c)
|
||||
{
|
||||
|
||||
@@ -4,22 +4,67 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using static PKHeX.WinForms.PluginLoadSetting;
|
||||
using System.Runtime.Loader;
|
||||
|
||||
namespace PKHeX.WinForms;
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality to load plugins from assemblies at runtime.
|
||||
/// </summary>
|
||||
public static class PluginLoader
|
||||
{
|
||||
public static IEnumerable<T> LoadPlugins<T>(string pluginPath, PluginLoadSetting loadSetting) where T : class
|
||||
/// <summary>
|
||||
/// Loads plugin assemblies from the given directory using the provided load setting.
|
||||
/// </summary>
|
||||
/// <param name="pluginPath">The directory path to search for plugin assemblies.</param>
|
||||
/// <param name="loadMerged">The plugin load setting to use.</param>
|
||||
/// <returns>A PluginLoadResult containing contexts and assemblies.</returns>
|
||||
public static PluginLoadResult LoadPluginAssemblies(string pluginPath, bool loadMerged)
|
||||
{
|
||||
var result = new PluginLoadResult();
|
||||
var dllFileNames = !Directory.Exists(pluginPath)
|
||||
? [] // 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<T>(assemblies);
|
||||
foreach (var file in dllFileNames)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = new PluginLoadContext(file);
|
||||
var asm = context.LoadFromAssemblyPath(file);
|
||||
result.Contexts.Add(context);
|
||||
result.Assemblies.Add(asm);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Unable to load plugin from file: {file}");
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
if (loadMerged)
|
||||
result.Assemblies.Add(Assembly.GetExecutingAssembly());
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads plugins of the specified type from the given directory using the provided load setting.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of plugin to load.</typeparam>
|
||||
/// <param name="pluginPath">The directory path to search for plugin assemblies.</param>
|
||||
/// <param name="loadMerged">The plugin load setting to use.</param>
|
||||
/// <returns>An enumerable of loaded plugin instances of type <typeparamref name="T"/>.</returns>
|
||||
public static IEnumerable<T> LoadPlugins<T>(string pluginPath, bool loadMerged) where T : class
|
||||
{
|
||||
var result = LoadPluginAssemblies(pluginPath, loadMerged);
|
||||
var pluginTypes = GetPluginsOfType<T>(result.GetAssemblies());
|
||||
return LoadPlugins<T>(pluginTypes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads plugin instances of the specified type from the given plugin types.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of plugin to load.</typeparam>
|
||||
/// <param name="pluginTypes">The types of plugins to instantiate.</param>
|
||||
/// <returns>An enumerable of loaded plugin instances of type <typeparamref name="T"/>.</returns>
|
||||
private static IEnumerable<T> LoadPlugins<T>(IEnumerable<Type> pluginTypes) where T : class
|
||||
{
|
||||
foreach (var t in pluginTypes)
|
||||
@@ -37,41 +82,24 @@ public static class PluginLoader
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Assembly> GetAssemblies(IEnumerable<string> 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<string, Assembly> 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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all plugin types of the specified type from the given assemblies.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of plugin to search for.</typeparam>
|
||||
/// <param name="assemblies">The assemblies to search for plugins.</param>
|
||||
/// <returns>An enumerable of plugin types.</returns>
|
||||
private static IEnumerable<Type> GetPluginsOfType<T>(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
var pluginType = typeof(T);
|
||||
return assemblies.SelectMany(z => GetPluginTypes(z, pluginType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all types from the specified assembly that match the given plugin type.
|
||||
/// </summary>
|
||||
/// <param name="z">The assembly to search.</param>
|
||||
/// <param name="plugin">The plugin type to match.</param>
|
||||
/// <returns>An enumerable of matching types.</returns>
|
||||
private static IEnumerable<Type> GetPluginTypes(Assembly z, Type plugin)
|
||||
{
|
||||
try
|
||||
@@ -101,6 +129,12 @@ private static IEnumerable<Type> GetPluginTypes(Assembly z, Type plugin)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified type is a valid plugin type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check.</param>
|
||||
/// <param name="plugin">The plugin type to match.</param>
|
||||
/// <returns><c>true</c> if the type is a valid plugin type; otherwise, <c>false</c>.</returns>
|
||||
private static bool IsTypePlugin(Type type, Type plugin)
|
||||
{
|
||||
if (type.IsInterface || type.IsAbstract)
|
||||
@@ -108,3 +142,58 @@ private static bool IsTypePlugin(Type type, Type plugin)
|
||||
return plugin.IsAssignableFrom(type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates the result of loading plugins, including their contexts and assemblies.
|
||||
/// </summary>
|
||||
public class PluginLoadResult
|
||||
{
|
||||
public List<PluginLoadContext> Contexts { get; } = new();
|
||||
public List<Assembly> Assemblies { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Returns all loaded assemblies for downstream use.
|
||||
/// </summary>
|
||||
public IEnumerable<Assembly> GetAssemblies() => Assemblies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom AssemblyLoadContext for loading plugin assemblies in isolation.
|
||||
/// </summary>
|
||||
public class PluginLoadContext : AssemblyLoadContext
|
||||
{
|
||||
private readonly AssemblyDependencyResolver Resolver;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginLoadContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pluginPath">The path to the plugin assembly.</param>
|
||||
public PluginLoadContext(string pluginPath) : base(isCollectible: true)
|
||||
{
|
||||
Resolver = new AssemblyDependencyResolver(pluginPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the main plugin assembly from the specified path. Delegates framework assemblies to the default context.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">The assembly name to load.</param>
|
||||
/// <returns>The loaded assembly, or null if not the main plugin assembly.</returns>
|
||||
protected override Assembly? Load(AssemblyName assemblyName)
|
||||
{
|
||||
// Try to resolve plugin-local dependencies
|
||||
var assemblyPath = Resolver.ResolveAssemblyToPath(assemblyName);
|
||||
if (assemblyPath != null)
|
||||
return LoadFromAssemblyPath(assemblyPath);
|
||||
|
||||
// Fallback: try to resolve from the default context (main app/shared dependencies)
|
||||
try
|
||||
{
|
||||
return Default.LoadFromAssemblyName(assemblyName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Not found in default context
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,8 +139,11 @@ public sealed class StartupSettings : IStartupSettings
|
||||
[LocalizedDescription("Show the changelog when a new version of the program is run for the first time.")]
|
||||
public bool ShowChangelogOnUpdate { get; set; } = true;
|
||||
|
||||
[LocalizedDescription("Loads plugins from the plugins folder, assuming the folder exists. Try LoadFile to mitigate intermittent load failures.")]
|
||||
public PluginLoadSetting PluginLoadMethod { get; set; } = PluginLoadSetting.LoadFrom;
|
||||
[LocalizedDescription("Loads plugins from the plugins folder, assuming the folder exists.")]
|
||||
public bool PluginLoadEnable { get; set; } = true;
|
||||
|
||||
[LocalizedDescription("Loads any plugins that were merged into the main executable file.")]
|
||||
public bool PluginLoadMerged { get; set; }
|
||||
|
||||
[Browsable(false)]
|
||||
public List<string> RecentlyLoaded { get; set; } = new(DefaultMaxRecent);
|
||||
@@ -203,17 +206,6 @@ public void LoadSaveFile(string path)
|
||||
}
|
||||
}
|
||||
|
||||
public enum PluginLoadSetting
|
||||
{
|
||||
DontLoad,
|
||||
LoadFrom,
|
||||
LoadFile,
|
||||
UnsafeLoadFrom,
|
||||
LoadFromMerged,
|
||||
LoadFileMerged,
|
||||
UnsafeMerged,
|
||||
}
|
||||
|
||||
public sealed class EntityConverterSettings
|
||||
{
|
||||
[LocalizedDescription("Allow PKM file conversion paths that are not possible via official methods. Individual properties will be copied sequentially.")]
|
||||
|
||||
Reference in New Issue
Block a user