Forráskód Böngészése

Add PluginInfo class for metadata; process plugins via cecil instead of Assembly.Load

ghorsington 5 éve
szülő
commit
99dad98fd7

+ 1 - 0
BepInEx/BepInEx.csproj

@@ -46,6 +46,7 @@
     </Reference>
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Contract\PluginInfo.cs" />
     <Compile Include="Configuration\ConfigDefinition.cs" />
     <Compile Include="Configuration\ConfigFile.cs" />
     <Compile Include="Configuration\ConfigWrapper.cs" />

+ 41 - 66
BepInEx/Bootstrap/Chainloader.cs

@@ -7,6 +7,8 @@ using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Text;
+using BepInEx.Contract;
+using Mono.Cecil;
 using UnityEngine;
 using UnityInjector.ConsoleUtil;
 using Logger = BepInEx.Logging.Logger;
@@ -21,7 +23,7 @@ namespace BepInEx.Bootstrap
 		/// <summary>
 		/// The loaded and initialized list of plugins.
 		/// </summary>
-		public static List<BaseUnityPlugin> Plugins { get; private set; } = new List<BaseUnityPlugin>();
+		public static Dictionary<string, PluginInfo> Plugins { get; private set; } = new Dictionary<string, PluginInfo>();
 
 		/// <summary>
 		/// The GameObject that all plugins are attached to as components.
@@ -97,8 +99,7 @@ namespace BepInEx.Bootstrap
 			{
 				var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static);
 				if (productNameProp != null)
-					ConsoleWindow.Title =
-						$"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}";
+					ConsoleWindow.Title = $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}";
 
 				Logger.LogMessage("Chainloader started");
 
@@ -106,62 +107,33 @@ namespace BepInEx.Bootstrap
 
 				UnityEngine.Object.DontDestroyOnLoad(ManagerObject);
 
+				var pluginsToLoad = TypeLoader.FindPluginTypes(Paths.PluginPath);
 
-				string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();
+				var pluginInfos = pluginsToLoad.SelectMany(p => p.Value).ToList();
 
-				var globalPluginTypes = TypeLoader.LoadTypes<BaseUnityPlugin>(Paths.PluginPath).ToList();
+				var loadedAssemblies = new Dictionary<AssemblyDefinition, Assembly>();
 
-				var selectedPluginTypes = globalPluginTypes
-										  .Where(plugin =>
-										  {
-											  //Ensure metadata exists
-											  var metadata = MetadataHelper.GetMetadata(plugin);
-
-											  if (metadata == null)
-											  {
-												  Logger.LogWarning($"Skipping over type [{plugin.Name}] as no metadata attribute is specified");
-												  return false;
-											  }
-
-											  //Perform a filter for currently running process
-											  var filters = MetadataHelper.GetAttributes<BepInProcess>(plugin);
-
-											  if (filters.Length == 0) //no filters means it loads everywhere
-												  return true;
-
-											  var result = filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);
-
-											  if (!result)
-												  Logger.LogInfo($"Skipping over plugin [{metadata.GUID}] due to process filter");
-
-											  return result;
-										  })
-										  .ToList();
-
-				Logger.LogInfo($"{selectedPluginTypes.Count} / {globalPluginTypes.Count} plugins to load");
+				Logger.LogInfo($"{pluginInfos.Count} / {pluginInfos.Count} plugins to load");
 
 				var dependencyDict = new Dictionary<string, IEnumerable<string>>();
-				var pluginsByGUID = new Dictionary<string, Type>();
+				var pluginsByGUID = new Dictionary<string, PluginInfo>();
 
-				foreach (Type t in selectedPluginTypes)
+				foreach (var pluginInfo in pluginInfos)
 				{
-					var dependencies = MetadataHelper.GetDependencies(t, selectedPluginTypes);
-					var metadata = MetadataHelper.GetMetadata(t);
-
-					if (metadata.GUID == null)
+					if (pluginInfo.Metadata.GUID == null)
 					{
-						Logger.LogWarning($"Skipping [{metadata.Name}] because it does not have a valid GUID.");
+						Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because it does not have a valid GUID.");
 						continue;
 					}
 
-					if (dependencyDict.ContainsKey(metadata.GUID))
+					if (dependencyDict.ContainsKey(pluginInfo.Metadata.GUID))
 					{
-						Logger.LogWarning($"Skipping [{metadata.Name}] because its GUID ({metadata.GUID}) is already used by another plugin.");
+						Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because its GUID ({pluginInfo.Metadata.GUID}) is already used by another plugin.");
 						continue;
 					}
 
-					dependencyDict[metadata.GUID] = dependencies.Select(d => d.DependencyGUID);
-					pluginsByGUID[metadata.GUID] = t;
+					dependencyDict[pluginInfo.Metadata.GUID] = pluginInfo.Dependencies.Select(d => d.DependencyGUID);
+					pluginsByGUID[pluginInfo.Metadata.GUID] = pluginInfo;
 				}
 
 				var emptyDependencies = new string[0];
