From bd7597fa56a17f65308e36eda2536684b6c1f354 Mon Sep 17 00:00:00 2001 From: ghorsington Date: Wed, 23 Jan 2019 20:35:23 +0200 Subject: [PATCH] Move preloader to a separate project --- BepInEx.Preloader/BepInEx.Preloader.csproj | 72 +++++ .../Entrypoint.cs | 11 +- BepInEx.Preloader/Patcher/AssemblyPatcher.cs | 180 +++++++++++ BepInEx.Preloader/Patcher/PatcherPlugin.cs | 36 +++ BepInEx.Preloader/Preloader.cs | 296 ++++++++++++++++++ BepInEx.Preloader/Properties/AssemblyInfo.cs | 35 +++ BepInEx.Preloader/UnityPatches.cs | 50 +++ BepInEx.sln | 22 ++ BepInEx/BepInEx.csproj | 4 - BepInEx/Bootstrap/AssemblyPatcher.cs | 239 -------------- BepInEx/Bootstrap/Chainloader.cs | 5 +- BepInEx/Bootstrap/Preloader.cs | 293 ----------------- BepInEx/Bootstrap/UnityPatches.cs | 29 -- .../ConsoleEncoding/ConsoleEncoding.cs | 2 +- BepInEx/ConsoleUtil/ConsoleWindow.cs | 2 +- BepInEx/Logging/PreloaderLogWriter.cs | 4 +- BepInEx/Logging/UnityLogWriter.cs | 8 +- 17 files changed, 707 insertions(+), 581 deletions(-) create mode 100644 BepInEx.Preloader/BepInEx.Preloader.csproj rename {BepInEx/Bootstrap => BepInEx.Preloader}/Entrypoint.cs (83%) create mode 100644 BepInEx.Preloader/Patcher/AssemblyPatcher.cs create mode 100644 BepInEx.Preloader/Patcher/PatcherPlugin.cs create mode 100644 BepInEx.Preloader/Preloader.cs create mode 100644 BepInEx.Preloader/Properties/AssemblyInfo.cs create mode 100644 BepInEx.Preloader/UnityPatches.cs delete mode 100644 BepInEx/Bootstrap/AssemblyPatcher.cs delete mode 100644 BepInEx/Bootstrap/Preloader.cs delete mode 100644 BepInEx/Bootstrap/UnityPatches.cs diff --git a/BepInEx.Preloader/BepInEx.Preloader.csproj b/BepInEx.Preloader/BepInEx.Preloader.csproj new file mode 100644 index 0000000..d6e280b --- /dev/null +++ b/BepInEx.Preloader/BepInEx.Preloader.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA} + Library + Properties + BepInEx.Preloader + BepInEx.Preloader + v3.5 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\bin\ + TRACE + prompt + 4 + + + bin\v2018\ + TRACE;UNITY_2018 + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + + + + ..\lib\Mono.Cecil.dll + + + + + + + + + + + + + + + {4ffba620-f5ed-47f9-b90c-dad1316fd9b9} + BepInEx + + + {54161cfe-ff42-4dde-b161-3a49545db5cd} + BepInEx.Harmony + + + {a15d6ee6-f954-415b-8605-8a8470cc87dc} + Harmony + + + + \ No newline at end of file diff --git a/BepInEx/Bootstrap/Entrypoint.cs b/BepInEx.Preloader/Entrypoint.cs similarity index 83% rename from BepInEx/Bootstrap/Entrypoint.cs rename to BepInEx.Preloader/Entrypoint.cs index 5aae57c..89d6b56 100644 --- a/BepInEx/Bootstrap/Entrypoint.cs +++ b/BepInEx.Preloader/Entrypoint.cs @@ -2,9 +2,9 @@ using System.Linq; using System.Reflection; -namespace BepInEx.Bootstrap +namespace BepInEx.Preloader { - public static class Entrypoint + internal static class Entrypoint { /// /// The main entrypoint of BepInEx, called from Doorstop. @@ -15,7 +15,9 @@ namespace BepInEx.Bootstrap /// public static void Main(string[] args) { - Paths.ExecutablePath = args[0]; + // Manually set up the path for patchers to work + typeof(Paths).GetProperty(nameof(Paths.ExecutablePath)).SetValue(null, args[0], null); + //Paths.ExecutablePath = args[0]; AppDomain.CurrentDomain.AssemblyResolve += LocalResolve; Preloader.Run(); @@ -35,7 +37,8 @@ namespace BepInEx.Bootstrap { var assemblyName = new AssemblyName(args.Name); - var foundAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.GetName().Name == assemblyName.Name); + var foundAssembly = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(x => x.GetName().Name == assemblyName.Name); if (foundAssembly != null) return foundAssembly; diff --git a/BepInEx.Preloader/Patcher/AssemblyPatcher.cs b/BepInEx.Preloader/Patcher/AssemblyPatcher.cs new file mode 100644 index 0000000..2c29a45 --- /dev/null +++ b/BepInEx.Preloader/Patcher/AssemblyPatcher.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using BepInEx.Logging; +using Mono.Cecil; + +namespace BepInEx.Preloader.Patcher +{ + /// + /// Delegate used in patching assemblies. + /// + /// The assembly that is being patched. + internal delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly); + + /// + /// Worker class which is used for loading and patching entire folders of assemblies, or alternatively patching and + /// loading assemblies one at a time. + /// + internal static class AssemblyPatcher + { + private static readonly List patchers = new List(); + + /// + /// Configuration value of whether assembly dumping is enabled or not. + /// + private static bool DumpingEnabled => + Utility.SafeParseBool(Config.GetEntry("dump-assemblies", "false", "Preloader")); + + /// + /// Adds a single assembly patcher to the pool of applicable patches. + /// + /// Patcher to apply. + public static void AddPatcher(PatcherPlugin patcher) + { + patchers.Add(patcher); + } + + /// + /// 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) + { + if (!Directory.Exists(directory)) + return; + + var sortedPatchers = new SortedDictionary(); + + foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll")) + try + { + var assembly = Assembly.LoadFrom(assemblyPath); + + foreach (var patcher in patcherLocator(assembly)) + sortedPatchers.Add(patcher.Name, patcher); + } + catch (BadImageFormatException) + { + } //unmanaged DLL + catch (ReflectionTypeLoadException) + { + } //invalid references + + foreach (KeyValuePair patcher in sortedPatchers) + AddPatcher(patcher.Value); + } + + private static void InitializePatchers() + { + foreach (var assemblyPatcher in patchers) + assemblyPatcher.Initializer?.Invoke(); + } + + private static void FinalizePatching() + { + foreach (var assemblyPatcher in patchers) + assemblyPatcher.Finalizer?.Invoke(); + } + + /// + /// Releases all patchers to let them be collected by GC. + /// + public static void DisposePatchers() + { + patchers.Clear(); + } + + /// + /// Applies patchers to all assemblies in the given directory and loads patched assemblies into memory. + /// + /// Directory to load CLR assemblies from. + public static void PatchAndLoad(string directory) + { + // First, load patchable assemblies into Cecil + var assemblies = new Dictionary(); + + foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll")) + { + var assembly = AssemblyDefinition.ReadAssembly(assemblyPath); + + //NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky + //System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency + //It's also generally dangerous to change system.dll since so many things rely on it, + // and it's already loaded into the appdomain since this loader references it, so we might as well skip it + if (assembly.Name.Name == "System" + || assembly.Name.Name == "mscorlib" + ) //mscorlib is already loaded into the appdomain so it can't be patched + { + assembly.Dispose(); + continue; + } + + if (UnityPatches.AssemblyLocations.ContainsKey(assembly.FullName)) + { + Logger.Log(LogLevel.Warning, + $"Tried to load duplicate assembly {Path.GetFileName(assemblyPath)} from Managed folder! Skipping..."); + continue; + } + + assemblies.Add(Path.GetFileName(assemblyPath), assembly); + UnityPatches.AssemblyLocations.Add(assembly.FullName, Path.GetFullPath(assemblyPath)); + } + + // Next, initialize all the patchers + InitializePatchers(); + + // Then, perform the actual patching + var patchedAssemblies = new HashSet(); + foreach (var assemblyPatcher in patchers) + foreach (string targetDll in assemblyPatcher.TargetDLLs) + if (assemblies.TryGetValue(targetDll, out var assembly)) + { + assemblyPatcher.Patcher?.Invoke(ref assembly); + assemblies[targetDll] = assembly; + patchedAssemblies.Add(targetDll); + } + + // Finally, load all assemblies into memory + foreach (KeyValuePair kv in assemblies) + { + string filename = kv.Key; + var assembly = kv.Value; + + if (DumpingEnabled && patchedAssemblies.Contains(filename)) + using (var mem = new MemoryStream()) + { + string dirPath = Path.Combine(Paths.PluginPath, "DumpedAssemblies"); + + if (!Directory.Exists(dirPath)) + Directory.CreateDirectory(dirPath); + + assembly.Write(mem); + File.WriteAllBytes(Path.Combine(dirPath, filename), mem.ToArray()); + } + + Load(assembly); + assembly.Dispose(); + } + + //run all finalizers + FinalizePatching(); + } + + /// + /// Loads an individual assembly defintion into the CLR. + /// + /// The assembly to load. + public static void Load(AssemblyDefinition assembly) + { + using (var assemblyStream = new MemoryStream()) + { + assembly.Write(assemblyStream); + Assembly.Load(assemblyStream.ToArray()); + } + } + } +} \ No newline at end of file diff --git a/BepInEx.Preloader/Patcher/PatcherPlugin.cs b/BepInEx.Preloader/Patcher/PatcherPlugin.cs new file mode 100644 index 0000000..d8b480d --- /dev/null +++ b/BepInEx.Preloader/Patcher/PatcherPlugin.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace BepInEx.Preloader.Patcher +{ + /// + /// A single assembly patcher. + /// + internal class PatcherPlugin + { + /// + /// Target assemblies to patch. + /// + public IEnumerable TargetDLLs { get; set; } = null; + + /// + /// Initializer method that is run before any patching occurs. + /// + public Action Initializer { get; set; } = null; + + /// + /// Finalizer method that is run after all patching is done. + /// + public Action Finalizer { get; set; } = null; + + /// + /// The main patcher method that is called on every DLL defined in . + /// + public AssemblyPatcherDelegate Patcher { get; set; } = null; + + /// + /// Name of the patcher. + /// + public string Name { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/BepInEx.Preloader/Preloader.cs b/BepInEx.Preloader/Preloader.cs new file mode 100644 index 0000000..d45ec9a --- /dev/null +++ b/BepInEx.Preloader/Preloader.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using BepInEx.Logging; +using BepInEx.Preloader.Patcher; +using Mono.Cecil; +using Mono.Cecil.Cil; +using UnityInjector.ConsoleUtil; +using MethodAttributes = Mono.Cecil.MethodAttributes; + +namespace BepInEx.Preloader +{ + /// + /// The main entrypoint of BepInEx, and initializes all patchers and the chainloader. + /// + internal static class Preloader + { + /// + /// The log writer that is specific to the preloader. + /// + private static PreloaderLogWriter PreloaderLog { get; set; } + + public static void Run() + { + try + { + AllocateConsole(); + + UnityPatches.Apply(); + + PreloaderLog = + new PreloaderLogWriter( + Utility.SafeParseBool(Config.GetEntry("preloader-logconsole", "false", "BepInEx"))); + PreloaderLog.Enabled = true; + + string consoleTile = + $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}"; + ConsoleWindow.Title = consoleTile; + + Logger.SetLogger(PreloaderLog); + + PreloaderLog.WriteLine(consoleTile); + +#if DEBUG + object[] attributes = + typeof(DebugInfoAttribute).Assembly.GetCustomAttributes(typeof(DebugInfoAttribute), false); + + if (attributes.Length > 0) + { + var attribute = (DebugInfoAttribute) attributes[0]; + + PreloaderLog.WriteLine(attribute.Info); + } +#endif + + Logger.Log(LogLevel.Message, "Preloader started"); + + string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader"); + + AssemblyPatcher.AddPatcher(new PatcherPlugin + {TargetDLLs = new[] {entrypointAssembly}, Patcher = PatchEntrypoint}); + AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath, GetPatcherMethods); + + AssemblyPatcher.PatchAndLoad(Paths.ManagedPath); + + AssemblyPatcher.DisposePatchers(); + } + catch (Exception ex) + { + Logger.Log(LogLevel.Fatal, "Could not run preloader!"); + Logger.Log(LogLevel.Fatal, ex); + + PreloaderLog.Enabled = false; + + try + { + if (!ConsoleWindow.IsAttatched) + { + //if we've already attached the console, then the log will already be written to the console + AllocateConsole(); + Console.Write(PreloaderLog); + } + } + finally + { + File.WriteAllText( + Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"), + PreloaderLog.ToString()); + + PreloaderLog.Dispose(); + PreloaderLog = null; + } + } + } + + /// + /// 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); + } + catch (Exception ex) + { + Logger.Log(LogLevel.Warning, $"Could not load patcher methods from {assembly.GetName().Name}"); + Logger.Log(LogLevel.Warning, $"{ex}"); + } + + Logger.Log(LogLevel.Info, + $"Loaded {patcherMethods.Count} patcher methods from {assembly.GetName().Name}"); + + return patcherMethods; + } + + /// + /// Inserts BepInEx's own chainloader entrypoint into UnityEngine. + /// + /// The assembly that will be attempted to be patched. + public static void PatchEntrypoint(ref AssemblyDefinition assembly) + { + if (assembly.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx"))) + throw new Exception( + "BepInEx has been detected to be patched! Please unpatch before using a patchless variant!"); + + string entrypointType = Config.GetEntry("entrypoint-type", "Application", "Preloader"); + string entrypointMethod = Config.GetEntry("entrypoint-method", ".cctor", "Preloader"); + + bool isCctor = entrypointMethod.IsNullOrWhiteSpace() || entrypointMethod == ".cctor"; + + + var entryType = assembly.MainModule.Types.FirstOrDefault(x => x.Name == entrypointType); + + if (entryType == null) throw new Exception("The entrypoint type is invalid! Please check your config.ini"); + + using (var injected = AssemblyDefinition.ReadAssembly(Paths.BepInExAssemblyPath)) + { + var originalInitMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods + .First(x => x.Name == "Initialize"); + + var originalStartMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods + .First(x => x.Name == "Start"); + + var initMethod = assembly.MainModule.ImportReference(originalInitMethod); + var startMethod = assembly.MainModule.ImportReference(originalStartMethod); + + var methods = new List(); + + if (isCctor) + { + var cctor = entryType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic); + + if (cctor == null) + { + cctor = new MethodDefinition(".cctor", + MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig + | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + assembly.MainModule.ImportReference(typeof(void))); + + entryType.Methods.Add(cctor); + var il = cctor.Body.GetILProcessor(); + il.Append(il.Create(OpCodes.Ret)); + } + + methods.Add(cctor); + } + else + { + methods.AddRange(entryType.Methods.Where(x => x.Name == entrypointMethod)); + } + + if (!methods.Any()) + throw new Exception("The entrypoint method is invalid! Please check your config.ini"); + + foreach (var method in methods) + { + var il = method.Body.GetILProcessor(); + + var ins = il.Body.Instructions.First(); + + il.InsertBefore(ins, il.Create(OpCodes.Ldstr, Paths.ExecutablePath)); //containerExePath + il.InsertBefore(ins, + 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 containerExePath, bool startConsole = true) + il.InsertBefore(ins, il.Create(OpCodes.Call, startMethod)); + } + } + } + + /// + /// Allocates a console window for use by BepInEx safely. + /// + public static void AllocateConsole() + { + bool console = Utility.SafeParseBool(Config.GetEntry("console", "false", "BepInEx")); + bool shiftjis = Utility.SafeParseBool(Config.GetEntry("console-shiftjis", "false", "BepInEx")); + + if (!console) + return; + + try + { + ConsoleWindow.Attach(); + + var encoding = (uint) Encoding.UTF8.CodePage; + + if (shiftjis) + encoding = 932; + + ConsoleEncoding.ConsoleCodePage = encoding; + Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding); + } + catch (Exception ex) + { + Logger.Log(LogLevel.Error, "Failed to allocate console!"); + Logger.Log(LogLevel.Error, ex); + } + } + } +} \ No newline at end of file diff --git a/BepInEx.Preloader/Properties/AssemblyInfo.cs b/BepInEx.Preloader/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..da1074a --- /dev/null +++ b/BepInEx.Preloader/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("BepInEx.Preloader")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BepInEx.Preloader")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f7abbe07-c02f-4f7c-bf6e-c6656bf588ca")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/BepInEx.Preloader/UnityPatches.cs b/BepInEx.Preloader/UnityPatches.cs new file mode 100644 index 0000000..0e0204b --- /dev/null +++ b/BepInEx.Preloader/UnityPatches.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using BepInEx.Harmony; +using Harmony; + +namespace BepInEx.Preloader +{ + internal static class UnityPatches + { + public static HarmonyInstance HarmonyInstance { get; } = HarmonyInstance.Create("com.bepinex.unitypatches"); + + public static Dictionary AssemblyLocations { get; } = + new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + public static void Apply() + { + HarmonyWrapper.PatchAll(typeof(UnityPatches), HarmonyInstance); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(Assembly), nameof(Assembly.Location), MethodType.Getter)] + public static void GetLocation(ref string __result, Assembly __instance) + { + if (AssemblyLocations.TryGetValue(__instance.FullName, out string location)) + __result = location; + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(Assembly), nameof(Assembly.CodeBase), MethodType.Getter)] + public static void GetCodeBase(ref string __result, Assembly __instance) + { + 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+ + */ + [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.sln b/BepInEx.sln index 8e4f528..69fccca 100644 --- a/BepInEx.sln +++ b/BepInEx.sln @@ -21,11 +21,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Harmony", "submodules\BepIn EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Patcher", "Patcher", "{A9071994-3533-4C1B-89DC-D817B676AB41}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Preloader", "BepInEx.Preloader\BepInEx.Preloader.csproj", "{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU v2018 Release|Any CPU = v2018 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 @@ -34,30 +37,48 @@ Global {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.Build.0 = Release|Any CPU {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018 Release|Any CPU.ActiveCfg = v2018|Any CPU {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018 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 = 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 {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018 Release|Any CPU.ActiveCfg = Release|Any CPU {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018 Release|Any CPU.Build.0 = Release|Any CPU + {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.ActiveCfg = Debug|Any CPU + {DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.Build.0 = Debug|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}.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 Release|Any CPU.ActiveCfg = Release|Any CPU {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018 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 = Release|Any CPU {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|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 Release|Any CPU.ActiveCfg = Release|Any CPU {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018 Release|Any CPU.Build.0 = Release|Any CPU + {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Any CPU.ActiveCfg = Debug|Any CPU + {54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Any CPU.Build.0 = Debug|Any CPU {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.ActiveCfg = Release|Any CPU {A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|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 Release|Any CPU.ActiveCfg = Release|Any CPU {A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018 Release|Any CPU.Build.0 = Release|Any CPU + {A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Any CPU.ActiveCfg = Debug|Any CPU + {A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Any CPU.Build.0 = Debug|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}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.Build.0 = Release|Any CPU + {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018 Release|Any CPU.ActiveCfg = Release|Any CPU + {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018 Release|Any CPU.Build.0 = Release|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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -67,6 +88,7 @@ Global {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB} = {A9071994-3533-4C1B-89DC-D817B676AB41} {54161CFE-FF42-4DDE-B161-3A49545DB5CD} = {BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB} {A15D6EE6-F954-415B-8605-8A8470CC87DC} = {BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB} + {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA} = {A9071994-3533-4C1B-89DC-D817B676AB41} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55AC11EF-F568-4C79-A356-7ED9510145B1} diff --git a/BepInEx/BepInEx.csproj b/BepInEx/BepInEx.csproj index 39a64ba..d757bb8 100644 --- a/BepInEx/BepInEx.csproj +++ b/BepInEx/BepInEx.csproj @@ -56,11 +56,7 @@ - - - - diff --git a/BepInEx/Bootstrap/AssemblyPatcher.cs b/BepInEx/Bootstrap/AssemblyPatcher.cs deleted file mode 100644 index dc5ded6..0000000 --- a/BepInEx/Bootstrap/AssemblyPatcher.cs +++ /dev/null @@ -1,239 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using BepInEx.Harmony; -using BepInEx.Logging; -using Harmony; -using Mono.Cecil; - -namespace BepInEx.Bootstrap -{ - /// - /// Delegate used in patching assemblies. - /// - /// The assembly that is being patched. - internal delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly); - - /// - /// A single assembly patcher. - /// - internal class PatcherPlugin - { - /// - /// Target assemblies to patch. - /// - public IEnumerable TargetDLLs { get; set; } = null; - - /// - /// Initializer method that is run before any patching occurs. - /// - public Action Initializer { get; set; } = null; - - /// - /// Finalizer method that is run after all patching is done. - /// - public Action Finalizer { get; set; } = null; - - /// - /// The main patcher method that is called on every DLL defined in . - /// - public AssemblyPatcherDelegate Patcher { get; set; } = null; - - /// - /// Name of the patcher. - /// - public string Name { get; set; } = string.Empty; - } - - /// - /// Worker class which is used for loading and patching entire folders of assemblies, or alternatively patching and loading assemblies one at a time. - /// - internal static class AssemblyPatcher - { - private static List patchers = new List(); - - /// - /// Configuration value of whether assembly dumping is enabled or not. - /// - private static bool DumpingEnabled => Utility.SafeParseBool(Config.GetEntry("dump-assemblies", "false", "Preloader")); - - /// - /// Adds a single assembly patcher to the pool of applicable patches. - /// - /// Patcher to apply. - public static void AddPatcher(PatcherPlugin patcher) - { - patchers.Add(patcher); - } - - /// - /// 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) - { - if (!Directory.Exists(directory)) - return; - - var sortedPatchers = new SortedDictionary(); - - foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll")) - try - { - var assembly = Assembly.LoadFrom(assemblyPath); - - foreach (var patcher in patcherLocator(assembly)) - sortedPatchers.Add(patcher.Name, patcher); - } - catch (BadImageFormatException) { } //unmanaged DLL - catch (ReflectionTypeLoadException) { } //invalid references - - foreach (var patcher in sortedPatchers) - AddPatcher(patcher.Value); - } - - private static void InitializePatchers() - { - foreach (var assemblyPatcher in patchers) - assemblyPatcher.Initializer?.Invoke(); - } - - private static void FinalizePatching() - { - foreach (var assemblyPatcher in patchers) - assemblyPatcher.Finalizer?.Invoke(); - } - - /// - /// Releases all patchers to let them be collected by GC. - /// - public static void DisposePatchers() - { - patchers.Clear(); - } - - /// - /// Applies patchers to all assemblies in the given directory and loads patched assemblies into memory. - /// - /// Directory to load CLR assemblies from. - public static void PatchAndLoad(string directory) - { - - // First, load patchable assemblies into Cecil - Dictionary assemblies = new Dictionary(); - - foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll")) - { - var assembly = AssemblyDefinition.ReadAssembly(assemblyPath); - - //NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky - //System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency - //It's also generally dangerous to change system.dll since so many things rely on it, - // and it's already loaded into the appdomain since this loader references it, so we might as well skip it - if (assembly.Name.Name == "System" - || assembly.Name.Name == "mscorlib") //mscorlib is already loaded into the appdomain so it can't be patched - { - assembly.Dispose(); - continue; - } - if (PatchedAssemblyResolver.AssemblyLocations.ContainsKey(assembly.FullName)) - { - Logger.Log(LogLevel.Warning, $"Tried to load duplicate assembly {Path.GetFileName(assemblyPath)} from Managed folder! Skipping..."); - continue; - } - - assemblies.Add(Path.GetFileName(assemblyPath), assembly); - PatchedAssemblyResolver.AssemblyLocations.Add(assembly.FullName, Path.GetFullPath(assemblyPath)); - } - - // Next, initialize all the patchers - InitializePatchers(); - - // Then, perform the actual patching - HashSet patchedAssemblies = new HashSet(); - foreach (var assemblyPatcher in patchers) - { - foreach (string targetDll in assemblyPatcher.TargetDLLs) - { - if (assemblies.TryGetValue(targetDll, out var assembly)) - { - assemblyPatcher.Patcher?.Invoke(ref assembly); - assemblies[targetDll] = assembly; - patchedAssemblies.Add(targetDll); - } - } - } - - // Finally, load all assemblies into memory - foreach (var kv in assemblies) - { - string filename = kv.Key; - var assembly = kv.Value; - - if (DumpingEnabled && patchedAssemblies.Contains(filename)) - { - using (MemoryStream mem = new MemoryStream()) - { - string dirPath = Path.Combine(Paths.PluginPath, "DumpedAssemblies"); - - if (!Directory.Exists(dirPath)) - Directory.CreateDirectory(dirPath); - - assembly.Write(mem); - File.WriteAllBytes(Path.Combine(dirPath, filename), mem.ToArray()); - } - } - - Load(assembly); - assembly.Dispose(); - } - - // Apply assembly location resolver patch - PatchedAssemblyResolver.ApplyPatch(); - - //run all finalizers - FinalizePatching(); - } - - /// - /// Loads an individual assembly defintion into the CLR. - /// - /// The assembly to load. - public static void Load(AssemblyDefinition assembly) - { - using (MemoryStream assemblyStream = new MemoryStream()) - { - assembly.Write(assemblyStream); - Assembly.Load(assemblyStream.ToArray()); - } - } - } - - internal static class PatchedAssemblyResolver - { - public static HarmonyInstance HarmonyInstance { get; } = HarmonyInstance.Create("com.bepis.bepinex.asmlocationfix"); - - public static Dictionary AssemblyLocations { get; } = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - - public static void ApplyPatch() - { - HarmonyWrapper.PatchAll(typeof(PatchedAssemblyResolver), HarmonyInstance); - } - - [HarmonyPostfix, HarmonyPatch(typeof(Assembly), nameof(Assembly.Location), MethodType.Getter)] - public static void GetLocation(ref string __result, Assembly __instance) - { - if (AssemblyLocations.TryGetValue(__instance.FullName, out string location)) - __result = location; - } - - [HarmonyPostfix, HarmonyPatch(typeof(Assembly), nameof(Assembly.CodeBase), MethodType.Getter)] - public static void GetCodeBase(ref string __result, Assembly __instance) - { - if (AssemblyLocations.TryGetValue(__instance.FullName, out string location)) - __result = $"file://{location.Replace('\\', '/')}"; - } - } -} \ No newline at end of file diff --git a/BepInEx/Bootstrap/Chainloader.cs b/BepInEx/Bootstrap/Chainloader.cs index 34c1a51..f3c33b6 100644 --- a/BepInEx/Bootstrap/Chainloader.cs +++ b/BepInEx/Bootstrap/Chainloader.cs @@ -54,12 +54,11 @@ namespace BepInEx.Bootstrap UnityLogWriter unityLogWriter = new UnityLogWriter(); - if (Preloader.PreloaderLog != null) - unityLogWriter.WriteToLog($"{Preloader.PreloaderLog}\r\n"); + if (Logger.CurrentLogger != null && Logger.CurrentLogger is PreloaderLogWriter preloaderLogger) + unityLogWriter.WriteToLog($"{preloaderLogger}\r\n"); Logger.SetLogger(unityLogWriter); - _initialized = true; } diff --git a/BepInEx/Bootstrap/Preloader.cs b/BepInEx/Bootstrap/Preloader.cs deleted file mode 100644 index b3be398..0000000 --- a/BepInEx/Bootstrap/Preloader.cs +++ /dev/null @@ -1,293 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using BepInEx.Logging; -using Mono.Cecil; -using Mono.Cecil.Cil; -using UnityInjector.ConsoleUtil; -using MethodAttributes = Mono.Cecil.MethodAttributes; - -namespace BepInEx.Bootstrap -{ - /// - /// The main entrypoint of BepInEx, and initializes all patchers and the chainloader. - /// - internal static class Preloader - { - /// - /// The log writer that is specific to the preloader. - /// - public static PreloaderLogWriter PreloaderLog { get; private set; } - - public static void Run() - { - try - { - AllocateConsole(); - - UnityPatches.Apply(); - - PreloaderLog = - new PreloaderLogWriter(Utility.SafeParseBool(Config.GetEntry("preloader-logconsole", "false", "BepInEx"))); - PreloaderLog.Enabled = true; - - string consoleTile = - $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {Process.GetCurrentProcess().ProcessName}"; - ConsoleWindow.Title = consoleTile; - - Logger.SetLogger(PreloaderLog); - - PreloaderLog.WriteLine(consoleTile); - -#if DEBUG - object[] attributes = typeof(DebugInfoAttribute).Assembly.GetCustomAttributes(typeof(DebugInfoAttribute), false); - - if (attributes.Length > 0) - { - var attribute = (DebugInfoAttribute)attributes[0]; - - PreloaderLog.WriteLine(attribute.Info); - } -#endif - - Logger.Log(LogLevel.Message, "Preloader started"); - - string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader"); - - AssemblyPatcher.AddPatcher(new PatcherPlugin { TargetDLLs = new []{ entrypointAssembly }, Patcher = PatchEntrypoint}); - AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath, GetPatcherMethods); - - AssemblyPatcher.PatchAndLoad(Paths.ManagedPath); - - AssemblyPatcher.DisposePatchers(); - } - catch (Exception ex) - { - Logger.Log(LogLevel.Fatal, "Could not run preloader!"); - Logger.Log(LogLevel.Fatal, ex); - - PreloaderLog.Enabled = false; - - try - { - if (!ConsoleWindow.IsAttatched) - { - //if we've already attached the console, then the log will already be written to the console - AllocateConsole(); - Console.Write(PreloaderLog); - } - } - finally - { - File.WriteAllText(Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"), - PreloaderLog.ToString()); - - PreloaderLog.Dispose(); - } - } - } - - /// - /// 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); - - } - catch (Exception ex) - { - Logger.Log(LogLevel.Warning, $"Could not load patcher methods from {assembly.GetName().Name}"); - Logger.Log(LogLevel.Warning, $"{ex}"); - } - - Logger.Log(LogLevel.Info, - $"Loaded {patcherMethods.Count} patcher methods from {assembly.GetName().Name}"); - - return patcherMethods; - } - - /// - /// Inserts BepInEx's own chainloader entrypoint into UnityEngine. - /// - /// The assembly that will be attempted to be patched. - public static void PatchEntrypoint(ref AssemblyDefinition assembly) - { - if (assembly.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx"))) - { - throw new Exception("BepInEx has been detected to be patched! Please unpatch before using a patchless variant!"); - } - - string entrypointType = Config.GetEntry("entrypoint-type", "Application", "Preloader"); - string entrypointMethod = Config.GetEntry("entrypoint-method", ".cctor", "Preloader"); - - bool isCctor = entrypointMethod.IsNullOrWhiteSpace() || entrypointMethod == ".cctor"; - - - var entryType = assembly.MainModule.Types.FirstOrDefault(x => x.Name == entrypointType); - - if (entryType == null) - { - throw new Exception("The entrypoint type is invalid! Please check your config.ini"); - } - - using (var injected = AssemblyDefinition.ReadAssembly(Paths.BepInExAssemblyPath)) - { - var originalInitMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods - .First(x => x.Name == "Initialize"); - - var originalStartMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods - .First(x => x.Name == "Start"); - - var initMethod = assembly.MainModule.ImportReference(originalInitMethod); - var startMethod = assembly.MainModule.ImportReference(originalStartMethod); - - List methods = new List(); - - if (isCctor) - { - MethodDefinition cctor = entryType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic); - - if (cctor == null) - { - cctor = new MethodDefinition(".cctor", - MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig - | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, - assembly.MainModule.ImportReference(typeof(void))); - - entryType.Methods.Add(cctor); - ILProcessor il = cctor.Body.GetILProcessor(); - il.Append(il.Create(OpCodes.Ret)); - } - - methods.Add(cctor); - } - else - { - methods.AddRange(entryType.Methods.Where(x => x.Name == entrypointMethod)); - } - - if (!methods.Any()) - { - throw new Exception("The entrypoint method is invalid! Please check your config.ini"); - } - - foreach (var method in methods) - { - var il = method.Body.GetILProcessor(); - - Instruction ins = il.Body.Instructions.First(); - - il.InsertBefore(ins, il.Create(OpCodes.Ldstr, Paths.ExecutablePath)); //containerExePath - il.InsertBefore(ins, 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 containerExePath, bool startConsole = true) - il.InsertBefore(ins, il.Create(OpCodes.Call, startMethod)); - } - } - } - - /// - /// Allocates a console window for use by BepInEx safely. - /// - public static void AllocateConsole() - { - bool console = Utility.SafeParseBool(Config.GetEntry("console", "false", "BepInEx")); - bool shiftjis = Utility.SafeParseBool(Config.GetEntry("console-shiftjis", "false", "BepInEx")); - - if (!console) - return; - - try - { - ConsoleWindow.Attach(); - - var encoding = (uint) Encoding.UTF8.CodePage; - - if (shiftjis) - encoding = 932; - - ConsoleEncoding.ConsoleCodePage = encoding; - Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding); - } - catch (Exception ex) - { - Logger.Log(LogLevel.Error, "Failed to allocate console!"); - Logger.Log(LogLevel.Error, ex); - } - } - } -} \ No newline at end of file diff --git a/BepInEx/Bootstrap/UnityPatches.cs b/BepInEx/Bootstrap/UnityPatches.cs deleted file mode 100644 index 5bc8905..0000000 --- a/BepInEx/Bootstrap/UnityPatches.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using BepInEx.Harmony; -using Harmony; - -namespace BepInEx.Bootstrap -{ - internal static class UnityPatches - { - public static HarmonyInstance HarmonyInstance { get; } = HarmonyInstance.Create("com.bepinex.unitypatches"); - - public static void Apply() - { - HarmonyWrapper.PatchAll(typeof(UnityPatches), HarmonyInstance); - } - -#if UNITY_2018 - /* - * DESC: Workaround for Trace class not working because of missing .config file - * AFFECTS: Unity 2018+ - */ - [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/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs b/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs index baa1ebf..8f42563 100644 --- a/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs +++ b/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs @@ -15,7 +15,7 @@ namespace UnityInjector.ConsoleUtil // http://jonskeet.uk/csharp/ebcdic/ // using only safe (managed) code // -------------------------------------------------- - internal partial class ConsoleEncoding : Encoding + public partial class ConsoleEncoding : Encoding { private readonly uint _codePage; public override int CodePage => (int) _codePage; diff --git a/BepInEx/ConsoleUtil/ConsoleWindow.cs b/BepInEx/ConsoleUtil/ConsoleWindow.cs index 6095392..db2e16d 100644 --- a/BepInEx/ConsoleUtil/ConsoleWindow.cs +++ b/BepInEx/ConsoleUtil/ConsoleWindow.cs @@ -10,7 +10,7 @@ using System.Text; namespace UnityInjector.ConsoleUtil { - internal class ConsoleWindow + public class ConsoleWindow { public static bool IsAttatched { get; private set; } private static IntPtr _cOut; diff --git a/BepInEx/Logging/PreloaderLogWriter.cs b/BepInEx/Logging/PreloaderLogWriter.cs index f0a9c89..5bbc9a1 100644 --- a/BepInEx/Logging/PreloaderLogWriter.cs +++ b/BepInEx/Logging/PreloaderLogWriter.cs @@ -62,7 +62,7 @@ namespace BepInEx.Logging if (IsRedirectingConsole) Console.SetOut(this); else - Console.SetOut(TextWriter.Null); + Console.SetOut(Null); Trace.Listeners.Add(traceListener); @@ -76,7 +76,7 @@ namespace BepInEx.Logging { if (!_enabled) return; - + Console.SetOut(stdout); Trace.Listeners.Remove(traceListener); diff --git a/BepInEx/Logging/UnityLogWriter.cs b/BepInEx/Logging/UnityLogWriter.cs index 3123794..0ea6d50 100644 --- a/BepInEx/Logging/UnityLogWriter.cs +++ b/BepInEx/Logging/UnityLogWriter.cs @@ -11,10 +11,8 @@ namespace BepInEx.Logging /// Logs entries using Unity specific outputs. /// public class UnityLogWriter : BaseLogger - { - private delegate void WriteStringToUnityLogDelegate(string s); - - private static readonly WriteStringToUnityLogDelegate WriteStringToUnityLog; + { + private static readonly Action WriteStringToUnityLog; [DllImport("mono.dll", EntryPoint = "mono_lookup_internal_call")] private static extern IntPtr MonoLookupInternalCall(IntPtr gconstpointer); @@ -26,7 +24,7 @@ namespace BepInEx.Logging if (MonoLookupInternalCall(methodInfo.MethodHandle.Value) == IntPtr.Zero) continue; - WriteStringToUnityLog = (WriteStringToUnityLogDelegate) Delegate.CreateDelegate(typeof(WriteStringToUnityLogDelegate), methodInfo); + WriteStringToUnityLog = (Action) Delegate.CreateDelegate(typeof(Action), methodInfo); break; } }