Revert "Merge pull request #960 from aldelaro5/metaplugins" (#995)

This reverts commit 2a61b61497, reversing
changes made to 6b38cee127.
This commit is contained in:
aldelaro5 authored and GitHub committed 2024-09-24 20:25:56 +02:00
1 parent d9b95a0ac7
commit 544f909a80
35 files changed
+257 -1199

No files matched your search

@@ -11,7 +11,6 @@ using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Mono.Cecil;
using MonoMod.Utils;
namespace BepInEx.Preloader.Core.Patching;
@@ -25,18 +24,9 @@ public class AssemblyPatcher : IDisposable
private Func<byte[], string, Assembly> assemblyLoader;
private readonly List<CachedPluginLoadContext> loadContexts = new();
private readonly List<BasePatcherProvider> providers = new();
public AssemblyPatcher(Func<byte[], string, Assembly> assemblyLoader)
{
this.assemblyLoader = assemblyLoader;
if (!Directory.Exists(Paths.PatcherProviderPath))
Directory.CreateDirectory(Paths.PatcherProviderPath);
if (!Directory.Exists(Paths.PatcherPluginPath))
Directory.CreateDirectory(Paths.PatcherPluginPath);
}
/// <summary>
@@ -71,18 +61,16 @@ public class AssemblyPatcher : IDisposable
// Clear to allow GC collection.
PatcherContext.PatcherPlugins.Clear();
AppDomain.CurrentDomain.AssemblyResolve -= PatcherProvidersAssemblyResolver;
}
private PatcherPluginMetadata ToPatcherPlugin<T>(TypeDefinition type, IPluginLoadContext loadContext, string assemblyPath)
private PatcherPluginMetadata ToPatcherPlugin(TypeDefinition type, string assemblyPath)
{
if (type.IsInterface || type.IsAbstract && !type.IsSealed)
return null;
try
{
if (!type.IsSubtypeOf(typeof(T)))
if (!type.IsSubtypeOf(typeof(BasePatcher)))
return null;
}
catch (AssemblyResolutionException)
@@ -91,13 +79,8 @@ public class AssemblyPatcher : IDisposable
return null;
}
if (type.IsSubtypeOf(typeof(BasePatcherProvider)))
return GetPatcherInfo(type, loadContext, PatcherProviderPluginInfoAttribute.FromCecilType(type));
return GetPatcherInfo(type, loadContext, PatcherPluginInfoAttribute.FromCecilType(type));
}
var metadata = PatcherPluginInfoAttribute.FromCecilType(type);
private PatcherPluginMetadata GetPatcherInfo(TypeDefinition type, IPluginLoadContext loadContext, PatcherPluginInfoAttribute metadata)
{
// Perform checks that will prevent the plugin from being loaded in ALL cases
if (metadata == null)
{
@@ -126,176 +109,20 @@ public class AssemblyPatcher : IDisposable
return new PatcherPluginMetadata
{
TypeName = type.FullName,
LoadContext = loadContext
TypeName = type.FullName
};
}
private bool HasPatcherType<T>(AssemblyDefinition ass)
private bool HasPatcherPlugins(AssemblyDefinition ass)
{
if (ass.MainModule.AssemblyReferences.All(r => r.Name != CurrentAssemblyName) &&
ass.Name.Name != CurrentAssemblyName)
return false;
if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(T).FullName))
if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(BasePatcher).FullName))
return false;
return true;
}
/// <summary>
/// Adds all patchers from all managed assemblies obtained from all patcher providers.
/// </summary>
public void AddPatchersFromProviders()
{
var allProviders = TypeLoader.FindPluginTypes(Paths.BepInExAssemblyDirectory, ToPatcherPlugin<BasePatcherProvider>, HasPatcherType<BasePatcherProvider>);
var foundProviders = TypeLoader.FindPluginTypes(Paths.PatcherProviderPath, ToPatcherPlugin<BasePatcherProvider>, HasPatcherType<BasePatcherProvider>);
allProviders.AddRange(foundProviders);
AppDomain.CurrentDomain.AssemblyResolve += PatcherProvidersAssemblyResolver;
foreach (var keyValuePair in allProviders)
{
var assemblyPath = keyValuePair.Key;
var patcherCollection = keyValuePair.Value;
if (patcherCollection.Count == 0)
continue;
var ass = Assembly.LoadFrom(assemblyPath);
foreach (var patcherPlugin in patcherCollection)
{
try
{
var type = ass.GetType(patcherPlugin.TypeName);
var instance = (BasePatcherProvider) Activator.CreateInstance(type);
providers.Add(instance);
loadContexts.AddRange(instance.GetPatchers().Select(x => new CachedPluginLoadContext(x)));
}
catch (Exception e)
{
Logger.Log(LogLevel.Error,
$"Failed to load patchers from type [{patcherPlugin.TypeName}]: {(e is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : e.ToString())}");
}
}
}
var patchers = TypeLoader.GetPluginsFromLoadContexts(loadContexts.Cast<IPluginLoadContext>(), ToPatcherPlugin<BasePatcher>, HasPatcherType<BasePatcher>);
var sortedPatchers = new List<PatchDefinition>();
// TODO: Add dependency ordering and process attribute filtering
foreach (var patcherPlugin in patchers)
{
var ass = Assembly.Load(patcherPlugin.LoadContext.GetAssemblyData());
var patchDefinitions = LoadPatcherPlugin(ass, patcherPlugin);
sortedPatchers.AddRange(patchDefinitions);
var assName = ass.GetName();
Logger.Log(LogLevel.Debug, $"Loaded patcher {patcherPlugin.TypeName} from [{assName.Name} {assName.Version}]");
}
PatcherContext.PatchDefinitions.AddRange(sortedPatchers);
Logger.Log(LogLevel.Info, $"Loaded {sortedPatchers.Count} patcher{(sortedPatchers.Count > 0 ? "s" : "")}");
foreach (CachedPluginLoadContext loadContext in loadContexts)
{
loadContext.Dispose();
}
}
private Assembly PatcherProvidersAssemblyResolver(object sender, ResolveEventArgs args)
{
BasePatcherProvider currentProvider = null;
try
{
foreach (BasePatcherProvider provider in providers)
{
currentProvider = provider;
var ass = provider.ResolveAssembly(args.Name);
if (ass != null)
return ass;
}
}
catch (Exception e)
{
Logger.Log(LogLevel.Error, $"The assembly resolver of the patcher provider [{currentProvider?.Info.GUID}] " +
$"threw an exception while resolving {args.Name}: {e}");
return null;
}
return null;
}
private List<PatchDefinition> LoadPatcherPlugin(Assembly ass, PatcherPluginMetadata patcher)
{
var patchDefinitions = new List<PatchDefinition>();
try
{
var type = ass.GetType(patcher.TypeName);
var instance = (BasePatcher) Activator.CreateInstance(type);
instance.Context = PatcherContext;
PatcherContext.PatcherPlugins.Add(instance);
var methods =
type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var method in methods)
{
var targetAssemblies = MetadataHelper.GetAttributes<TargetAssemblyAttribute>(method);
var targetTypes = MetadataHelper.GetAttributes<TargetTypeAttribute>(method);
if (targetAssemblies.Length == 0 && targetTypes.Length == 0)
continue;
var parameters = method.GetParameters();
if (parameters.Length < 1 || parameters.Length > 2
// Next few lines ensure that the first parameter is AssemblyDefinition and does not have any
// target type attributes, and vice versa
|| !(
parameters[0].ParameterType == typeof(AssemblyDefinition)
|| parameters[0].ParameterType ==
typeof(AssemblyDefinition).MakeByRefType()
&& targetTypes.Length == 0
|| parameters[0].ParameterType == typeof(TypeDefinition)
&& targetAssemblies.Length == 0
)
|| parameters.Length == 2 &&
parameters[1].ParameterType != typeof(string)
|| method.ReturnType != typeof(void) &&
method.ReturnType != typeof(bool)
)
{
Logger
.Log(LogLevel.Warning,
$"Skipping method [{method.FullDescription()}] as it is not a valid patcher method");
continue;
}
void AddDefinition(PatchDefinition definition)
{
Logger.Log(LogLevel.Debug, $"Discovered patch [{definition.FullName}]");
patchDefinitions.Add(definition);
}
foreach (var targetAssembly in targetAssemblies)
AddDefinition(new PatchDefinition(targetAssembly, instance, method));
foreach (var targetType in targetTypes)
AddDefinition(new PatchDefinition(targetType, instance, method));
}
}
catch (Exception e)
{
Logger.Log(LogLevel.Error,
$"Failed to load patchers from type [{patcher.TypeName}]: {(e is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : e.ToString())}");
}
return patchDefinitions;
}
/// <summary>
/// Adds all patchers from all managed assemblies specified in a directory.
@@ -308,7 +135,7 @@ public class AssemblyPatcher : IDisposable
var sortedPatchers = new List<PatchDefinition>();
var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin<BasePatcher>, HasPatcherType<BasePatcher>);
var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin, HasPatcherPlugins);
// TODO: Add dependency ordering and process attribute filtering
@@ -323,10 +150,68 @@ public class AssemblyPatcher : IDisposable
var ass = Assembly.LoadFrom(assemblyPath);
foreach (var patcherPlugin in patcherCollection)
{
var patchDefinitions = LoadPatcherPlugin(ass, patcherPlugin);
sortedPatchers.AddRange(patchDefinitions);
}
try
{
var type = ass.GetType(patcherPlugin.TypeName);
var instance = (BasePatcher) Activator.CreateInstance(type);
instance.Context = PatcherContext;
PatcherContext.PatcherPlugins.Add(instance);
var methods =
type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var method in methods)
{
var targetAssemblies = MetadataHelper.GetAttributes<TargetAssemblyAttribute>(method);
var targetTypes = MetadataHelper.GetAttributes<TargetTypeAttribute>(method);
if (targetAssemblies.Length == 0 && targetTypes.Length == 0)
continue;
var parameters = method.GetParameters();
if (parameters.Length < 1 || parameters.Length > 2
// Next few lines ensure that the first parameter is AssemblyDefinition and does not have any
// target type attributes, and vice versa
|| !(
parameters[0].ParameterType == typeof(AssemblyDefinition)
|| parameters[0].ParameterType ==
typeof(AssemblyDefinition).MakeByRefType()
&& targetTypes.Length == 0
|| parameters[0].ParameterType == typeof(TypeDefinition)
&& targetAssemblies.Length == 0
)
|| parameters.Length == 2 &&
parameters[1].ParameterType != typeof(string)
|| method.ReturnType != typeof(void) &&
method.ReturnType != typeof(bool)
)
{
Logger
.Log(LogLevel.Warning,
$"Skipping method [{method.FullDescription()}] as it is not a valid patcher method");
continue;
}
void AddDefinition(PatchDefinition definition)
{
Logger.Log(LogLevel.Debug, $"Discovered patch [{definition.FullName}]");
sortedPatchers.Add(definition);
}
foreach (var targetAssembly in targetAssemblies)
AddDefinition(new PatchDefinition(targetAssembly, instance, method));
foreach (var targetType in targetTypes)
AddDefinition(new PatchDefinition(targetType, instance, method));
}
}
catch (Exception e)
{
Logger.Log(LogLevel.Error,
$"Failed to load patchers from type [{patcherPlugin.TypeName}]: {(e is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : e.ToString())}");
}
var assName = ass.GetName();
Logger.Log(patcherCollection.Any() ? LogLevel.Info : LogLevel.Debug,
@@ -79,31 +79,6 @@ public class PatcherPluginInfoAttribute : Attribute
}
}
/// <summary>
/// This attribute denotes that a class is a patcher plugin provider, and specifies the required metadata.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class PatcherProviderPluginInfoAttribute : PatcherPluginInfoAttribute {
/// <param name="GUID">The unique identifier of the plugin. Should not change between plugin versions.</param>
/// <param name="Name">The user friendly name of the plugin. Is able to be changed between versions.</param>
/// <param name="Version">The specific version of the plugin.</param>
public PatcherProviderPluginInfoAttribute(string GUID, string Name, string Version) : base(GUID, Name, Version)
{
}
internal new static PatcherProviderPluginInfoAttribute FromCecilType(TypeDefinition td)
{
var attr = MetadataHelper.GetCustomAttributes<PatcherProviderPluginInfoAttribute>(td, false).FirstOrDefault();
if (attr == null)
return null;
return new PatcherProviderPluginInfoAttribute((string) attr.ConstructorArguments[0].Value,
(string) attr.ConstructorArguments[1].Value,
(string) attr.ConstructorArguments[2].Value);
}
}
/// <summary>
/// Defines an assembly that a patch method will target.
/// </summary>
@@ -1,55 +0,0 @@
using System.Collections.Generic;
using System.Reflection;
using BepInEx.Configuration;
using BepInEx.Logging;
namespace BepInEx.Preloader.Core.Patching;
/// <summary>
/// A patcher provider that can discover and load assemblies containing patcher plugins
/// </summary>
public abstract class BasePatcherProvider
{
protected BasePatcherProvider()
{
Info = PatcherPluginInfoAttribute.FromType(GetType());
Log = Logger.CreateLogSource(Info.Name);
Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, Info.GUID + ".cfg"), false,
new BepInPlugin(Info.GUID, Info.Name, Info.Version.ToString()));
}
/// <summary>
/// A <see cref="ILogSource" /> instance created for use by this patcher plugin provider.
/// </summary>
public ManualLogSource Log { get; }
/// <summary>
/// A configuration file binding created with the <see cref="PatcherPluginInfoAttribute.GUID" /> of this provider as the
/// filename.
/// </summary>
public ConfigFile Config { get; }
/// <summary>
/// Metadata associated with this patcher plugin provider.
/// </summary>
public PatcherPluginInfoAttribute Info { get; }
/// <summary>
/// Obtains a list of assemblies containing patchers to load
/// </summary>
/// <returns>A list of load context, one per assembly</returns>
public abstract IList<IPluginLoadContext> GetPatchers();
/// <summary>
/// A custom assembly resolver that can be used by this provider to resolve assemblies
/// that have failed to resolve
/// </summary>
/// <param name="name">The assembly's name</param>
/// <returns>The resolved assembly or null if the assembly couldn't be resolved</returns>
public virtual Assembly ResolveAssembly(string name)
{
return null;
}
}
@@ -13,11 +13,6 @@ internal class PatcherPluginMetadata : ICacheable
/// </summary>
public string TypeName { get; set; } = string.Empty;
/// <summary>
/// The load context used to load this patcher, null if it is a provider
/// </summary>
public IPluginLoadContext LoadContext { get; set; }
/// <inheritdoc />
public void Save(BinaryWriter bw) => bw.Write(TypeName);