mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-19 15:51:45 +00:00
Base implementation for v2 patcher plugin contract
This commit is contained in:
8 files changed
+400
-92
No files matched your search
@@ -48,8 +48,9 @@
|
||||
<Compile Include="EnvVars.cs" />
|
||||
<Compile Include="Patching\AssemblyPatcher.cs" />
|
||||
<Compile Include="Entrypoint.cs" />
|
||||
<Compile Include="Patching\Attributes.cs" />
|
||||
<Compile Include="Patching\BasePatcher.cs" />
|
||||
<Compile Include="Patching\PatcherPlugin.cs" />
|
||||
<Compile Include="Patching\PatchContext.cs" />
|
||||
<Compile Include="RuntimeFixes\TraceFix.cs" />
|
||||
<Compile Include="Preloader.cs" />
|
||||
<Compile Include="Logger\PreloaderLogWriter.cs" />
|
||||
@@ -73,5 +74,6 @@
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using BepInEx.Bootstrap;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Logging;
|
||||
@@ -26,22 +27,14 @@ namespace BepInEx.Preloader.Patching
|
||||
{
|
||||
private const BindingFlags ALL = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.IgnoreCase;
|
||||
|
||||
public static List<PatcherPlugin> PatcherPlugins { get; } = new List<PatcherPlugin>();
|
||||
public static List<LegacyPatcherPlugin> LegacyPatcherPlugins { get; } = new List<LegacyPatcherPlugin>();
|
||||
public static List<PatcherDefinition> PatcherPlugins { get; } = new List<PatcherDefinition>();
|
||||
|
||||
private static readonly string DumpedAssembliesPath = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single assembly patcher to the pool of applicable patches.
|
||||
/// </summary>
|
||||
/// <param name="patcher">Patcher to apply.</param>
|
||||
public static void AddPatcher(PatcherPlugin patcher)
|
||||
{
|
||||
PatcherPlugins.Add(patcher);
|
||||
}
|
||||
|
||||
private static T CreateDelegate<T>(MethodInfo method) where T : class => method != null ? Delegate.CreateDelegate(typeof(T), method) as T : null;
|
||||
|
||||
private static PatcherPlugin ToPatcherPlugin(TypeDefinition type)
|
||||
private static LegacyPatcherPlugin ToLegacyPatcherPlugin(TypeDefinition type)
|
||||
{
|
||||
if (type.IsInterface || type.IsAbstract && !type.IsSealed)
|
||||
return null;
|
||||
@@ -65,7 +58,30 @@ namespace BepInEx.Preloader.Patching
|
||||
if (patch == null)
|
||||
return null;
|
||||
|
||||
return new PatcherPlugin
|
||||
return new LegacyPatcherPlugin
|
||||
{
|
||||
TypeName = type.FullName
|
||||
};
|
||||
}
|
||||
|
||||
private static Regex allowedGuidRegex { get; } = new Regex(@"^[a-zA-Z0-9\._\-]+$");
|
||||
private static PatcherDefinition ToPatcherPlugin(TypeDefinition type)
|
||||
{
|
||||
if (type.IsInterface || type.IsAbstract)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
if (!type.IsSubtypeOf(typeof(BasePatcher)))
|
||||
return null;
|
||||
}
|
||||
catch (AssemblyResolutionException)
|
||||
{
|
||||
// Can happen if this type inherits a type from an assembly that can't be found. Safe to assume it's not a plugin.
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PatcherDefinition
|
||||
{
|
||||
TypeName = type.FullName
|
||||
};
|
||||
@@ -81,25 +97,24 @@ namespace BepInEx.Preloader.Patching
|
||||
if (!Directory.Exists(directory))
|
||||
return;
|
||||
|
||||
var sortedPatchers = new SortedDictionary<string, PatcherPlugin>();
|
||||
var legacyPatchers = TypeLoader.FindPluginTypes(directory, ToLegacyPatcherPlugin);
|
||||
var patchers = TypeLoader.FindPluginTypesCacheless(directory, ToPatcherPlugin);
|
||||
|
||||
var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin);
|
||||
|
||||
foreach (var keyValuePair in patchers)
|
||||
foreach (var keyValuePair in legacyPatchers)
|
||||
{
|
||||
var assemblyPath = keyValuePair.Key;
|
||||
var patcherCollection = keyValuePair.Value;
|
||||
|
||||
if(patcherCollection.Count == 0)
|
||||
if (patcherCollection.Count == 0)
|
||||
continue;
|
||||
|
||||
var ass = Assembly.LoadFile(assemblyPath);
|
||||
var assembly = Assembly.LoadFile(assemblyPath);
|
||||
|
||||
foreach (var patcherPlugin in patcherCollection)
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = ass.GetType(patcherPlugin.TypeName);
|
||||
var type = assembly.GetType(patcherPlugin.TypeName);
|
||||
|
||||
var methods = type.GetMethods(ALL);
|
||||
|
||||
@@ -129,7 +144,7 @@ namespace BepInEx.Preloader.Patching
|
||||
pAss = (AssemblyDefinition)args[0];
|
||||
};
|
||||
|
||||
sortedPatchers.Add($"{ass.GetName().Name}/{type.FullName}", patcherPlugin);
|
||||
LegacyPatcherPlugins.Add(patcherPlugin);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -142,23 +157,111 @@ namespace BepInEx.Preloader.Patching
|
||||
}
|
||||
|
||||
Logger.Log(patcherCollection.Any() ? LogLevel.Info : LogLevel.Debug,
|
||||
$"Loaded {patcherCollection.Count} patcher methods from {ass.GetName().FullName}");
|
||||
$"Loaded {patcherCollection.Count} legacy patcher methods from {assembly.GetName().FullName}");
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, PatcherPlugin> patcher in sortedPatchers)
|
||||
AddPatcher(patcher.Value);
|
||||
foreach (var keyValuePair in patchers)
|
||||
{
|
||||
var assemblyPath = keyValuePair.Key;
|
||||
var patcherCollection = keyValuePair.Value;
|
||||
|
||||
if (patcherCollection.Count == 0)
|
||||
continue;
|
||||
|
||||
var assembly = Assembly.LoadFile(assemblyPath);
|
||||
|
||||
foreach (var patcherPlugin in patcherCollection)
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = assembly.GetType(patcherPlugin.TypeName);
|
||||
|
||||
patcherPlugin.PatcherInfo = (PatcherInfo)type.GetCustomAttributes(typeof(PatcherInfo), false).FirstOrDefault();
|
||||
|
||||
if (patcherPlugin.PatcherInfo == null)
|
||||
{
|
||||
Logger.LogWarning($"Skipping over type [{type.FullName}] as no metadata attribute is specified");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(patcherPlugin.PatcherInfo.GUID) || !allowedGuidRegex.IsMatch(patcherPlugin.PatcherInfo.GUID))
|
||||
{
|
||||
Logger.LogWarning($"Skipping type [{type.FullName}] because its GUID [{patcherPlugin.PatcherInfo.GUID}] is of an illegal format.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (patcherPlugin.PatcherInfo.Version == null)
|
||||
{
|
||||
Logger.LogWarning($"Skipping type [{type.FullName}] because its version is invalid.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (patcherPlugin.PatcherInfo.Name == null)
|
||||
{
|
||||
Logger.LogWarning($"Skipping type [{type.FullName}] because its name is null.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var methods = type.GetMethods(ALL);
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var parameters = method.GetParameters();
|
||||
|
||||
if (parameters.Length == 1 && parameters[0].ParameterType == typeof(AssemblyDefinition))
|
||||
{
|
||||
var targetAssemblyAttributes = method.GetCustomAttributes(typeof(TargetAssemblyAttribute), false);
|
||||
|
||||
foreach (var attribute in targetAssemblyAttributes)
|
||||
{
|
||||
patcherPlugin.AssemblyDefinitionPatchers[(TargetAssemblyAttribute)attribute] = method;
|
||||
}
|
||||
}
|
||||
else if (parameters.Length == 1 && parameters[0].ParameterType == typeof(TypeDefinition))
|
||||
{
|
||||
var targetClassAttributes = method.GetCustomAttributes(typeof(TargetClassAttribute), false);
|
||||
|
||||
foreach (var attribute in targetClassAttributes)
|
||||
{
|
||||
patcherPlugin.TypeDefinitionPatchers[(TargetClassAttribute)attribute] = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
patcherPlugin.Instance = (BasePatcher)Activator.CreateInstance(type);
|
||||
|
||||
Logger.LogInfo($"Loaded patcher [{patcherPlugin.PatcherInfo.Name} {patcherPlugin.PatcherInfo.Version}]");
|
||||
|
||||
PatcherPlugins.Add(patcherPlugin);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Failed to load patcher [{patcherPlugin.TypeName}]: {e.Message}");
|
||||
if (e is ReflectionTypeLoadException re)
|
||||
Logger.LogDebug(TypeLoader.TypeLoadExceptionToString(re));
|
||||
else
|
||||
Logger.LogDebug(e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitializePatchers()
|
||||
private static void InitializePatchers(PatchContext context)
|
||||
{
|
||||
foreach (var assemblyPatcher in PatcherPlugins)
|
||||
foreach (var assemblyPatcher in LegacyPatcherPlugins)
|
||||
assemblyPatcher.Initializer?.Invoke();
|
||||
|
||||
foreach (var patcher in PatcherPlugins)
|
||||
patcher.Instance.Setup(context);
|
||||
}
|
||||
|
||||
private static void FinalizePatching()
|
||||
{
|
||||
foreach (var assemblyPatcher in PatcherPlugins)
|
||||
foreach (var assemblyPatcher in LegacyPatcherPlugins)
|
||||
assemblyPatcher.Finalizer?.Invoke();
|
||||
|
||||
foreach (var patcher in PatcherPlugins)
|
||||
patcher.Instance.Finalize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -166,7 +269,7 @@ namespace BepInEx.Preloader.Patching
|
||||
/// </summary>
|
||||
public static void DisposePatchers()
|
||||
{
|
||||
PatcherPlugins.Clear();
|
||||
LegacyPatcherPlugins.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -176,7 +279,8 @@ namespace BepInEx.Preloader.Patching
|
||||
public static void PatchAndLoad(string directory)
|
||||
{
|
||||
// First, load patchable assemblies into Cecil
|
||||
var assemblies = new Dictionary<string, AssemblyDefinition>();
|
||||
var assemblies = new List<AssemblyDefinition>();
|
||||
var filenameDictionary = new Dictionary<string, AssemblyDefinition>();
|
||||
|
||||
foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
|
||||
{
|
||||
@@ -197,30 +301,82 @@ namespace BepInEx.Preloader.Patching
|
||||
Logger.LogWarning($"Tried to load duplicate assembly {Path.GetFileName(assemblyPath)} from Managed folder! Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
assemblies.Add(Path.GetFileName(assemblyPath), assembly);
|
||||
|
||||
assemblies.Add(assembly);
|
||||
filenameDictionary[Path.GetFileName(assemblyPath)] = assembly;
|
||||
UnityPatches.AssemblyLocations.Add(assembly.FullName, Path.GetFullPath(assemblyPath));
|
||||
}
|
||||
|
||||
// Create the patch context
|
||||
PatchContext context = new PatchContext
|
||||
{
|
||||
LoadedPatchers = PatcherPlugins.AsReadOnly(),
|
||||
AvailableAssemblies = assemblies,
|
||||
AssemblyLocations = filenameDictionary
|
||||
};
|
||||
|
||||
// Next, initialize all the patchers
|
||||
InitializePatchers();
|
||||
InitializePatchers(context);
|
||||
|
||||
// Then, perform the actual patching
|
||||
|
||||
// Legacy patchers
|
||||
var patchedAssemblies = new HashSet<string>();
|
||||
foreach (var assemblyPatcher in PatcherPlugins)
|
||||
foreach (var assemblyPatcher in LegacyPatcherPlugins)
|
||||
foreach (string targetDll in assemblyPatcher.TargetDLLs())
|
||||
if (assemblies.TryGetValue(targetDll, out var assembly))
|
||||
if (filenameDictionary.TryGetValue(targetDll, out var assembly))
|
||||
{
|
||||
if (AppDomain.CurrentDomain.GetAssemblies().Any(x => x.GetName().Name == assembly.Name.Name))
|
||||
Logger.LogWarning($"Trying to patch an already loaded assembly [{assembly.Name.Name}] with [{assemblyPatcher.TypeName}]");
|
||||
|
||||
Logger.LogInfo($"Patching [{assembly.Name.Name}] with [{assemblyPatcher.TypeName}]");
|
||||
|
||||
var oldAssembly = assembly;
|
||||
|
||||
assemblyPatcher.Patcher?.Invoke(ref assembly);
|
||||
assemblies[targetDll] = assembly;
|
||||
|
||||
if (!ReferenceEquals(oldAssembly, assembly))
|
||||
{
|
||||
assemblies.Remove(oldAssembly);
|
||||
assemblies.Add(assembly);
|
||||
}
|
||||
|
||||
filenameDictionary[targetDll] = assembly;
|
||||
patchedAssemblies.Add(targetDll);
|
||||
}
|
||||
|
||||
// V2 patchers
|
||||
|
||||
foreach (var patcherDefinition in PatcherPlugins)
|
||||
{
|
||||
patcherDefinition.Instance.PatchAll(assemblies);
|
||||
}
|
||||
|
||||
foreach (var patcherDefinition in PatcherPlugins)
|
||||
{
|
||||
if (patcherDefinition.AssemblyDefinitionPatchers != null)
|
||||
foreach (var assemblyPatchDefinition in patcherDefinition.AssemblyDefinitionPatchers)
|
||||
{
|
||||
var assemblyDefinition = assemblies.FirstOrDefault(x => x.Name.Name == assemblyPatchDefinition.Key.AssemblyName);
|
||||
patchedAssemblies.Add(filenameDictionary.First(x => x.Value == assemblyDefinition).Key);
|
||||
|
||||
assemblyPatchDefinition.Value.Invoke(patcherDefinition.Instance, new object[] { assemblyDefinition });
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var patcherDefinition in PatcherPlugins)
|
||||
{
|
||||
if (patcherDefinition.TypeDefinitionPatchers != null)
|
||||
foreach (var typePatchDefinition in patcherDefinition.TypeDefinitionPatchers)
|
||||
{
|
||||
var assemblyDefinition = assemblies.FirstOrDefault(x => x.Name.Name == typePatchDefinition.Key.AssemblyName);
|
||||
patchedAssemblies.Add(filenameDictionary.First(x => x.Value == assemblyDefinition).Key);
|
||||
|
||||
var typeDefinition = assemblyDefinition?.MainModule.Types.FirstOrDefault(x => x.FullName == typePatchDefinition.Key.ClassName);
|
||||
|
||||
typePatchDefinition.Value.Invoke(patcherDefinition.Instance, new object[] { typeDefinition });
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, load patched assemblies into memory
|
||||
if (ConfigDumpAssemblies.Value || ConfigLoadDumpedAssemblies.Value)
|
||||
@@ -228,7 +384,7 @@ namespace BepInEx.Preloader.Patching
|
||||
if (!Directory.Exists(DumpedAssembliesPath))
|
||||
Directory.CreateDirectory(DumpedAssembliesPath);
|
||||
|
||||
foreach (KeyValuePair<string, AssemblyDefinition> kv in assemblies)
|
||||
foreach (KeyValuePair<string, AssemblyDefinition> kv in filenameDictionary)
|
||||
{
|
||||
string filename = kv.Key;
|
||||
var assembly = kv.Value;
|
||||
@@ -246,7 +402,7 @@ namespace BepInEx.Preloader.Patching
|
||||
Debugger.Break();
|
||||
}
|
||||
|
||||
foreach (var kv in assemblies)
|
||||
foreach (var kv in filenameDictionary)
|
||||
{
|
||||
string filename = kv.Key;
|
||||
var assembly = kv.Value;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
|
||||
namespace BepInEx.Preloader
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class TargetAssemblyAttribute : Attribute
|
||||
{
|
||||
public string AssemblyName { get; }
|
||||
|
||||
public TargetAssemblyAttribute(string assemblyName)
|
||||
{
|
||||
AssemblyName = assemblyName;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class TargetClassAttribute : Attribute
|
||||
{
|
||||
public string AssemblyName { get; }
|
||||
public string ClassName { get; }
|
||||
|
||||
public TargetClassAttribute(string assemblyName, string className)
|
||||
{
|
||||
AssemblyName = assemblyName;
|
||||
ClassName = className;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class PatcherInfo : Attribute
|
||||
{
|
||||
public string GUID { get; }
|
||||
public string Name { get; }
|
||||
public Version Version { get; }
|
||||
|
||||
public PatcherInfo(string guid, string name, string version)
|
||||
{
|
||||
GUID = guid;
|
||||
Name = name;
|
||||
Version = new Version(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using BepInEx.Preloader.Patching;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace BepInEx.Preloader
|
||||
{
|
||||
public abstract class BasePatcher
|
||||
{
|
||||
private PatchContext _patchContext;
|
||||
protected PatchContext PatchContext => _patchContext;
|
||||
|
||||
public virtual void Setup(PatchContext patchContext)
|
||||
{
|
||||
_patchContext = patchContext;
|
||||
}
|
||||
|
||||
public virtual void PatchAll(IList<AssemblyDefinition> assemblyDefinitions)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void Finalize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[TargetAssembly(assemblyName: "Terraria")]
|
||||
public void Patch(AssemblyDefinition assemblyDefinition)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using BepInEx.Bootstrap;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace BepInEx.Preloader.Patching
|
||||
{
|
||||
public class PatchContext
|
||||
{
|
||||
public ReadOnlyCollection<PatcherDefinition> LoadedPatchers { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// A read-only list of legacy patchers that are loaded. Will be removed in a future release.
|
||||
/// </summary>
|
||||
[Obsolete("Will be removed in a future release.", false)]
|
||||
public IList<Type> LoadedLegacyPatchers { get; internal set; }
|
||||
|
||||
public IList<AssemblyDefinition> AvailableAssemblies { get; internal set; }
|
||||
|
||||
public Dictionary<string, AssemblyDefinition> AssemblyLocations { get; internal set; }
|
||||
|
||||
internal PatchContext() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A preloader assembly patcher.
|
||||
/// </summary>
|
||||
public class PatcherDefinition
|
||||
{
|
||||
public PatcherInfo PatcherInfo { get; internal set; }
|
||||
|
||||
public string TypeName { get; internal set; }
|
||||
public BasePatcher Instance { get; internal set; }
|
||||
|
||||
public Dictionary<TargetAssemblyAttribute, MethodInfo> AssemblyDefinitionPatchers { get; internal set; } = new Dictionary<TargetAssemblyAttribute, MethodInfo>();
|
||||
|
||||
public Dictionary<TargetClassAttribute, MethodInfo> TypeDefinitionPatchers { get; internal set; } = new Dictionary<TargetClassAttribute, MethodInfo>();
|
||||
|
||||
internal PatcherDefinition() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single assembly patcher, based on the old reflection contract.
|
||||
/// </summary>
|
||||
internal class LegacyPatcherPlugin : ICacheable
|
||||
{
|
||||
/// <summary>
|
||||
/// Target assemblies to patch.
|
||||
/// </summary>
|
||||
public Func<IEnumerable<string>> TargetDLLs { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializer method that is run before any patching occurs.
|
||||
/// </summary>
|
||||
public Action Initializer { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Finalizer method that is run after all patching is done.
|
||||
/// </summary>
|
||||
public Action Finalizer { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// The main patcher method that is called on every DLL defined in <see cref="TargetDLLs" />.
|
||||
/// </summary>
|
||||
public AssemblyPatcherDelegate Patcher { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Type name of the patcher.
|
||||
/// </summary>
|
||||
public string TypeName { get; set; } = string.Empty;
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(TypeName);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader br)
|
||||
{
|
||||
TypeName = br.ReadString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using BepInEx.Bootstrap;
|
||||
|
||||
namespace BepInEx.Preloader.Patching
|
||||
{
|
||||
/// <summary>
|
||||
/// A single assembly patcher.
|
||||
/// </summary>
|
||||
internal class PatcherPlugin : ICacheable
|
||||
{
|
||||
/// <summary>
|
||||
/// Target assemblies to patch.
|
||||
/// </summary>
|
||||
public Func<IEnumerable<string>> TargetDLLs { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializer method that is run before any patching occurs.
|
||||
/// </summary>
|
||||
public Action Initializer { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Finalizer method that is run after all patching is done.
|
||||
/// </summary>
|
||||
public Action Finalizer { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// The main patcher method that is called on every DLL defined in <see cref="TargetDLLs" />.
|
||||
/// </summary>
|
||||
public AssemblyPatcherDelegate Patcher { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Type name of the patcher.
|
||||
/// </summary>
|
||||
public string TypeName { get; set; } = string.Empty;
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(TypeName);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader br)
|
||||
{
|
||||
TypeName = br.ReadString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ namespace BepInEx.Preloader
|
||||
|
||||
Logger.LogMessage("Preloader started");
|
||||
|
||||
AssemblyPatcher.AddPatcher(new PatcherPlugin
|
||||
AssemblyPatcher.LegacyPatcherPlugins.Add(new LegacyPatcherPlugin
|
||||
{
|
||||
TargetDLLs = () => new[] { ConfigEntrypointAssembly.Value },
|
||||
Patcher = PatchEntrypoint,
|
||||
@@ -88,7 +88,7 @@ namespace BepInEx.Preloader
|
||||
|
||||
AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
|
||||
|
||||
Logger.LogInfo($"{AssemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded");
|
||||
Logger.LogInfo($"{AssemblyPatcher.LegacyPatcherPlugins.Count + AssemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded");
|
||||
|
||||
AssemblyPatcher.PatchAndLoad(Paths.ManagedPath);
|
||||
AssemblyPatcher.DisposePatchers();
|
||||
|
||||
@@ -104,18 +104,18 @@ namespace BepInEx.Bootstrap
|
||||
}
|
||||
}
|
||||
|
||||
var ass = AssemblyDefinition.ReadAssembly(dll, readerParameters);
|
||||
var assembly = AssemblyDefinition.ReadAssembly(dll, readerParameters);
|
||||
|
||||
if (!assemblyFilter?.Invoke(ass) ?? false)
|
||||
if (!assemblyFilter?.Invoke(assembly) ?? false)
|
||||
{
|
||||
result[dll] = new List<T>();
|
||||
ass.Dispose();
|
||||
assembly.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
var matches = ass.MainModule.Types.Select(typeSelector).Where(t => t != null).ToList();
|
||||
var matches = assembly.MainModule.Types.Select(typeSelector).Where(t => t != null).ToList();
|
||||
result[dll] = matches;
|
||||
ass.Dispose();
|
||||
assembly.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -128,6 +128,43 @@ namespace BepInEx.Bootstrap
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up assemblies in the given directory and locates all types that can be loaded and collects their metadata.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The specific base type to search for.</typeparam>
|
||||
/// <param name="directory">The directory to search for assemblies.</param>
|
||||
/// <param name="typeSelector">A function to check if a type should be selected and to build the type metadata.</param>
|
||||
/// <param name="assemblyFilter">A filter function to quickly determine if the assembly can be loaded.</param>
|
||||
/// <param name="cacheName">The name of the cache to get cached types from.</param>
|
||||
/// <returns>A dictionary of all assemblies in the directory and the list of type metadatas of types that match the selector.</returns>
|
||||
public static Dictionary<string, List<T>> FindPluginTypesCacheless<T>(string directory, Func<TypeDefinition, T> typeSelector, Func<AssemblyDefinition, bool> assemblyFilter = null)
|
||||
{
|
||||
var result = new Dictionary<string, List<T>>();
|
||||
|
||||
foreach (string dll in Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories))
|
||||
try
|
||||
{
|
||||
var assembly = AssemblyDefinition.ReadAssembly(dll, readerParameters);
|
||||
|
||||
if (!assemblyFilter?.Invoke(assembly) ?? false)
|
||||
{
|
||||
result[dll] = new List<T>();
|
||||
assembly.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
var matches = assembly.MainModule.Types.Select(typeSelector).Where(t => t != null).ToList();
|
||||
result[dll] = matches;
|
||||
assembly.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e.ToString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads an index of type metadatas from a cache.
|
||||
/// </summary>
|
||||
|
||||
Reference in new issue
Block a user