diff --git a/.gitmodules b/.gitmodules
index f433f06..0629616 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -3,4 +3,4 @@
url = https://github.com/BepInEx/BepInEx.Harmony
[submodule "submodules/MonoMod"]
path = submodules/MonoMod
- url = https://github.com/0x0ade/MonoMod.git
+ url = https://github.com/MonoMod/MonoMod.git
diff --git a/BepInEx.Patcher/BepInEx.Patcher.csproj b/BepInEx.Patcher/BepInEx.Patcher.csproj
index b645247..db8fa92 100644
--- a/BepInEx.Patcher/BepInEx.Patcher.csproj
+++ b/BepInEx.Patcher/BepInEx.Patcher.csproj
@@ -1,7 +1,6 @@
-
Debug
@@ -20,7 +19,7 @@
BepInEx.Patcher.Program
-
+
true
..\bin\patcher\
TRACE
@@ -30,29 +29,10 @@
prompt
MinimumRecommendedRules.ruleset
-
- true
- ..\bin\patcher\
- TRACE;UNITY_2018
- true
- embedded
- AnyCPU
- prompt
- MinimumRecommendedRules.ruleset
-
..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.dll
-
- ..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Mdb.dll
-
-
- ..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Pdb.dll
-
-
- ..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Rocks.dll
-
@@ -62,6 +42,7 @@
+
@@ -73,19 +54,8 @@
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/BepInEx.Patcher/ILRepack.targets b/BepInEx.Patcher/ILRepack.targets
new file mode 100644
index 0000000..071b3b5
--- /dev/null
+++ b/BepInEx.Patcher/ILRepack.targets
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BepInEx.Patcher/Program.cs b/BepInEx.Patcher/Program.cs
index 187168a..509f427 100644
--- a/BepInEx.Patcher/Program.cs
+++ b/BepInEx.Patcher/Program.cs
@@ -26,6 +26,14 @@ namespace BepInEx.Patcher
Console.ResetColor();
}
+ static string GetUnityEngineAssembly(string managedDir)
+ {
+ var path = Path.Combine(managedDir, "UnityEngine.CoreModule.dll");
+ if (File.Exists(path))
+ return path;
+ return Path.Combine(managedDir, "UnityEngine.dll");
+ }
+
static void Main(string[] args)
{
Console.WriteLine($"BepInEx Patcher v{Assembly.GetExecutingAssembly().GetName().Version}");
@@ -43,12 +51,7 @@ namespace BepInEx.Patcher
string managedDir = Environment.CurrentDirectory + $@"\{gameName}_Data\Managed";
-#if UNITY_2018
- string unityOutputDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.CoreModule.dll");
-#else
- string unityOutputDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll");
-#endif
-
+ string unityOutputDLL = GetUnityEngineAssembly(managedDir);
if (!Directory.Exists(managedDir) || !File.Exists(unityOutputDLL))
continue;
@@ -101,11 +104,7 @@ namespace BepInEx.Patcher
AssemblyResolver = defaultResolver
};
-#if UNITY_2018
- string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.CoreModule.dll.bak");
-#else
- string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll.bak");
-#endif
+ string unityBackupDLL = $"{GetUnityEngineAssembly(managedDir)}.bak";
//determine which assembly to use as a base
AssemblyDefinition unity = AssemblyDefinition.ReadAssembly(unityOutputDLL, rp);
diff --git a/BepInEx.Patcher/packages.config b/BepInEx.Patcher/packages.config
index eed4416..68b8c20 100644
--- a/BepInEx.Patcher/packages.config
+++ b/BepInEx.Patcher/packages.config
@@ -1,6 +1,7 @@
+
-
+
\ No newline at end of file
diff --git a/BepInEx.Preloader/BepInEx.Preloader.csproj b/BepInEx.Preloader/BepInEx.Preloader.csproj
index 1be0603..1692a76 100644
--- a/BepInEx.Preloader/BepInEx.Preloader.csproj
+++ b/BepInEx.Preloader/BepInEx.Preloader.csproj
@@ -13,7 +13,7 @@
512
true
-
+
none
true
..\bin\
@@ -22,36 +22,10 @@
4
..\bin\BepInEx.Preloader.xml
-
- ..\bin\
- TRACE;UNITY_2018
- true
- none
- AnyCPU
- prompt
- MinimumRecommendedRules.ruleset
-
-
- bin\Debug\
- TRACE;UNITY_2018
- true
- AnyCPU
- prompt
- MinimumRecommendedRules.ruleset
-
..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.dll
-
- ..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Mdb.dll
-
-
- ..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Pdb.dll
-
-
- ..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Rocks.dll
-
diff --git a/BepInEx.Preloader/Entrypoint.cs b/BepInEx.Preloader/Entrypoint.cs
index ebb1118..ec2ec3e 100644
--- a/BepInEx.Preloader/Entrypoint.cs
+++ b/BepInEx.Preloader/Entrypoint.cs
@@ -5,38 +5,18 @@ using System.Reflection;
namespace BepInEx.Preloader
{
- internal static class Entrypoint
+ internal static class PreloaderRunner
{
- ///
- /// The main entrypoint of BepInEx, called from Doorstop.
- ///
- ///
- /// The arguments passed in from Doorstop. First argument is the path of the currently executing
- /// process.
- ///
- public static void Main(string[] args)
+ public static void PreloaderMain(string[] args)
{
- EnvVars.LoadVars();
-
string bepinPath = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH)));
Paths.SetExecutablePath(args[0], bepinPath, EnvVars.DOORSTOP_MANAGED_FOLDER_DIR);
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
-
Preloader.Run();
}
- ///
- /// A handler for to perform some special handling.
- ///
- /// It attempts to check currently loaded assemblies (ignoring the version), and then checks the BepInEx/core path,
- /// BepInEx/patchers path and the BepInEx folder, all in that order.
- ///
- ///
- ///
- ///
- ///
- internal static Assembly LocalResolve(object sender, ResolveEventArgs args)
+ private static Assembly LocalResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
@@ -54,4 +34,48 @@ namespace BepInEx.Preloader
return null;
}
}
+
+ internal static class Entrypoint
+ {
+ private static string preloaderPath;
+
+ ///
+ /// The main entrypoint of BepInEx, called from Doorstop.
+ ///
+ ///
+ /// The arguments passed in from Doorstop. First argument is the path of the currently executing
+ /// process.
+ ///
+ public static void Main(string[] args)
+ {
+ EnvVars.LoadVars();
+
+ // Get the path of this DLL via Doorstop env var because Assembly.Location mangles non-ASCII characters on some versions of Mono for unknown reasons
+ preloaderPath = Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH));
+
+ AppDomain.CurrentDomain.AssemblyResolve += ResolveCurrentDirectory;
+
+ // In some versions of Unity 4, Mono tries to resolve BepInEx.dll prematurely because of the call to Paths.SetExecutablePath
+ // To prevent that, we have to use reflection and a separate startup class so that we can install required assembly resolvers before the main code
+ typeof(Entrypoint).Assembly.GetType($"BepInEx.Preloader.{nameof(PreloaderRunner)}")
+ ?.GetMethod(nameof(PreloaderRunner.PreloaderMain))
+ ?.Invoke(null, new object[] { args });
+
+ AppDomain.CurrentDomain.AssemblyResolve -= ResolveCurrentDirectory;
+ }
+
+ private static Assembly ResolveCurrentDirectory(object sender, ResolveEventArgs args)
+ {
+ var name = new AssemblyName(args.Name);
+
+ try
+ {
+ return Assembly.LoadFile(Path.Combine(preloaderPath, $"{name.Name}.dll"));
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/BepInEx.Preloader/Logger/PreloaderLogWriter.cs b/BepInEx.Preloader/Logger/PreloaderLogWriter.cs
index 2b23fa4..b73d781 100644
--- a/BepInEx.Preloader/Logger/PreloaderLogWriter.cs
+++ b/BepInEx.Preloader/Logger/PreloaderLogWriter.cs
@@ -56,7 +56,6 @@ namespace BepInEx.Preloader
public void Dispose()
{
-
if (LoggerSource != null)
{
Console.SetOut(StandardOut);
diff --git a/BepInEx.Preloader/Patching/AssemblyPatcher.cs b/BepInEx.Preloader/Patching/AssemblyPatcher.cs
index 249b8db..e01aa5f 100644
--- a/BepInEx.Preloader/Patching/AssemblyPatcher.cs
+++ b/BepInEx.Preloader/Patching/AssemblyPatcher.cs
@@ -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
///
internal static class AssemblyPatcher
{
+ private const BindingFlags ALL = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.IgnoreCase;
+
public static List PatcherPlugins { get; } = new List();
private static readonly string DumpedAssembliesPath = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
@@ -36,29 +39,108 @@ namespace BepInEx.Preloader.Patching
PatcherPlugins.Add(patcher);
}
+ private static T CreateDelegate(MethodInfo method) where T : class => method != null ? Delegate.CreateDelegate(typeof(T), method) as T : null;
+
+ private static PatcherPlugin ToPatcherPlugin(TypeDefinition type)
+ {
+ if (type.IsInterface || type.IsAbstract && !type.IsSealed)
+ return null;
+
+ var targetDlls = type.Methods.FirstOrDefault(m => m.Name.Equals("get_TargetDLLs", StringComparison.InvariantCultureIgnoreCase) &&
+ m.IsPublic &&
+ m.IsStatic);
+
+ if (targetDlls == null ||
+ targetDlls.ReturnType.FullName != "System.Collections.Generic.IEnumerable`1")
+ return null;
+
+ var patch = type.Methods.FirstOrDefault(m => m.Name.Equals("Patch") &&
+ m.IsPublic &&
+ m.IsStatic &&
+ m.ReturnType.FullName == "System.Void" &&
+ m.Parameters.Count == 1 &&
+ (m.Parameters[0].ParameterType.FullName == "Mono.Cecil.AssemblyDefinition&" ||
+ m.Parameters[0].ParameterType.FullName == "Mono.Cecil.AssemblyDefinition"));
+
+ if (patch == null)
+ return null;
+
+ return new PatcherPlugin
+ {
+ TypeName = type.FullName
+ };
+ }
+
///
/// Adds all patchers from all managed assemblies specified in a directory.
///
/// Directory to search patcher DLLs from.
/// A function that locates assembly patchers in a given managed assembly.
- public static void AddPatchersFromDirectory(string directory,
- Func> patcherLocator)
+ public static void AddPatchersFromDirectory(string directory)
{
if (!Directory.Exists(directory))
return;
var sortedPatchers = new SortedDictionary();
- foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories))
- try
- {
- var assembly = Assembly.LoadFrom(assemblyPath);
+ var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin);
- foreach (var patcher in patcherLocator(assembly))
- sortedPatchers.Add(patcher.Name, patcher);
+ foreach (var keyValuePair in patchers)
+ {
+ var assemblyPath = keyValuePair.Key;
+ var patcherCollection = keyValuePair.Value;
+
+ var ass = Assembly.LoadFile(assemblyPath);
+
+ foreach (var patcherPlugin in patcherCollection)
+ {
+ try
+ {
+ var type = ass.GetType(patcherPlugin.TypeName);
+
+ var methods = type.GetMethods(ALL);
+
+ patcherPlugin.Initializer = CreateDelegate(methods.FirstOrDefault(m => m.Name.Equals("Initialize", StringComparison.InvariantCultureIgnoreCase) &&
+ m.GetParameters().Length == 0 &&
+ m.ReturnType == typeof(void)));
+
+ patcherPlugin.Finalizer = CreateDelegate(methods.FirstOrDefault(m => m.Name.Equals("Finish", StringComparison.InvariantCultureIgnoreCase) &&
+ m.GetParameters().Length == 0 &&
+ m.ReturnType == typeof(void)));
+
+ patcherPlugin.TargetDLLs = CreateDelegate>>(type.GetProperty("TargetDLLs", ALL).GetGetMethod());
+
+ var patcher = methods.FirstOrDefault(m => m.Name.Equals("Patch", StringComparison.CurrentCultureIgnoreCase) &&
+ m.ReturnType == typeof(void) &&
+ m.GetParameters().Length == 1 &&
+ (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);
+ }
+ 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());
+ }
}
- catch (BadImageFormatException) { } //unmanaged DLL
- catch (ReflectionTypeLoadException) { } //invalid references
+
+ Logger.Log(patcherCollection.Any() ? LogLevel.Info : LogLevel.Debug,
+ $"Loaded {patcherCollection.Count} patcher methods from {ass.GetName().FullName}");
+ }
foreach (KeyValuePair patcher in sortedPatchers)
AddPatcher(patcher.Value);
@@ -123,10 +205,10 @@ namespace BepInEx.Preloader.Patching
// Then, perform the actual patching
var patchedAssemblies = new HashSet();
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}]");
+ Logger.LogInfo($"Patching [{assembly.Name.Name}] with [{assemblyPatcher.TypeName}]");
assemblyPatcher.Patcher?.Invoke(ref assembly);
assemblies[targetDll] = assembly;
@@ -135,7 +217,6 @@ namespace BepInEx.Preloader.Patching
// Finally, load patched assemblies into memory
-
if (ConfigDumpAssemblies.Value || ConfigLoadDumpedAssemblies.Value)
{
if (!Directory.Exists(DumpedAssembliesPath))
@@ -153,7 +234,7 @@ namespace BepInEx.Preloader.Patching
if (ConfigBreakBeforeLoadAssemblies.Value)
{
- Logger.LogInfo($"BepInEx is about load the following assemblies:\n{string.Join("\n", patchedAssemblies.ToArray())}");
+ Logger.LogInfo(data: $"BepInEx is about load the following assemblies:\n{String.Join("\n", patchedAssemblies.ToArray())}");
Logger.LogInfo($"The assemblies were dumped into {DumpedAssembliesPath}");
Logger.LogInfo("Load any assemblies into the debugger, set breakpoints and continue execution.");
Debugger.Break();
diff --git a/BepInEx.Preloader/Patching/PatcherPlugin.cs b/BepInEx.Preloader/Patching/PatcherPlugin.cs
index b602d4b..390cc88 100644
--- a/BepInEx.Preloader/Patching/PatcherPlugin.cs
+++ b/BepInEx.Preloader/Patching/PatcherPlugin.cs
@@ -1,17 +1,19 @@
using System;
using System.Collections.Generic;
+using System.IO;
+using BepInEx.Bootstrap;
namespace BepInEx.Preloader.Patching
{
///
/// A single assembly patcher.
///
- internal class PatcherPlugin
+ internal class PatcherPlugin : ICacheable
{
///
/// Target assemblies to patch.
///
- public IEnumerable TargetDLLs { get; set; } = null;
+ public Func> TargetDLLs { get; set; } = null;
///
/// Initializer method that is run before any patching occurs.
@@ -29,8 +31,18 @@ namespace BepInEx.Preloader.Patching
public AssemblyPatcherDelegate Patcher { get; set; } = null;
///
- /// Name of the patcher.
+ /// Type name of the patcher.
///
- public string Name { get; set; } = string.Empty;
+ public string TypeName { get; set; } = string.Empty;
+
+ public void Save(BinaryWriter bw)
+ {
+ bw.Write(TypeName);
+ }
+
+ public void Load(BinaryReader br)
+ {
+ TypeName = br.ReadString();
+ }
}
}
\ No newline at end of file
diff --git a/BepInEx.Preloader/Preloader.cs b/BepInEx.Preloader/Preloader.cs
index a30b0a1..fd90677 100644
--- a/BepInEx.Preloader/Preloader.cs
+++ b/BepInEx.Preloader/Preloader.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
-using System.Reflection;
using System.Text;
using BepInEx.Configuration;
using BepInEx.Logging;
@@ -11,6 +10,7 @@ using BepInEx.Preloader.Patching;
using BepInEx.Preloader.RuntimeFixes;
using Mono.Cecil;
using Mono.Cecil.Cil;
+using MonoMod.RuntimeDetour;
using UnityInjector.ConsoleUtil;
using MethodAttributes = Mono.Cecil.MethodAttributes;
@@ -26,24 +26,32 @@ namespace BepInEx.Preloader
///
private static PreloaderConsoleListener PreloaderLog { get; set; }
+ public static bool IsPostUnity2017 { get; } = File.Exists(Path.Combine(Paths.ManagedPath, "UnityEngine.CoreModule.dll"));
+
public static void Run()
{
try
{
AllocateConsole();
- if (ConfigApplyRuntimePatches.Value)
- UnityPatches.Apply();
+ Utility.TryDo(() =>
+ {
+ if (ConfigShimHarmony.Value)
+ HarmonyDetourBridge.Init();
+ }, out var harmonyBridgeException);
+
+ Utility.TryDo(() =>
+ {
+ if (ConfigApplyRuntimePatches.Value)
+ UnityPatches.Apply();
+ }, out var runtimePatchException);
Logger.Sources.Add(TraceLogSource.CreateSource());
PreloaderLog = new PreloaderConsoleListener(ConfigPreloaderCOutLogging.Value);
-
Logger.Listeners.Add(PreloaderLog);
-
- string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} RC1 - {Process.GetCurrentProcess().ProcessName}";
-
+ string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
ConsoleWindow.Title = consoleTile;
Logger.LogMessage(consoleTile);
@@ -53,35 +61,33 @@ namespace BepInEx.Preloader
if (attributes.Length > 0)
{
var attribute = (BuildInfoAttribute)attributes[0];
-
Logger.LogMessage(attribute.Info);
}
-#if UNITY_2018
- Logger.LogMessage("Compiled in Unity v2018 mode");
-#else
- Logger.LogMessage("Compiled in Legacy Unity mode");
-#endif
+ Logger.LogInfo($"Running under Unity v{FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion}");
+ Logger.LogInfo($"CLR runtime version: {Environment.Version}");
+ Logger.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}");
- Logger.LogInfo($"Running under Unity v{Process.GetCurrentProcess().MainModule.FileVersionInfo.FileVersion}");
+ if (harmonyBridgeException != null)
+ Logger.LogWarning($"Failed to enable fix for Harmony for .NET Standard API. Error message: {harmonyBridgeException.Message}");
+
+ if (runtimePatchException != null)
+ Logger.LogWarning($"Failed to apply runtime patches for Mono. See more info in the output log. Error message: {runtimePatchException.Message}");
Logger.LogMessage("Preloader started");
-
AssemblyPatcher.AddPatcher(new PatcherPlugin
- { TargetDLLs = new[] { ConfigEntrypointAssembly.Value },
- Patcher = PatchEntrypoint,
- Name = "BepInEx.Chainloader"
- });
+ {
+ TargetDLLs = () => new[] { ConfigEntrypointAssembly.Value },
+ Patcher = PatchEntrypoint,
+ TypeName = "BepInEx.Chainloader"
+ });
- AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath, GetPatcherMethods);
+ AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
Logger.LogInfo($"{AssemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded");
-
AssemblyPatcher.PatchAndLoad(Paths.ManagedPath);
-
-
AssemblyPatcher.DisposePatchers();
@@ -122,97 +128,6 @@ namespace BepInEx.Preloader
}
}
- ///
- /// Scans the assembly for classes that use the patcher contract, and returns a list of valid patchers.
- ///
- /// The assembly to scan.
- /// A list of assembly patchers that were found in the assembly.
- public static List GetPatcherMethods(Assembly assembly)
- {
- var patcherMethods = new List();
- var flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase;
-
- foreach (var type in assembly.GetExportedTypes())
- try
- {
- if (type.IsInterface)
- continue;
-
- var targetsProperty = type.GetProperty("TargetDLLs",
- flags,
- null,
- typeof(IEnumerable),
- Type.EmptyTypes,
- null);
-
- //first try get the ref patcher method
- var patcher = type.GetMethod("Patch",
- flags,
- null,
- CallingConventions.Any,
- new[] { typeof(AssemblyDefinition).MakeByRefType() },
- null);
-
- if (patcher == null) //otherwise try getting the non-ref patcher method
- patcher = type.GetMethod("Patch",
- flags,
- null,
- CallingConventions.Any,
- new[] { typeof(AssemblyDefinition) },
- null);
-
- if (targetsProperty == null || !targetsProperty.CanRead || patcher == null)
- continue;
-
- var assemblyPatcher = new PatcherPlugin();
-
- assemblyPatcher.Name = $"{assembly.GetName().Name}/{type.FullName}";
- assemblyPatcher.Patcher = (ref AssemblyDefinition ass) =>
- {
- //we do the array fuckery here to get the ref result out
- object[] args = { ass };
-
- patcher.Invoke(null, args);
-
- ass = (AssemblyDefinition)args[0];
- };
-
- assemblyPatcher.TargetDLLs = (IEnumerable)targetsProperty.GetValue(null, null);
-
- var initMethod = type.GetMethod("Initialize",
- flags,
- null,
- CallingConventions.Any,
- Type.EmptyTypes,
- null);
-
- if (initMethod != null)
- assemblyPatcher.Initializer = () => initMethod.Invoke(null, null);
-
- var finalizeMethod = type.GetMethod("Finish",
- flags,
- null,
- CallingConventions.Any,
- Type.EmptyTypes,
- null);
-
- if (finalizeMethod != null)
- assemblyPatcher.Finalizer = () => finalizeMethod.Invoke(null, null);
-
- patcherMethods.Add(assemblyPatcher);
- }
- catch (Exception ex)
- {
- Logger.LogWarning($"Could not load patcher methods from {assembly.GetName().Name}");
- Logger.LogWarning(ex);
- }
-
- Logger.Log(patcherMethods.Count > 0 ? LogLevel.Info : LogLevel.Debug,
- $"Loaded {patcherMethods.Count} patcher methods from {assembly.GetName().Name}");
-
- return patcherMethods;
- }
-
///
/// Inserts BepInEx's own chainloader entrypoint into UnityEngine.
///
@@ -281,9 +196,9 @@ namespace BepInEx.Preloader
il.InsertBefore(ins,
il.Create(OpCodes.Ldnull)); // gameExePath (always null, we initialize the Paths class in Entrypoint
il.InsertBefore(ins,
- il.Create(OpCodes.Ldc_I4_0)); // startConsole (always false, we already load the console in Preloader)
+ il.Create(OpCodes.Ldc_I4_0)); //startConsole (always false, we already load the console in Preloader)
il.InsertBefore(ins,
- il.Create(OpCodes.Call, initMethod)); // Chainloader.Initialize(string gameExePath, bool startConsole = true)
+ il.Create(OpCodes.Call, initMethod)); // Chainloader.Initialize(string gamePath, string managedPath = null, bool startConsole = true)
il.InsertBefore(ins,
il.Create(OpCodes.Call, startMethod));
}
@@ -323,12 +238,8 @@ namespace BepInEx.Preloader
"Preloader.Entrypoint",
"Assembly",
"The local filename of the assembly to target.",
-#if UNITY_2018
- "UnityEngine.CoreModule.dll"
-#else
- "UnityEngine.dll"
-#endif
- );
+ IsPostUnity2017 ? "UnityEngine.CoreModule.dll" : "UnityEngine.dll"
+ );
private static readonly ConfigWrapper ConfigEntrypointType = ConfigFile.CoreConfig.Wrap(
"Preloader.Entrypoint",
@@ -348,6 +259,12 @@ namespace BepInEx.Preloader
"Enables or disables runtime patches.\nThis should always be true, unless you cannot start the game due to a Harmony related issue (such as running .NET Standard runtime) or you know what you're doing.",
true);
+ private static readonly ConfigWrapper ConfigShimHarmony = ConfigFile.CoreConfig.Wrap(
+ "Preloader",
+ "ShimHarmonySupport",
+ "If enabled, basic Harmony functionality is patched to use MonoMod's RuntimeDetour instead.\nTry using this if Harmony does not work in a game.",
+ !Utility.CLRSupportsDynamicAssemblies);
+
private static readonly ConfigWrapper ConfigPreloaderCOutLogging = ConfigFile.CoreConfig.Wrap(
"Logging",
"PreloaderConsoleOutRedirection",
diff --git a/BepInEx.Preloader/RuntimeFixes/UnityPatches.cs b/BepInEx.Preloader/RuntimeFixes/UnityPatches.cs
index a6a8c4d..6d406ac 100644
--- a/BepInEx.Preloader/RuntimeFixes/UnityPatches.cs
+++ b/BepInEx.Preloader/RuntimeFixes/UnityPatches.cs
@@ -39,18 +39,5 @@ namespace BepInEx.Preloader.RuntimeFixes
if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
__result = $"file://{location.Replace('\\', '/')}";
}
-
-#if UNITY_2018
-/*
- * DESC: Workaround for Trace class not working because of missing .config file
- * AFFECTS: Unity 2018+ (not .NET Standard / MonoBleedingEdge runtimes)
- */
- [HarmonyPostfix, HarmonyPatch(typeof(AppDomain), nameof(AppDomain.SetupInformation), MethodType.Getter)]
- public static void GetExeConfigName(AppDomainSetup __result)
- {
- __result.ApplicationBase = $"file://{Paths.GameRootPath}";
- __result.ConfigurationFile = "app.config";
- }
-#endif
}
}
\ No newline at end of file
diff --git a/BepInEx.Preloader/packages.config b/BepInEx.Preloader/packages.config
index 020bed6..2fbe0b1 100644
--- a/BepInEx.Preloader/packages.config
+++ b/BepInEx.Preloader/packages.config
@@ -1,4 +1,5 @@
+
\ No newline at end of file
diff --git a/BepInEx.sln b/BepInEx.sln
index 2538f19..3c6a5ae 100644
--- a/BepInEx.sln
+++ b/BepInEx.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2027
+VisualStudioVersion = 15.0.28307.757
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx", "BepInEx\BepInEx.csproj", "{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}"
EndProject
@@ -35,82 +35,44 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
- Legacy|Any CPU = Legacy|Any CPU
Release|Any CPU = Release|Any CPU
- v2018|Any CPU = v2018|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Legacy|Any CPU.ActiveCfg = Legacy|Any CPU
- {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Legacy|Any CPU.Build.0 = Legacy|Any CPU
- {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.ActiveCfg = v2018|Any CPU
- {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.Build.0 = v2018|Any CPU
- {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
- {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018|Any CPU.Build.0 = v2018|Any CPU
- {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.ActiveCfg = v2018|Any CPU
- {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.Build.0 = v2018|Any CPU
- {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Any CPU.ActiveCfg = Legacy|Any CPU
- {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Any CPU.Build.0 = Legacy|Any CPU
- {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.ActiveCfg = v2018|Any CPU
- {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.Build.0 = v2018|Any CPU
- {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
- {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.Build.0 = v2018|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.Build.0 = Release|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.Build.0 = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Any CPU.Build.0 = Release|Any CPU
- {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
- {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Legacy|Any CPU.Build.0 = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Release|Any CPU.Build.0 = Release|Any CPU
- {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018|Any CPU.ActiveCfg = Release|Any CPU
- {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018|Any CPU.Build.0 = Release|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
- {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Legacy|Any CPU.Build.0 = Release|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release|Any CPU.Build.0 = Release|Any CPU
- {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Any CPU.ActiveCfg = Release|Any CPU
- {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Any CPU.Build.0 = Release|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
- {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Legacy|Any CPU.Build.0 = Release|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release|Any CPU.Build.0 = Release|Any CPU
- {A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Any CPU.ActiveCfg = Release|Any CPU
- {A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Any CPU.Build.0 = Release|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Legacy|Any CPU.ActiveCfg = Legacy|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Legacy|Any CPU.Build.0 = Legacy|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.ActiveCfg = v2018|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.Build.0 = v2018|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018|Any CPU.Build.0 = v2018|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.Build.0 = Release|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.Build.0 = Release|Any CPU
{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
- {D0C584C0-81D7-486E-B70E-D7F9256E0909}.Legacy|Any CPU.Build.0 = Release|Any CPU
{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Any CPU.Build.0 = Release|Any CPU
- {D0C584C0-81D7-486E-B70E-D7F9256E0909}.v2018|Any CPU.ActiveCfg = Release|Any CPU
- {D0C584C0-81D7-486E-B70E-D7F9256E0909}.v2018|Any CPU.Build.0 = Release|Any CPU
{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
- {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Legacy|Any CPU.Build.0 = Release|Any CPU
{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Any CPU.Build.0 = Release|Any CPU
- {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.v2018|Any CPU.ActiveCfg = Release|Any CPU
- {1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.v2018|Any CPU.Build.0 = Release|Any CPU
{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.ActiveCfg = Debug|x86
{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.Build.0 = Debug|x86
- {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Legacy|Any CPU.ActiveCfg = Release|x86
- {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Legacy|Any CPU.Build.0 = Release|x86
{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release|Any CPU.ActiveCfg = Release|x86
- {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.v2018|Any CPU.ActiveCfg = Release|x86
- {E7CD429A-D057-48E3-8C51-E5C934E8E07B}.v2018|Any CPU.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/BepInEx/BepInEx.csproj b/BepInEx/BepInEx.csproj
index fdb963c..0c61db8 100644
--- a/BepInEx/BepInEx.csproj
+++ b/BepInEx/BepInEx.csproj
@@ -13,7 +13,7 @@
true
-
+
AnyCPU
none
true
@@ -37,7 +37,7 @@
bin\Debug\
- DEBUG;TRACE;UNITY_2018
+ TRACE;DEBUG
false
@@ -70,6 +70,7 @@
+
diff --git a/BepInEx/Bootstrap/Chainloader.cs b/BepInEx/Bootstrap/Chainloader.cs
index ab3bb79..d877cc1 100644
--- a/BepInEx/Bootstrap/Chainloader.cs
+++ b/BepInEx/Bootstrap/Chainloader.cs
@@ -8,6 +8,8 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
+using BepInEx.Contract;
+using Mono.Cecil;
using UnityEngine;
using UnityInjector.ConsoleUtil;
using Logger = BepInEx.Logging.Logger;
@@ -22,7 +24,9 @@ namespace BepInEx.Bootstrap
///
/// The loaded and initialized list of plugins.
///
- public static List Plugins { get; private set; } = new List();
+ public static Dictionary PluginInfos { get; } = new Dictionary();
+
+ public static List Plugins { get; } = new List();
///
/// The GameObject that all plugins are attached to as components.
@@ -34,15 +38,14 @@ namespace BepInEx.Bootstrap
private static bool _initialized = false;
///
- /// Initializes BepInEx to be able to start the chainloader.
- ///
- public static void Initialize(string gameExePath, bool startConsole = true)
+ /// Initializes BepInEx to be able to start the chainloader.
+ ///
+ public static void Initialize(string gameExePath, bool startConsole = true)
{
if (_initialized)
return;
// Set vitals
-
if (gameExePath != null)
{
// Checking for null allows a more advanced initialization workflow, where the Paths class has been initialized before calling Chainloader.Initialize
@@ -50,14 +53,12 @@ namespace BepInEx.Bootstrap
Paths.SetExecutablePath(gameExePath);
}
- Paths.SetPluginPath(ConfigPluginsDirectory.Value);
-
- // Start logging
- if (ConsoleWindow.ConfigConsoleEnabled.Value && startConsole)
+ // Start logging
+ if (ConsoleWindow.ConfigConsoleEnabled.Value && startConsole)
{
ConsoleWindow.Attach();
Logger.Listeners.Add(new ConsoleLogListener());
- }
+ }
// Fix for standard output getting overwritten by UnityLogger
if (ConsoleWindow.StandardOut != null)
@@ -69,7 +70,7 @@ namespace BepInEx.Bootstrap
Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
}
- Logger.Listeners.Add(new UnityLogListener());
+ Logger.Listeners.Add(new UnityLogListener());
Logger.Listeners.Add(new DiskLogListener());
if (!TraceLogSource.IsListening)
@@ -84,7 +85,71 @@ namespace BepInEx.Bootstrap
_initialized = true;
}
- private static Regex allowedGuidRegex { get; } = new Regex(@"^[a-zA-Z0-9\._]+$");
+ private static Regex allowedGuidRegex { get; } = new Regex(@"^[a-zA-Z0-9\._\-]+$");
+
+ public static PluginInfo ToPluginInfo(TypeDefinition type)
+ {
+ if (type.IsInterface || type.IsAbstract || !type.IsSubtypeOf(typeof(BaseUnityPlugin)))
+ return null;
+
+ var metadata = BepInPlugin.FromCecilType(type);
+
+ if (metadata == null)
+ {
+ Logger.LogWarning($"Skipping over type [{type.FullName}] as no metadata attribute is specified");
+ return null;
+ }
+
+ if (string.IsNullOrEmpty(metadata.GUID) || !allowedGuidRegex.IsMatch(metadata.GUID))
+ {
+ Logger.LogWarning($"Skipping type [{type.FullName}] because its GUID [{metadata.GUID}] is of an illegal format.");
+ return null;
+ }
+
+ if (metadata.Version == null)
+ {
+ Logger.LogWarning($"Skipping type [{type.FullName}] because its version is invalid.");
+ return null;
+ }
+
+ if (metadata.Name == null)
+ {
+ Logger.LogWarning($"Skipping type [{type.FullName}] because its name is null.");
+ return null;
+ }
+
+ //Perform a filter for currently running process
+ var filters = BepInProcess.FromCecilType(type);
+ bool invalidProcessName = filters.Count != 0 && filters.All(x => !string.Equals(x.ProcessName.Replace(".exe", ""), Paths.ProcessName, StringComparison.InvariantCultureIgnoreCase));
+
+ if (invalidProcessName)
+ {
+ Logger.LogWarning($"Skipping over plugin [{metadata.GUID}] due to process filter");
+ return null;
+ }
+
+ var dependencies = BepInDependency.FromCecilType(type);
+
+ return new PluginInfo
+ {
+ Metadata = metadata,
+ Processes = filters,
+ Dependencies = dependencies,
+ TypeName = type.FullName
+ };
+ }
+
+ private static readonly string CurrentAssemblyName = Assembly.GetExecutingAssembly().GetName().Name;
+
+ private static bool HasBepinPlugins(AssemblyDefinition ass)
+ {
+ if (ass.MainModule.AssemblyReferences.All(r => r.Name != CurrentAssemblyName))
+ return false;
+ if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(BaseUnityPlugin).FullName))
+ return false;
+
+ return true;
+ }
///
/// The entrypoint for the BepInEx plugin system.
@@ -107,8 +172,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");
@@ -116,76 +180,34 @@ namespace BepInEx.Bootstrap
UnityEngine.Object.DontDestroyOnLoad(ManagerObject);
+ var pluginsToLoad = TypeLoader.FindPluginTypes(Paths.PluginPath, ToPluginInfo, HasBepinPlugins, "chainloader");
+ foreach (var keyValuePair in pluginsToLoad)
+ foreach (var pluginInfo in keyValuePair.Value)
+ pluginInfo.Location = keyValuePair.Key;
+ var pluginInfos = pluginsToLoad.SelectMany(p => p.Value).ToList();
+ var loadedAssemblies = new Dictionary();
- string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();
-
- var globalPluginTypes = TypeLoader.LoadTypes(Paths.PluginPath).ToList();
-
- Dictionary selectedPluginTypes = new Dictionary(globalPluginTypes.Count);
-
- foreach (var pluginType in globalPluginTypes)
- {
- //Ensure metadata exists
- var metadata = MetadataHelper.GetMetadata(pluginType);
-
- if (metadata == null)
- {
- Logger.LogWarning($"Skipping type [{pluginType.FullName}] as no metadata attribute is specified");
- continue;
- }
-
- if (string.IsNullOrEmpty(metadata.GUID) || !allowedGuidRegex.IsMatch(metadata.GUID))
- {
- Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its GUID [{metadata.GUID}] is of an illegal format.");
- continue;
- }
-
- if (selectedPluginTypes.Any(x => x.Value.GUID.Equals(metadata.GUID, StringComparison.OrdinalIgnoreCase)))
- {
- Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its GUID [{metadata.GUID}] is already used by another plugin.");
- continue;
- }
-
- if (metadata.Version == null)
- {
- Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its version is invalid.");
- continue;
- }
-
- if (metadata.Name == null)
- {
- Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its name is null.");
- continue;
- }
-
- //Perform a filter for currently running process
- var filters = MetadataHelper.GetAttributes(pluginType);
-
- if (filters.Length != 0)
- {
- var result = filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);
-
- if (!result)
- {
- Logger.LogInfo($"Skipping over plugin [{metadata.GUID}] due to process filter");
- continue;
- }
- }
-
- selectedPluginTypes.Add(pluginType, metadata);
- }
-
- Logger.LogInfo($"{selectedPluginTypes.Count} / {globalPluginTypes.Count} plugins to load");
+ Logger.LogInfo($"{pluginInfos.Count} plugins to load");
var dependencyDict = new Dictionary>();
- var pluginsByGUID = new Dictionary();
+ var pluginsByGUID = new Dictionary();
- foreach (var kv in selectedPluginTypes)
+ foreach (var pluginInfo in pluginInfos)
{
- var dependencies = MetadataHelper.GetDependencies(kv.Key, selectedPluginTypes.Keys);
+ if (pluginInfo.Metadata.GUID == null)
+ {
+ Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because it does not have a valid GUID.");
+ continue;
+ }
- dependencyDict[kv.Value.GUID] = dependencies.Select(d => d.DependencyGUID);
- pluginsByGUID[kv.Value.GUID] = kv.Key;
+ if (dependencyDict.ContainsKey(pluginInfo.Metadata.GUID))
+ {
+ Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because its GUID ({pluginInfo.Metadata.GUID}) is already used by another plugin.");
+ continue;
+ }
+
+ dependencyDict[pluginInfo.Metadata.GUID] = pluginInfo.Dependencies.Select(d => d.DependencyGUID);
+ pluginsByGUID[pluginInfo.Metadata.GUID] = pluginInfo;
}
var emptyDependencies = new string[0];
@@ -200,14 +222,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.Keys);
var dependsOnInvalidPlugin = false;
var missingDependencies = new List();
- 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))
@@ -230,14 +250,14 @@ 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())
+ 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);
@@ -246,14 +266,26 @@ 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.Location, out var ass))
+ loadedAssemblies[pluginInfo.Location] = ass = Assembly.LoadFile(pluginInfo.Location);
+
+ PluginInfos[pluginGUID] = pluginInfo;
+ pluginInfo.Instance = (BaseUnityPlugin)ManagerObject.AddComponent(ass.GetType(pluginInfo.TypeName));
+
+ Plugins.Add(pluginInfo.Instance);
}
catch (Exception ex)
{
invalidPlugins.Add(pluginGUID);
- Logger.LogError($"Error loading [{metadata.Name}] : {ex.Message}");
- Logger.LogDebug(ex);
+ PluginInfos.Remove(pluginGUID);
+
+ Logger.LogError($"Error loading [{pluginInfo.Metadata.Name}] : {ex.Message}");
+ if (ex is ReflectionTypeLoadException re)
+ Logger.LogDebug(TypeLoader.TypeLoadExceptionToString(re));
+ else
+ Logger.LogDebug(ex);
}
}
}
@@ -272,17 +304,9 @@ namespace BepInEx.Bootstrap
#region Config
- private static readonly ConfigWrapper ConfigPluginsDirectory = ConfigFile.CoreConfig.Wrap(
- "Paths",
- "PluginsDirectory",
- "The relative directory to the BepInEx folder where plugins are loaded.",
- "plugins");
+ private static readonly ConfigWrapper ConfigPluginsDirectory = ConfigFile.CoreConfig.Wrap("Paths", "PluginsDirectory", "The relative directory to the BepInEx folder where plugins are loaded.", "plugins");
- private static readonly ConfigWrapper ConfigUnityLogging = ConfigFile.CoreConfig.Wrap(
- "Logging",
- "UnityLogListening",
- "Enables showing unity log messages in the BepInEx logging system.",
- true);
+ private static readonly ConfigWrapper ConfigUnityLogging = ConfigFile.CoreConfig.Wrap("Logging", "UnityLogListening", "Enables showing unity log messages in the BepInEx logging system.", true);
#endregion
}
diff --git a/BepInEx/Bootstrap/TypeLoader.cs b/BepInEx/Bootstrap/TypeLoader.cs
index 180e037..6bec362 100644
--- a/BepInEx/Bootstrap/TypeLoader.cs
+++ b/BepInEx/Bootstrap/TypeLoader.cs
@@ -1,56 +1,235 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Reflection;
using System.Text;
+using BepInEx.Configuration;
using BepInEx.Logging;
+using Mono.Cecil;
namespace BepInEx.Bootstrap
{
///
- /// Provides methods for loading specified types from an assembly.
+ /// A cacheable metadata item. Can be used with and to cache plugin metadata.
+ ///
+ public interface ICacheable
+ {
+ ///
+ /// Serialize the object into a binary format.
+ ///
+ ///
+ void Save(BinaryWriter bw);
+
+ ///
+ /// Loads the object from binary format.
+ ///
+ ///
+ void Load(BinaryReader br);
+ }
+
+ ///
+ /// A cached assembly.
+ ///
+ ///
+ public class CachedAssembly where T : ICacheable
+ {
+ ///
+ /// List of cached items inside the assembly.
+ ///
+ public List CacheItems { get; set; }
+
+
+ ///
+ /// Timestamp of the assembly. Used to check the age of the cache.
+ ///
+ public long Timestamp { get; set; }
+ }
+
+ ///
+ /// Provides methods for loading specified types from an assembly.
///
public static class TypeLoader
{
+ private static readonly DefaultAssemblyResolver resolver;
+ private static readonly 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 var assembly) ||
+ Utility.TryResolveDllAssembly(name, Paths.PluginPath, readerParameters, out assembly) ||
+ Utility.TryResolveDllAssembly(name, Paths.ManagedPath, readerParameters, out assembly))
+ return assembly;
+
+ return AssemblyResolve?.Invoke(sender, reference);
+ };
+ }
+
+ public static event AssemblyResolveEventHandler AssemblyResolve;
+
///
- /// Loads a list of types from a directory containing assemblies, that derive from a base type.
+ /// Looks up assemblies in the given directory and locates all types that can be loaded and collects their metadata.
///
/// The specific base type to search for.
/// The directory to search for assemblies.
- /// Returns a list of found derivative types.
- public static IEnumerable LoadTypes(string directory)
+ /// A function to check if a type should be selected and to build the type metadata.
+ /// A filter function to quickly determine if the assembly can be loaded.
+ /// The name of the cache to get cached types from.
+ /// A list of all loadable type metadatas indexed by the full path to the assembly that contains the types.
+ public static Dictionary> FindPluginTypes(string directory, Func typeSelector, Func assemblyFilter = null, string cacheName = null) where T : ICacheable, new()
{
- List types = new List();
- Type pluginType = typeof(T);
+ var result = new Dictionary>();
+ Dictionary> cache = null;
+
+ if (cacheName != null)
+ cache = LoadAssemblyCache(cacheName);
foreach (string dll in Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories))
- {
try
{
- AssemblyName an = AssemblyName.GetAssemblyName(dll);
- Assembly assembly = Assembly.Load(an);
-
- foreach (Type type in assembly.GetTypes())
+ if (cache != null && cache.TryGetValue(dll, out var cacheEntry))
{
- if (!type.IsInterface && !type.IsAbstract && pluginType.IsAssignableFrom(type))
- types.Add(type);
+ long lastWrite = File.GetLastWriteTimeUtc(dll).Ticks;
+ if (lastWrite == cacheEntry.Timestamp)
+ {
+ result[dll] = cacheEntry.CacheItems;
+ continue;
+ }
}
- }
- catch (BadImageFormatException) { } //unmanaged DLL
- catch (ReflectionTypeLoadException ex)
- {
- Logger.LogError($"Could not load \"{Path.GetFileName(dll)}\" as a plugin!");
- Logger.LogDebug(TypeLoadExceptionToString(ex));
- }
- }
- return types;
+ var ass = AssemblyDefinition.ReadAssembly(dll, readerParameters);
+
+ if (!assemblyFilter?.Invoke(ass) ?? false)
+ {
+ ass.Dispose();
+ continue;
+ }
+
+ var matches = ass.MainModule.Types.Select(typeSelector).Where(t => t != null).ToList();
+
+ if (matches.Count == 0)
+ {
+ ass.Dispose();
+ continue;
+ }
+
+ result[dll] = matches;
+ ass.Dispose();
+ }
+ catch (Exception e)
+ {
+ Logger.LogError(e.ToString());
+ }
+
+ if (cacheName != null)
+ SaveAssemblyCache(cacheName, result);
+
+ return result;
}
- private static string TypeLoadExceptionToString(ReflectionTypeLoadException ex)
+ ///
+ /// Loads an index of type metadatas from a cache.
+ ///
+ /// Name of the cache
+ /// Cacheable item
+ /// Cached type metadatas indexed by the path of the assembly that defines the type. If no cache is defined, return null.
+ public static Dictionary> LoadAssemblyCache(string cacheName) where T : ICacheable, new()
{
- StringBuilder sb = new StringBuilder();
- foreach (Exception exSub in ex.LoaderExceptions)
+ if (!EnableAssemblyCache.Value)
+ return null;
+
+ var result = new Dictionary>();
+ try
+ {
+ string path = Path.Combine(Paths.CachePath, $"{cacheName}_typeloader.dat");
+ if (!File.Exists(path))
+ return null;
+
+ using (var br = new BinaryReader(File.OpenRead(path)))
+ {
+ int entriesCount = br.ReadInt32();
+
+ for (var i = 0; i < entriesCount; i++)
+ {
+ string entryIdentifier = br.ReadString();
+ long entryDate = br.ReadInt64();
+ int itemsCount = br.ReadInt32();
+ var items = new List();
+
+ for (var j = 0; j < itemsCount; j++)
+ {
+ var entry = new T();
+ entry.Load(br);
+ items.Add(entry);
+ }
+
+ result[entryIdentifier] = new CachedAssembly { Timestamp = entryDate, CacheItems = items };
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.LogWarning($"Failed to load cache \"{cacheName}\"; skipping loading cache. Reason: {e.Message}.");
+ }
+
+ return result;
+ }
+
+ ///
+ /// Saves indexed type metadata into a cache.
+ ///
+ /// Name of the cache
+ /// List of plugin metadatas indexed by the path to the assembly that contains the types
+ /// Cacheable item
+ public static void SaveAssemblyCache(string cacheName, Dictionary> entries) where T : ICacheable
+ {
+ if (!EnableAssemblyCache.Value)
+ return;
+
+ try
+ {
+ if (!Directory.Exists(Paths.CachePath))
+ Directory.CreateDirectory(Paths.CachePath);
+
+ string path = Path.Combine(Paths.CachePath, $"{cacheName}_typeloader.dat");
+
+ using (var bw = new BinaryWriter(File.OpenWrite(path)))
+ {
+ bw.Write(entries.Count);
+
+ foreach (var kv in entries)
+ {
+ bw.Write(kv.Key);
+ bw.Write(File.GetLastWriteTimeUtc(kv.Key).Ticks);
+ bw.Write(kv.Value.Count);
+
+ foreach (var item in kv.Value)
+ item.Save(bw);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.LogWarning($"Failed to save cache \"{cacheName}\"; skipping saving cache. Reason: {e.Message}.");
+ }
+ }
+
+ ///
+ /// Converts TypeLoadException to a readable string.
+ ///
+ /// TypeLoadException
+ /// Readable representation of the exception
+ public static string TypeLoadExceptionToString(ReflectionTypeLoadException ex)
+ {
+ var sb = new StringBuilder();
+ foreach (var exSub in ex.LoaderExceptions)
{
sb.AppendLine(exSub.Message);
if (exSub is FileNotFoundException exFileNotFound)
@@ -75,5 +254,15 @@ namespace BepInEx.Bootstrap
return sb.ToString();
}
+
+ #region Config
+
+ private static ConfigWrapper EnableAssemblyCache = ConfigFile.CoreConfig.Wrap(
+ "Caching",
+ "EnableAssemblyCache",
+ "Enable/disable assembly metadata cache\nEnabling this will speed up discovery of plugins and patchers by caching the metadata of all types BepInEx discovers.",
+ true);
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/BepInEx/Configuration/ConfigFile.cs b/BepInEx/Configuration/ConfigFile.cs
index 32b2478..6c4f33d 100644
--- a/BepInEx/Configuration/ConfigFile.cs
+++ b/BepInEx/Configuration/ConfigFile.cs
@@ -54,7 +54,7 @@ namespace BepInEx.Configuration
/// The plugin that owns this setting.
public ConfigFile(string configPath, bool saveOnInit, BaseUnityPlugin owner = null)
{
- _ownerMetadata = owner?.Metadata;
+ _ownerMetadata = owner?.Info.Metadata;
if (configPath == null) throw new ArgumentNullException(nameof(configPath));
configPath = Path.GetFullPath(configPath);
diff --git a/BepInEx/Configuration/TomlTypeConverter.cs b/BepInEx/Configuration/TomlTypeConverter.cs
index 2376653..9f41b06 100644
--- a/BepInEx/Configuration/TomlTypeConverter.cs
+++ b/BepInEx/Configuration/TomlTypeConverter.cs
@@ -150,4 +150,4 @@ namespace BepInEx.Configuration
return TypeConverters.Keys;
}
}
-}
+}
\ No newline at end of file
diff --git a/BepInEx/ConsoleUtil/ConsoleWindow.cs b/BepInEx/ConsoleUtil/ConsoleWindow.cs
index 87682bf..9c9e9b6 100644
--- a/BepInEx/ConsoleUtil/ConsoleWindow.cs
+++ b/BepInEx/ConsoleUtil/ConsoleWindow.cs
@@ -25,7 +25,7 @@ namespace UnityInjector.ConsoleUtil
"If true, console is set to the Shift-JIS encoding, otherwise UTF-8 encoding.",
false);
- public static bool IsAttached { get; private set; }
+ public static bool IsAttached { get; private set; }
private static IntPtr _cOut;
private static IntPtr _oOut;
diff --git a/BepInEx/Contract/Attributes.cs b/BepInEx/Contract/Attributes.cs
index 3e2eddf..eff3e92 100644
--- a/BepInEx/Contract/Attributes.cs
+++ b/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
{
@@ -46,6 +50,16 @@ namespace BepInEx
Version = null;
}
}
+
+ internal static BepInPlugin FromCecilType(TypeDefinition td)
+ {
+ var attr = MetadataHelper.GetCustomAttributes(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);
+ }
}
///
@@ -84,6 +98,12 @@ namespace BepInEx
this.DependencyGUID = DependencyGUID;
this.Flags = Flags;
}
+
+ internal static IEnumerable FromCecilType(TypeDefinition td)
+ {
+ var attrs = MetadataHelper.GetCustomAttributes(td, true);
+ return attrs.Select(customAttribute => new BepInDependency((string)customAttribute.ConstructorArguments[0].Value, (DependencyFlags)customAttribute.ConstructorArguments[1].Value)).ToList();
+ }
}
///
@@ -102,6 +122,12 @@ namespace BepInEx
{
this.ProcessName = ProcessName;
}
+
+ internal static List FromCecilType(TypeDefinition td)
+ {
+ var attrs = MetadataHelper.GetCustomAttributes(td, true);
+ return attrs.Select(customAttribute => new BepInProcess((string)customAttribute.ConstructorArguments[0].Value)).ToList();
+ }
}
#endregion
@@ -113,6 +139,22 @@ namespace BepInEx
///
public static class MetadataHelper
{
+ internal static IEnumerable GetCustomAttributes(TypeDefinition td, bool inherit) where T : Attribute
+ {
+ var result = new List();
+ 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");
+
+
+ return result;
+ }
+
///
/// Retrieves the BepInPlugin metadata from a plugin type.
///
@@ -159,10 +201,9 @@ namespace BepInEx
///
/// Retrieves the dependencies of the specified plugin type.
///
- /// The plugin type.
- /// All currently loaded plugin types.
+ /// The plugin type.
/// A list of all plugin types that the specified plugin type depends upon.
- public static IEnumerable GetDependencies(Type plugin, IEnumerable allPlugins)
+ public static IEnumerable GetDependencies(Type plugin)
{
return plugin.GetCustomAttributes(typeof(BepInDependency), true).Cast();
}
diff --git a/BepInEx/Contract/BaseUnityPlugin.cs b/BepInEx/Contract/BaseUnityPlugin.cs
index 7781a46..7a74859 100644
--- a/BepInEx/Contract/BaseUnityPlugin.cs
+++ b/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;
@@ -12,8 +14,7 @@ namespace BepInEx
///
/// Information about this plugin as it was loaded.
///
- public BepInPlugin Metadata { get; }
-
+ public PluginInfo Info { get; }
///
/// Logger instance tied to this plugin.
///
@@ -30,11 +31,25 @@ namespace BepInEx
///
protected BaseUnityPlugin()
{
- Metadata = MetadataHelper.GetMetadata(this);
+ var metadata = MetadataHelper.GetMetadata(this);
- Logger = Logging.Logger.CreateLogSource(Metadata.Name);
+ if (Chainloader.PluginInfos.TryGetValue(metadata.GUID, out var info))
+ Info = info;
+ else
+ {
+ Info = new PluginInfo
+ {
+ Metadata = metadata,
+ Instance = this,
+ Dependencies = MetadataHelper.GetDependencies(GetType()),
+ Processes = MetadataHelper.GetAttributes(GetType()),
+ Location = GetType().Assembly.Location
+ };
+ }
- Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, Metadata.GUID + ".cfg"), false, this);
+ Logger = Logging.Logger.CreateLogSource(metadata.Name);
+
+ Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, this);
}
}
}
\ No newline at end of file
diff --git a/BepInEx/Contract/PluginInfo.cs b/BepInEx/Contract/PluginInfo.cs
new file mode 100644
index 0000000..5cf8506
--- /dev/null
+++ b/BepInEx/Contract/PluginInfo.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using BepInEx.Bootstrap;
+
+namespace BepInEx.Contract
+{
+ public class PluginInfo : ICacheable
+ {
+ public BepInPlugin Metadata { get; internal set; }
+
+ public IEnumerable Processes { get; internal set; }
+
+ public IEnumerable Dependencies { get; internal set; }
+
+ public string Location { get; internal set; }
+
+ public BaseUnityPlugin Instance { get; internal set; }
+
+ internal string TypeName { get; set; }
+
+ public void Save(BinaryWriter bw)
+ {
+ bw.Write(TypeName);
+
+ bw.Write(Metadata.GUID);
+ bw.Write(Metadata.Name);
+ bw.Write(Metadata.Version.ToString());
+
+ var processList = Processes.ToList();
+ bw.Write(processList.Count);
+ foreach (var bepInProcess in processList)
+ bw.Write(bepInProcess.ProcessName);
+
+ var depList = Dependencies.ToList();
+ bw.Write(depList.Count);
+ foreach (var bepInDependency in depList)
+ {
+ bw.Write(bepInDependency.DependencyGUID);
+ bw.Write((int)bepInDependency.Flags);
+ }
+ }
+
+ public void Load(BinaryReader br)
+ {
+ TypeName = br.ReadString();
+
+ Metadata = new BepInPlugin(br.ReadString(), br.ReadString(), br.ReadString());
+
+ var processListCount = br.ReadInt32();
+ var processList = new List(processListCount);
+ for (int i = 0; i < processListCount; i++)
+ processList.Add(new BepInProcess(br.ReadString()));
+ Processes = processList;
+
+ var depCount = br.ReadInt32();
+ var depList = new List(depCount);
+ for (int i = 0; i < depCount; i++)
+ depList.Add(new BepInDependency(br.ReadString(), (BepInDependency.DependencyFlags) br.ReadInt32()));
+ Dependencies = depList;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BepInEx/Logging/ConsoleLogListener.cs b/BepInEx/Logging/ConsoleLogListener.cs
index 86aee3f..ee9d9c2 100644
--- a/BepInEx/Logging/ConsoleLogListener.cs
+++ b/BepInEx/Logging/ConsoleLogListener.cs
@@ -16,7 +16,7 @@ namespace BepInEx.Logging
if (eventArgs.Level.GetHighestLevel() > DisplayedLogLevel)
return;
- string log = $"[{eventArgs.Level, -7}:{((ILogSource)sender).SourceName, 10}] {eventArgs.Data}\r\n";
+ string log = $"[{eventArgs.Level,-7}:{((ILogSource)sender).SourceName,10}] {eventArgs.Data}\r\n";
Kon.ForegroundColor = eventArgs.Level.GetConsoleColor();
Console.Write(log);
diff --git a/BepInEx/Logging/DiskLogListener.cs b/BepInEx/Logging/DiskLogListener.cs
index 65968a2..e2b94cd 100644
--- a/BepInEx/Logging/DiskLogListener.cs
+++ b/BepInEx/Logging/DiskLogListener.cs
@@ -39,12 +39,9 @@ namespace BepInEx.Logging
}
LogWriter = TextWriter.Synchronized(new StreamWriter(fileStream, Encoding.UTF8));
-
- FlushTimer = new Timer(o =>
- {
- LogWriter?.Flush();
- }, null, 2000, 2000);
+
+ FlushTimer = new Timer(o => { LogWriter?.Flush(); }, null, 2000, 2000);
}
public void LogEvent(object sender, LogEventArgs eventArgs)
diff --git a/BepInEx/Logging/TraceLogSource.cs b/BepInEx/Logging/TraceLogSource.cs
index 1d03e91..571b8a8 100644
--- a/BepInEx/Logging/TraceLogSource.cs
+++ b/BepInEx/Logging/TraceLogSource.cs
@@ -76,7 +76,7 @@ namespace BepInEx.Logging
level = LogLevel.Debug;
break;
}
-
+
LogSource.Log(level, $"{message}".Trim());
}
}
diff --git a/BepInEx/Logging/UnityLogListener.cs b/BepInEx/Logging/UnityLogListener.cs
index 0cc4adc..bcf802e 100644
--- a/BepInEx/Logging/UnityLogListener.cs
+++ b/BepInEx/Logging/UnityLogListener.cs
@@ -27,7 +27,7 @@ namespace BepInEx.Logging
WriteStringToUnityLog = (Action)Delegate.CreateDelegate(typeof(Action), methodInfo);
break;
}
-
+
if (WriteStringToUnityLog == null)
Logger.LogError("Unable to start Unity log writer");
}
diff --git a/BepInEx/Logging/UnityLogSource.cs b/BepInEx/Logging/UnityLogSource.cs
index 407a07d..fe04cf3 100644
--- a/BepInEx/Logging/UnityLogSource.cs
+++ b/BepInEx/Logging/UnityLogSource.cs
@@ -24,6 +24,7 @@ namespace BepInEx.Logging
}
private bool disposed = false;
+
public void Dispose()
{
if (!disposed)
diff --git a/BepInEx/Paths.cs b/BepInEx/Paths.cs
index 227d61f..b53616c 100644
--- a/BepInEx/Paths.cs
+++ b/BepInEx/Paths.cs
@@ -21,6 +21,14 @@ namespace BepInEx
PatcherPluginPath = Path.Combine(BepInExRootPath, "patchers");
BepInExAssemblyDirectory = Path.Combine(BepInExRootPath, "core");
BepInExAssemblyPath = Path.Combine(BepInExAssemblyDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.dll");
+ CachePath = Path.Combine(BepInExRootPath, "cache");
+ }
+
+ internal static void SetManagedPath(string managedPath)
+ {
+ if (managedPath == null)
+ return;
+ ManagedPath = managedPath;
}
internal static void SetPluginPath(string pluginPath)
@@ -68,6 +76,11 @@ namespace BepInEx
///
public static string BepInExConfigPath { get; private set; }
+ ///
+ /// The path to temporary cache files.
+ ///
+ public static string CachePath { get; private set; }
+
///
/// The path to the patcher plugin folder which resides in the BepInEx folder.
///
diff --git a/BepInEx/Utility.cs b/BepInEx/Utility.cs
index a88e0cd..67eaa1c 100644
--- a/BepInEx/Utility.cs
+++ b/BepInEx/Utility.cs
@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Reflection.Emit;
+using Mono.Cecil;
namespace BepInEx
{
@@ -12,11 +14,50 @@ namespace BepInEx
public static class Utility
{
///
- /// Combines multiple paths together, as the specific method is not available in .NET 3.5.
+ /// Whether current Common Language Runtime supports dynamic method generation using namespace.
///
- /// The multiple paths to combine together.
- /// A combined path.
- public static string CombinePaths(params string[] parts) => parts.Aggregate(Path.Combine);
+ public static bool CLRSupportsDynamicAssemblies { get; }
+
+ static Utility()
+ {
+ try
+ {
+ var m = new DynamicMethod("SRE_Test", null, null);
+ CLRSupportsDynamicAssemblies = true;
+ }
+ catch (PlatformNotSupportedException)
+ {
+ CLRSupportsDynamicAssemblies = false;
+ }
+ }
+
+ ///
+ /// Try to perform an action.
+ ///
+ /// Action to perform.
+ /// Possible exception that gets returned.
+ /// True, if action succeeded, false if an exception occured.
+ public static bool TryDo(Action action, out Exception exception)
+ {
+ exception = null;
+ try
+ {
+ action();
+ return true;
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ return false;
+ }
+ }
+
+ ///
+ /// Combines multiple paths together, as the specific method is not available in .NET 3.5.
+ ///
+ /// The multiple paths to combine together.
+ /// A combined path.
+ public static string CombinePaths(params string[] parts) => parts.Aggregate(Path.Combine);
///
/// Tries to parse a bool, with a default value if unable to parse.
@@ -26,7 +67,7 @@ namespace BepInEx
/// Boolean value of input if able to be parsed, otherwise default value.
public static bool SafeParseBool(string input, bool defaultValue = false)
{
- return bool.TryParse(input, out bool result) ? result : defaultValue;
+ return Boolean.TryParse(input, out bool result) ? result : defaultValue;
}
///
@@ -46,7 +87,7 @@ namespace BepInEx
/// True if the value parameter is null or empty, or if value consists exclusively of white-space characters.
public static bool IsNullOrWhiteSpace(this string self)
{
- return self == null || self.All(char.IsWhiteSpace);
+ return self == null || self.All(Char.IsWhiteSpace);
}
public static IEnumerable TopologicalSort(IEnumerable nodes, Func> dependencySelector)
@@ -61,9 +102,8 @@ namespace BepInEx
Stack currentStack = new Stack();
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 +147,7 @@ namespace BepInEx
/// Directory to search the assembly from.
/// The loaded assembly.
/// True, if the assembly was found and loaded. Otherwise, false.
- public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly)
+ private static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, Func loader, out T assembly) where T : class
{
assembly = null;
@@ -124,7 +164,7 @@ namespace BepInEx
try
{
- assembly = Assembly.LoadFile(path);
+ assembly = loader(path);
}
catch (Exception)
{
@@ -137,7 +177,47 @@ namespace BepInEx
return false;
}
- public static bool TryOpenFileStream(string path, FileMode mode, out FileStream fileStream, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.None)
+ public static bool IsSubtypeOf(this TypeDefinition self, Type td)
+ {
+ if (self.FullName == td.FullName)
+ return true;
+ return self.FullName != "System.Object" && (self.BaseType?.Resolve()?.IsSubtypeOf(td) ?? false);
+ }
+
+ ///
+ /// Try to resolve and load the given assembly DLL.
+ ///
+ /// Name of the assembly, of the type .
+ /// Directory to search the assembly from.
+ /// The loaded assembly.
+ /// True, if the assembly was found and loaded. Otherwise, false.
+ public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly)
+ {
+ return TryResolveDllAssembly(assemblyName, directory, Assembly.LoadFile, out assembly);
+ }
+
+ ///
+ /// Try to resolve and load the given assembly DLL.
+ ///
+ /// Name of the assembly, of the type .
+ /// Directory to search the assembly from.
+ /// The loaded assembly.
+ /// True, if the assembly was found and loaded. Otherwise, false.
+ public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, ReaderParameters readerParameters, out AssemblyDefinition assembly)
+ {
+ return TryResolveDllAssembly(assemblyName, directory, s => AssemblyDefinition.ReadAssembly(s, readerParameters), out assembly);
+ }
+
+ ///
+ /// Tries to create a file with the given name
+ ///
+ /// Path of the file to create
+ /// File open mode
+ /// Resulting filestream
+ /// File access options
+ /// File share options
+ ///
+ public static bool TryOpenFileStream(string path, FileMode mode, out FileStream fileStream, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.Read)
{
try
{
diff --git a/BepInEx/packages.config b/BepInEx/packages.config
index 020bed6..2fbe0b1 100644
--- a/BepInEx/packages.config
+++ b/BepInEx/packages.config
@@ -1,4 +1,5 @@
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 8fa2d00..0661f24 100644
--- a/README.md
+++ b/README.md
@@ -21,9 +21,9 @@ Unity plugin framework
**[User and developer guides](https://github.com/BepInEx/BepInEx/wiki)**
## Used libraries
-- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - 2.7.1.0 ([df7d636](https://github.com/NeighTools/UnityDoorstop/commit/df7d6366d8dc69f024c61cd31e6f690eb44ce57a))
+- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - 2.11.0.0 ([68a4e2d](https://github.com/NeighTools/UnityDoorstop/commit/68a4e2db1f09e5f5cc2f479d293b09764d09c80b))
- [pardeike/Harmony](https://github.com/pardeike/Harmony) - pre-2.0 ([443f551](https://github.com/pardeike/Harmony/commit/443f551ec45ecf409755b5979a4466343197de03))
-- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v19.05.01.01 ([934a8ae](https://github.com/0x0ade/MonoMod/commit/934a8ae921affac0093757d23c6f3ead34e996ac))
+- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v19.08.02.03 ([a70072c](https://github.com/MonoMod/MonoMod/tree/a70072cdf759ac0cfa80991fcd2cca67d3eec130))
- [jbevain/cecil](https://github.com/jbevain/cecil) - 0.10.3 ([fb289a7](https://github.com/jbevain/cecil/commit/fb289a7cd80ceb6af5c86e7c7ecce9bf1e98b8fe))
## Credits
diff --git a/scripts/jenkins_master.groovy b/scripts/jenkins_master.groovy
index dd6b896..4d19411 100644
--- a/scripts/jenkins_master.groovy
+++ b/scripts/jenkins_master.groovy
@@ -46,12 +46,12 @@ Changes since ${latestTag}:
}
dir('Doorstop') {
- sh ''' tag="v2.7.1.0";
- version="2.7.1.0";
+ sh ''' tag="v2.11.0.0";
+ version="2.11.0.0";
wget https://github.com/NeighTools/UnityDoorstop/releases/download/$tag/Doorstop_x64_$version.zip;
wget https://github.com/NeighTools/UnityDoorstop/releases/download/$tag/Doorstop_x86_$version.zip;
- unzip -o Doorstop_x86_$version.zip winhttp.dll -d x86;
- unzip -o Doorstop_x64_$version.zip winhttp.dll -d x64;'''
+ unzip -o Doorstop_x86_$version.zip version.dll -d x86;
+ unzip -o Doorstop_x64_$version.zip version.dll -d x64;'''
}
}
}
@@ -120,7 +120,7 @@ Changes since ${latestTag}:
sh 'rm -rf BepInEx/core/patcher'
sh 'cp -f ../../../BepInEx/doorstop/doorstop_config.ini doorstop_config.ini'
- sh 'cp -f ../../../Doorstop/x86/winhttp.dll winhttp.dll'
+ sh 'cp -f ../../../Doorstop/x86/version.dll version.dll'
script {
if(params.IS_BE) {
@@ -134,7 +134,7 @@ Changes since ${latestTag}:
sh "zip -r9 BepInEx_Legacy_x86${commitPrefix}${versionNumber}.zip ./*"
- sh 'cp -f ../../../Doorstop/x64/winhttp.dll winhttp.dll'
+ sh 'cp -f ../../../Doorstop/x64/version.dll version.dll'
sh 'unix2dos doorstop_config.ini'
@@ -167,7 +167,7 @@ Changes since ${latestTag}:
sh 'rm -f BepInEx/core/UnityEngine.CoreModule.dll'
sh 'cp -f ../../../BepInEx/doorstop/doorstop_config.ini doorstop_config.ini'
- sh 'cp -f ../../../Doorstop/x86/winhttp.dll winhttp.dll'
+ sh 'cp -f ../../../Doorstop/x86/version.dll version.dll'
script {
if(params.IS_BE) {
@@ -181,7 +181,7 @@ Changes since ${latestTag}:
sh "zip -r9 BepInEx_v2018_x86${commitPrefix}${versionNumber}.zip ./*"
- sh 'cp -f ../../../Doorstop/x64/winhttp.dll winhttp.dll'
+ sh 'cp -f ../../../Doorstop/x64/version.dll version.dll'
sh 'unix2dos doorstop_config.ini'
diff --git a/submodules/MonoMod b/submodules/MonoMod
index 934a8ae..a70072c 160000
--- a/submodules/MonoMod
+++ b/submodules/MonoMod
@@ -1 +1 @@
-Subproject commit 934a8ae921affac0093757d23c6f3ead34e996ac
+Subproject commit a70072cdf759ac0cfa80991fcd2cca67d3eec130