@@ -176,14 +148,12 @@ namespace BepInEx.Bootstrap
 				foreach (var pluginGUID in sortedPlugins)
 				{
 					// If the plugin is missing, don't process it
-					if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginType))
+					if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginInfo))
 						continue;
 
-					var metadata = MetadataHelper.GetMetadata(pluginType);
-					var dependencies = MetadataHelper.GetDependencies(pluginType, selectedPluginTypes);
 					var dependsOnInvalidPlugin = false;
 					var missingDependencies = new List<string>();
-					foreach (var dependency in dependencies)
+					foreach (var dependency in pluginInfo.Dependencies)
 					{
 						// If the depenency wasn't already processed, it's missing altogether
 						if (!processedPlugins.Contains(dependency.DependencyGUID))
@@ -206,15 +176,15 @@ namespace BepInEx.Bootstrap
 
 					if (dependsOnInvalidPlugin)
 					{
-						Logger.LogWarning($"Skipping [{metadata.Name}] because it has a dependency that was not loaded. See above errors for details.");
+						Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because it has a dependency that was not loaded. See above errors for details.");
 						continue;
 					}
 
 					if (missingDependencies.Count != 0)
 					{
-						Logger.LogError($@"Missing the following dependencies for [{metadata.Name}]: {"\r\n"}{
-							string.Join("\r\n", missingDependencies.Select(s => $"- {s}").ToArray())
-							}{"\r\n"}Loading will be skipped; expect further errors and unstabilities.");
+						Logger.LogError($@"Missing the following dependencies for [{pluginInfo.Metadata.Name}]: {"\r\n"}{
+												string.Join("\r\n", missingDependencies.Select(s => $"- {s}").ToArray())
+											}{"\r\n"}Loading will be skipped; expect further errors and unstabilities.");
 
 						invalidPlugins.Add(pluginGUID);
 						continue;
@@ -222,16 +192,29 @@ namespace BepInEx.Bootstrap
 
 					try
 					{
-						Logger.LogInfo($"Loading [{metadata.Name} {metadata.Version}]");
-						Plugins.Add((BaseUnityPlugin)ManagerObject.AddComponent(pluginType));
+						Logger.LogInfo($"Loading [{pluginInfo.Metadata.Name} {pluginInfo.Metadata.Version}]");
+
+						if (!loadedAssemblies.TryGetValue(pluginInfo.CecilType.Module.Assembly, out var ass))
+							loadedAssemblies[pluginInfo.CecilType.Module.Assembly] = ass = Assembly.LoadFile(pluginInfo.Location);
+
+						Plugins[pluginGUID] = pluginInfo;
+						pluginInfo.Instance = (BaseUnityPlugin)ManagerObject.AddComponent(ass.GetType(pluginInfo.CecilType.FullName));
+						pluginInfo.CecilType = null;
 					}
 					catch (Exception ex)
 					{
 						invalidPlugins.Add(pluginGUID);
-						Logger.LogError($"Error loading [{metadata.Name}] : {ex.Message}");
+						Plugins.Remove(pluginGUID);
+
+						Logger.LogError($"Error loading [{pluginInfo.Metadata.Name}] : {ex.Message}");
 						Logger.LogDebug(ex);
 					}
 				}
+
+				foreach (var selectedTypesInfo in pluginsToLoad)
+				{
+					selectedTypesInfo.Key.Dispose();
+				}
 			}
 			catch (Exception ex)
 			{
@@ -248,17 +231,9 @@ namespace BepInEx.Bootstrap
 
 		#region Config
 
-		private static readonly ConfigWrapper<string> ConfigPluginsDirectory = ConfigFile.CoreConfig.Wrap(
-				"Paths",
-				"PluginsDirectory",
-				"The relative directory to the BepInEx folder where plugins are loaded.",
-				"plugins");
-
-		private static readonly ConfigWrapper<bool> ConfigUnityLogging = ConfigFile.CoreConfig.Wrap(
-				"Logging",
-				"UnityLogListening",
-				"Enables showing unity log messages in the BepInEx logging system.",
-				true);
+		private static readonly ConfigWrapper<string> ConfigPluginsDirectory = ConfigFile.CoreConfig.Wrap("Paths", "PluginsDirectory", "The relative directory to the BepInEx folder where plugins are loaded.", "plugins");
+
+		private static readonly ConfigWrapper<bool> ConfigUnityLogging = ConfigFile.CoreConfig.Wrap("Logging", "UnityLogListening", "Enables showing unity log messages in the BepInEx logging system.", true);
 
 		#endregion
 	}

