|
@@ -4,6 +4,7 @@ using System.Diagnostics;
|
|
|
using System.IO;
|
|
|
using System.Linq;
|
|
|
using System.Reflection;
|
|
|
+using BepInEx.Bootstrap;
|
|
|
using BepInEx.Configuration;
|
|
|
using BepInEx.Logging;
|
|
|
using BepInEx.Preloader.RuntimeFixes;
|
|
@@ -23,6 +24,8 @@ namespace BepInEx.Preloader.Patching
|
|
|
/// </summary>
|
|
|
internal static class AssemblyPatcher
|
|
|
{
|
|
|
+ private const BindingFlags ALL = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.IgnoreCase;
|
|
|
+
|
|
|
public static List<PatcherPlugin> PatcherPlugins { get; } = new List<PatcherPlugin>();
|
|
|
|
|
|
private static readonly string DumpedAssembliesPath = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
|
|
@@ -36,32 +39,83 @@ namespace BepInEx.Preloader.Patching
|
|
|
PatcherPlugins.Add(patcher);
|
|
|
}
|
|
|
|
|
|
+ private static T CreateDelegate<T>(MethodInfo method) where T : class => method != null ? Delegate.CreateDelegate(typeof(T), method) as T : null;
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Adds all patchers from all managed assemblies specified in a directory.
|
|
|
/// </summary>
|
|
|
/// <param name="directory">Directory to search patcher DLLs from.</param>
|
|
|
/// <param name="patcherLocator">A function that locates assembly patchers in a given managed assembly.</param>
|
|
|
public static void AddPatchersFromDirectory(string directory,
|
|
|
- Func<Assembly, List<PatcherPlugin>> patcherLocator)
|
|
|
+ Func<TypeDefinition, PatcherPlugin> patcherLocator)
|
|
|
{
|
|
|
if (!Directory.Exists(directory))
|
|
|
return;
|
|
|
|
|
|
var sortedPatchers = new SortedDictionary<string, PatcherPlugin>();
|
|
|
|
|
|
- foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories))
|
|
|
- try
|
|
|
+ var patchers = TypeLoader.FindPluginTypes(directory, patcherLocator);
|
|
|
+
|
|
|
+ foreach (var keyValuePair in patchers)
|
|
|
+ {
|
|
|
+ var assembly = keyValuePair.Key;
|
|
|
+ var patcherCollection = keyValuePair.Value;
|
|
|
+
|
|
|
+ var ass = Assembly.LoadFile(assembly.MainModule.FileName);
|
|
|
+
|
|
|
+ foreach (var patcherPlugin in patcherCollection)
|
|
|
{
|
|
|
- var assembly = Assembly.LoadFrom(assemblyPath);
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var type = ass.GetType(patcherPlugin.Type.FullName);
|
|
|
+
|
|
|
+ var methods = type.GetMethods(ALL);
|
|
|
+
|
|
|
+ patcherPlugin.Initializer = CreateDelegate<Action>(methods.FirstOrDefault(m => m.Name.Equals("Initialize", StringComparison.InvariantCultureIgnoreCase) &&
|
|
|
+ m.GetParameters().Length == 0 &&
|
|
|
+ m.ReturnType == typeof(void)));
|
|
|
+
|
|
|
+ patcherPlugin.Finalizer = CreateDelegate<Action>(methods.FirstOrDefault(m => m.Name.Equals("Finish", StringComparison.InvariantCultureIgnoreCase) &&
|
|
|
+ m.GetParameters().Length == 0 &&
|
|
|
+ m.ReturnType == typeof(void)));
|
|
|
+
|
|
|
+ patcherPlugin.TargetDLLs = CreateDelegate<Func<IEnumerable<string>>>(type.GetProperty("TargetDLLs", ALL).GetGetMethod());
|
|
|
|
|
|
- foreach (var patcher in patcherLocator(assembly))
|
|
|
- sortedPatchers.Add(patcher.Name, patcher);
|
|
|
+ var patcher = methods.FirstOrDefault(m => m.Name.Equals("Patch", StringComparison.CurrentCultureIgnoreCase) &&
|
|
|
+ m.ReturnType == typeof(void) &&
|
|
|
+ m.GetParameters().Length == 0 &&
|
|
|
+ (m.GetParameters()[0].ParameterType == typeof(AssemblyDefinition) ||
|
|
|
+ m.GetParameters()[0].ParameterType == typeof(AssemblyDefinition).MakeByRefType()));
|
|
|
+
|
|
|
+ patcherPlugin.Patcher = (ref AssemblyDefinition pAss) =>
|
|
|
+ {
|
|
|
+ //we do the array fuckery here to get the ref result out
|
|
|
+ object[] args = { pAss };
|
|
|
+
|
|
|
+ patcher.Invoke(null, args);
|
|
|
+
|
|
|
+ pAss = (AssemblyDefinition)args[0];
|
|
|
+ };
|
|
|
+
|
|
|
+ sortedPatchers.Add($"{ass.GetName().Name}/{type.FullName}", patcherPlugin);
|
|
|
+ patcherPlugin.Type = null;
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ Logger.LogError($"Failed to load patcher [{patcherPlugin.Type.FullName}]: {e.Message}");
|
|
|
+ if (e is ReflectionTypeLoadException re)
|
|
|
+ Logger.LogDebug(TypeLoader.TypeLoadExceptionToString(re));
|
|
|
+ else
|
|
|
+ Logger.LogDebug(e.ToString());
|
|
|
+ }
|
|
|
}
|
|
|
- catch (BadImageFormatException) { } //unmanaged DLL
|
|
|
- catch (ReflectionTypeLoadException) { } //invalid references
|
|
|
+ }
|
|
|
|
|
|
foreach (KeyValuePair<string, PatcherPlugin> patcher in sortedPatchers)
|
|
|
AddPatcher(patcher.Value);
|
|
|
+
|
|
|
+ foreach (var assemblyDefinition in patchers.Keys)
|
|
|
+ assemblyDefinition.Dispose();
|
|
|
}
|
|
|
|
|
|
private static void InitializePatchers()
|
|
@@ -123,7 +177,7 @@ namespace BepInEx.Preloader.Patching
|
|
|
// Then, perform the actual patching
|
|
|
var patchedAssemblies = new HashSet<string>();
|
|
|
foreach (var assemblyPatcher in PatcherPlugins)
|
|
|
- foreach (string targetDll in assemblyPatcher.TargetDLLs)
|
|
|
+ foreach (string targetDll in assemblyPatcher.TargetDLLs())
|
|
|
if (assemblies.TryGetValue(targetDll, out var assembly))
|
|
|
{
|
|
|
Logger.LogInfo($"Patching [{assembly.Name.Name}] with [{assemblyPatcher.Name}]");
|
|
@@ -135,7 +189,6 @@ namespace BepInEx.Preloader.Patching
|
|
|
|
|
|
|
|
|
// Finally, load patched assemblies into memory
|
|
|
-
|
|
|
if (ConfigDumpAssemblies.Value || ConfigLoadDumpedAssemblies.Value)
|
|
|
{
|
|
|
if (!Directory.Exists(DumpedAssembliesPath))
|