mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-20 01:46:13 +00:00
This reverts commit2a61b61497, reversing changes made to6b38cee127.
This commit is contained in:
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);
|
||||
|
||||
|
||||
Reference in new issue
Block a user