+ 77 - 13
BepInEx/Bootstrap/TypeLoader.cs

@@ -1,9 +1,13 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
+using System.Linq;
 using System.Reflection;
 using System.Text;
+using BepInEx.Contract;
 using BepInEx.Logging;
+using Mono.Cecil;
 
 namespace BepInEx.Bootstrap
 {
@@ -12,39 +16,99 @@ namespace BepInEx.Bootstrap
 	/// </summary>
 	public static class TypeLoader
 	{
+		private static bool Is(this TypeDefinition self, Type td)
+		{
+			if (self.FullName == td.FullName)
+				return true;
+			return self.FullName != "System.Object" && (self.BaseType?.Resolve().Is(td) ?? false);
+		}
+
+		private static DefaultAssemblyResolver resolver;
+		private static ReaderParameters readerParameters;
+
+		static TypeLoader()
+		{
+			resolver = new DefaultAssemblyResolver();
+			readerParameters = new ReaderParameters { AssemblyResolver = resolver };
+
+			resolver.ResolveFailure += (sender, reference) =>
+			{
+				var name = new AssemblyName(reference.FullName);
+
+				if (Utility.TryResolveDllAssembly(name, Paths.BepInExAssemblyDirectory, readerParameters, out AssemblyDefinition assembly) || Utility.TryResolveDllAssembly(name, Paths.PluginPath, readerParameters, out assembly) || Utility.TryResolveDllAssembly(name, Paths.ManagedPath, readerParameters, out assembly))
+					return assembly;
+
+				return null;
+			};
+		}
+
 		/// <summary>
 		/// Loads a list of types from a directory containing assemblies, that derive from a base type.
 		/// </summary>
 		/// <typeparam name="T">The specific base type to search for.</typeparam>
 		/// <param name="directory">The directory to search for assemblies.</param>
 		/// <returns>Returns a list of found derivative types.</returns>
-		public static IEnumerable<Type> LoadTypes<T>(string directory)
+		public static Dictionary<AssemblyDefinition, IEnumerable<PluginInfo>> FindPluginTypes(string directory)
 		{
-			List<Type> types = new List<Type>();
-			Type pluginType = typeof(T);
+			var result = new Dictionary<AssemblyDefinition, IEnumerable<PluginInfo>>();
+			var pluginType = typeof(BaseUnityPlugin);
+			string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();
 
 			foreach (string dll in Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories))
 			{
 				try
 				{
-					AssemblyName an = AssemblyName.GetAssemblyName(dll);
-					Assembly assembly = Assembly.Load(an);
+					var ass = AssemblyDefinition.ReadAssembly(dll, readerParameters);
 
-					foreach (Type type in assembly.GetTypes())
+					var matchingTypes = ass.MainModule.Types.Where(t => !t.IsInterface && !t.IsAbstract && t.Is(pluginType)).ToList();
+
+					if (matchingTypes.Count == 0)
+						continue;
+
+					var pluginInfos = new List<PluginInfo>();
+
+					foreach (var pluginTypeDefinition in matchingTypes)
 					{
-						if (!type.IsInterface && !type.IsAbstract && pluginType.IsAssignableFrom(type))
-							types.Add(type);
+						var metadata = BepInPlugin.FromCecilType(pluginTypeDefinition);
+
+						if (metadata == null)
+						{
+							Logger.LogWarning($"Skipping over type [{pluginTypeDefinition.Name}] as no metadata attribute is specified");
+							continue;
+						}
+
+						//Perform a filter for currently running process
+						var filters = BepInProcess.FromCecilType(pluginTypeDefinition);
+
+						bool invalidProcessName = filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);
+
+						if (invalidProcessName)
+						{
+							Logger.LogInfo($"Skipping over plugin [{metadata.GUID}] due to process filter");
+							continue;
+						}
+
+						var dependencies = BepInDependency.FromCecilType(pluginTypeDefinition);
+
+						pluginInfos.Add(new PluginInfo
+						{
+							Metadata = metadata,
+							Processes = filters,
+							Dependencies = dependencies,
+							CecilType = pluginTypeDefinition,
+							Location = dll
+						});
 					}
+
+					result[ass] = pluginInfos;
 				}
-				catch (BadImageFormatException) { } //unmanaged DLL
-				catch (ReflectionTypeLoadException ex)
+				catch (Exception e)
 				{
-					Logger.LogError($"Could not load \"{Path.GetFileName(dll)}\" as a plugin!");
-					Logger.LogDebug(TypeLoadExceptionToString(ex));
+					Logger.LogError(e.ToString());
 				}
 			}
 
-			return types;
+			return result;
 		}
 
 		private static string TypeLoadExceptionToString(ReflectionTypeLoadException ex)

+ 43 - 28
BepInEx/Contract/Attributes.cs

@@ -1,6 +1,10 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Reflection;
+using BepInEx.Logging;
+using Mono.Cecil;
+using Mono.Collections.Generic;
 
 namespace BepInEx
 {
@@ -38,6 +42,16 @@ namespace BepInEx
 			this.Name = Name;
 			this.Version = new Version(Version);
 		}
+
+		internal static BepInPlugin FromCecilType(TypeDefinition td)
+		{
+			var attr = MetadataHelper.GetCustomAttributes<BepInPlugin>(td, false).FirstOrDefault();
+
+			if (attr == null)
+				return null;
+
+			return new BepInPlugin((string)attr.ConstructorArguments[0].Value, (string)attr.ConstructorArguments[1].Value, (string)attr.ConstructorArguments[2].Value);
+		}
 	}
 
 	/// <summary>
@@ -76,6 +90,12 @@ namespace BepInEx
 			this.DependencyGUID = DependencyGUID;
 			this.Flags = Flags;
 		}
+
+		internal static IEnumerable<BepInDependency> FromCecilType(TypeDefinition td)
+		{
+			var attrs = MetadataHelper.GetCustomAttributes<BepInDependency>(td, true);
+			return attrs.Select(customAttribute => new BepInDependency((string)customAttribute.ConstructorArguments[0].Value, (DependencyFlags)customAttribute.ConstructorArguments[1].Value)).ToList();
+		}
 	}
 
 	/// <summary>
@@ -90,9 +110,12 @@ namespace BepInEx
 		public string ProcessName { get; protected set; }
 
 		/// <param name="ProcessName">The name of the process that this plugin will run under.</param>
-		public BepInProcess(string ProcessName)
+		public BepInProcess(string ProcessName) { this.ProcessName = ProcessName; }
+
+		internal static IEnumerable<BepInProcess> FromCecilType(TypeDefinition td)
 		{
-			this.ProcessName = ProcessName;
+			var attrs = MetadataHelper.GetCustomAttributes<BepInProcess>(td, true);
+			return attrs.Select(customAttribute => new BepInProcess((string)customAttribute.ConstructorArguments[0].Value)).ToList();
 		}
 	}
 
@@ -105,19 +128,22 @@ namespace BepInEx
 	/// </summary>
 	public static class MetadataHelper
 	{
-		/// <summary>
-		/// Retrieves the BepInPlugin metadata from a plugin type.
-		/// </summary>
-		/// <param name="plugin">The plugin type.</param>
-		/// <returns>The BepInPlugin metadata of the plugin type.</returns>
-		public static BepInPlugin GetMetadata(Type pluginType)
+		public static BepInPlugin GetMetadata(BaseUnityPlugin pluginType) { return pluginType.GetType().GetCustomAttributes(typeof(BepInPlugin), false).FirstOrDefault() as BepInPlugin; }
+
+		internal static IEnumerable<CustomAttribute> GetCustomAttributes<T>(TypeDefinition td, bool inherit) where T : Attribute
 		{
-			object[] attributes = pluginType.GetCustomAttributes(typeof(BepInPlugin), false);
+			var result = new List<CustomAttribute>();
+			var type = typeof(T);
+			var currentType = td;
+
+			do
+			{
+				result.AddRange(currentType.CustomAttributes.Where(ca => ca.AttributeType.FullName == type.FullName));
+				currentType = currentType.BaseType?.Resolve();
+			} while (inherit && currentType?.FullName != "System.Object");
 
-			if (attributes.Length == 0)
-				return null;
 
-			return (BepInPlugin)attributes[0];
+			return result;
 		}
 
 		/// <summary>
@@ -125,8 +151,7 @@ namespace BepInEx
 		/// </summary>
 		/// <param name="plugin">The plugin instance.</param>
 		/// <returns>The BepInPlugin metadata of the plugin instance.</returns>
-		public static BepInPlugin GetMetadata(object plugin)
-			=> GetMetadata(plugin.GetType());
+		public static BepInPlugin GetMetadata(object plugin) => GetMetadata(plugin.GetType());
 
 		/// <summary>
 		/// Gets the specified attributes of a type, if they exist.
@@ -134,10 +159,7 @@ namespace BepInEx
 		/// <typeparam name="T">The attribute type to retrieve.</typeparam>
 		/// <param name="plugin">The plugin type.</param>
 		/// <returns>The attributes of the type, if existing.</returns>
-		public static T[] GetAttributes<T>(Type pluginType) where T : Attribute
-		{
-			return (T[])pluginType.GetCustomAttributes(typeof(T), true);
-		}
+		public static T[] GetAttributes<T>(Type pluginType) where T : Attribute { return (T[])pluginType.GetCustomAttributes(typeof(T), true); }
 
 		/// <summary>
 		/// Gets the specified attributes of an instance, if they exist.
@@ -145,8 +167,7 @@ namespace BepInEx
 		/// <typeparam name="T">The attribute type to retrieve.</typeparam>
 		/// <param name="plugin">The plugin instance.</param>
 		/// <returns>The attributes of the instance, if existing.</returns>
-		public static IEnumerable<T> GetAttributes<T>(object plugin) where T : Attribute
-			=> GetAttributes<T>(plugin.GetType());
+		public static IEnumerable<T> GetAttributes<T>(object plugin) where T : Attribute => GetAttributes<T>(plugin.GetType());
 
 		/// <summary>
 		/// Retrieves the dependencies of the specified plugin type.
@@ -154,10 +175,7 @@ namespace BepInEx
 		/// <param name="Plugin">The plugin type.</param>
 		/// <param name="AllPlugins">All currently loaded plugin types.</param>
 		/// <returns>A list of all plugin types that the specified plugin type depends upon.</returns>
-		public static IEnumerable<BepInDependency> GetDependencies(Type Plugin, IEnumerable<Type> AllPlugins)
-		{
-			return Plugin.GetCustomAttributes(typeof(BepInDependency), true).Cast<BepInDependency>();
-		}
+		public static IEnumerable<BepInDependency> GetDependencies(Type Plugin, IEnumerable<Type> AllPlugins) { return Plugin.GetCustomAttributes(typeof(BepInDependency), true).Cast<BepInDependency>(); }
 	}
 
 	/// <summary>
@@ -180,10 +198,7 @@ namespace BepInEx
 	{
 		public string Info { get; }
 
-		public BuildInfoAttribute(string info)
-		{
-			Info = info;
-		}
+		public BuildInfoAttribute(string info) { Info = info; }
 	}
 
 	#endregion

+ 7 - 1
BepInEx/Contract/BaseUnityPlugin.cs

@@ -1,4 +1,6 @@
-using BepInEx.Configuration;
+using BepInEx.Bootstrap;
+using BepInEx.Configuration;
+using BepInEx.Contract;
 using BepInEx.Logging;
 using UnityEngine;
 
@@ -13,10 +15,14 @@ namespace BepInEx
 
 		protected ConfigFile Config { get; }
 
+		protected PluginInfo Info { get; }
+
 		protected BaseUnityPlugin()
 		{
 			var metadata = MetadataHelper.GetMetadata(this);
 
+			Info = Chainloader.Plugins[metadata.GUID];
+
 			Logger = Logging.Logger.CreateLogSource(metadata.Name);
 
 			Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false);

+ 19 - 0
BepInEx/Contract/PluginInfo.cs

@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using Mono.Cecil;
+
+namespace BepInEx.Contract {
+	public class PluginInfo
+	{
+		public BepInPlugin Metadata { get; internal set; }
+
+		public IEnumerable<BepInProcess> Processes { get; internal set; }
+
+		public IEnumerable<BepInDependency> Dependencies { get; internal set; }
+
+		public string Location { get; internal set; }
+
+		public BaseUnityPlugin Instance { get; internal set; }
+
+		internal TypeDefinition CecilType { get; set; }
+	}
+}

+ 26 - 17
BepInEx/Utility.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Reflection;
+using Mono.Cecil;
 
 namespace BepInEx
 {
@@ -24,30 +25,21 @@ namespace BepInEx
 		/// <param name="input">The string to parse</param>
 		/// <param name="defaultValue">The value to return if parsing is unsuccessful.</param>
 		/// <returns>Boolean value of input if able to be parsed, otherwise default value.</returns>
-		public static bool SafeParseBool(string input, bool defaultValue = false)
-		{
-			return bool.TryParse(input, out bool result) ? result : defaultValue;
-		}
+		public static bool SafeParseBool(string input, bool defaultValue = false) { return bool.TryParse(input, out bool result) ? result : defaultValue; }
 
 		/// <summary>
 		/// Converts a file path into a UnityEngine.WWW format.
 		/// </summary>
 		/// <param name="path">The file path to convert.</param>
 		/// <returns>A converted file path.</returns>
-		public static string ConvertToWWWFormat(string path)
-		{
-			return $"file://{path.Replace('\\', '/')}";
-		}
+		public static string ConvertToWWWFormat(string path) { return $"file://{path.Replace('\\', '/')}"; }
 
 		/// <summary>
 		/// Indicates whether a specified string is null, empty, or consists only of white-space characters.
 		/// </summary>
 		/// <param name="self">The string to test.</param>
 		/// <returns>True if the value parameter is null or empty, or if value consists exclusively of white-space characters.</returns>
-		public static bool IsNullOrWhiteSpace(this string self)
-		{
-			return self == null || self.All(char.IsWhiteSpace);
-		}
+		public static bool IsNullOrWhiteSpace(this string self) { return self == null || self.All(char.IsWhiteSpace); }
 
 		public static IEnumerable<TNode> TopologicalSort<TNode>(IEnumerable<TNode> nodes, Func<TNode, IEnumerable<TNode>> dependencySelector)
 		{
@@ -61,9 +53,8 @@ namespace BepInEx
 				Stack<TNode> currentStack = new Stack<TNode>();
 				if (!Visit(input, currentStack))
 				{
-					throw new Exception("Cyclic Dependency:\r\n" + currentStack
-																   .Select(x => $" - {x}") //append dashes
-																   .Aggregate((a, b) => $"{a}\r\n{b}")); //add new lines inbetween
+					throw new Exception("Cyclic Dependency:\r\n" + currentStack.Select(x => $" - {x}") //append dashes
+																			   .Aggregate((a, b) => $"{a}\r\n{b}")); //add new lines inbetween
 				}
 			}
 
@@ -107,7 +98,7 @@ namespace BepInEx
 		/// <param name="directory">Directory to search the assembly from.</param>
 		/// <param name="assembly">The loaded assembly.</param>
 		/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
-		public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly)
+		private static bool TryResolveDllAssembly<T>(AssemblyName assemblyName, string directory, Func<string, T> loader, out T assembly) where T : class
 		{
 			assembly = null;
 
@@ -124,7 +115,7 @@ namespace BepInEx
 
 				try
 				{
-					assembly = Assembly.LoadFile(path);
+					assembly = loader(path);
 				}
 				catch (Exception)
 				{
@@ -136,5 +127,23 @@ namespace BepInEx
 
 			return false;
 		}
+
+		/// <summary>
+		/// Try to resolve and load the given assembly DLL.
+		/// </summary>
+		/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
+		/// <param name="directory">Directory to search the assembly from.</param>
+		/// <param name="assembly">The loaded assembly.</param>
+		/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
+		public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly) { return TryResolveDllAssembly(assemblyName, directory, Assembly.LoadFile, out assembly); }
+
+		/// <summary>
+		/// Try to resolve and load the given assembly DLL.
+		/// </summary>
+		/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
+		/// <param name="directory">Directory to search the assembly from.</param>
+		/// <param name="assembly">The loaded assembly.</param>
+		/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
+		public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, ReaderParameters readerParameters, out AssemblyDefinition assembly) { return TryResolveDllAssembly(assemblyName, directory, s => AssemblyDefinition.ReadAssembly(s, readerParameters), out assembly); }
 	}
 }