123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.IO;
- using System.Linq;
- using BepInEx.Bootstrap;
- using BepInEx.Configuration;
- using BepInEx.Core.Logging;
- using BepInEx.Logging;
- using BepInEx.Preloader.Core;
- using BepInEx.Preloader.Core.Logging;
- using BepInEx.Preloader.RuntimeFixes;
- using Mono.Cecil;
- using Mono.Cecil.Cil;
- using MonoMod.Utils;
- using MethodAttributes = Mono.Cecil.MethodAttributes;
- namespace BepInEx.Preloader.Unity
- {
-
-
-
- internal static class UnityPreloader
- {
-
-
-
- private static PreloaderConsoleListener PreloaderLog { get; set; }
- private static ManualLogSource Log => PreloaderLogger.Log;
- public static string ManagedPath { get; private set; } = Utility.CombinePaths(Paths.GameRootPath, $"{Paths.ProcessName}_Data", "Managed");
- public static bool IsPostUnity2017 { get; } = File.Exists(Path.Combine(ManagedPath, "UnityEngine.CoreModule.dll"));
- public static void Run(string managedDirectory)
- {
- try
- {
- InitializeHarmony();
- ConsoleManager.Initialize(false);
- AllocateConsole();
- if (managedDirectory != null)
- ManagedPath = managedDirectory;
-
- Utility.TryDo(() =>
- {
- if (ConfigApplyRuntimePatches.Value)
- UnityPatches.Apply();
- }, out var runtimePatchException);
- Logger.Sources.Add(TraceLogSource.CreateSource());
- Logger.Sources.Add(new HarmonyLogSource());
- Logger.Listeners.Add(new ConsoleLogListener());
- PreloaderLog = new PreloaderConsoleListener();
- Logger.Listeners.Add(PreloaderLog);
- ChainloaderLogHelper.PrintLogInfo(Log);
- Log.LogInfo($"Running under Unity v{GetUnityVersion()}");
- Log.LogInfo($"CLR runtime version: {Environment.Version}");
- Log.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}");
- Log.LogDebug($"Game executable path: {Paths.ExecutablePath}");
- Log.LogDebug($"Unity Managed directory: {ManagedPath}");
- Log.LogDebug($"BepInEx root path: {Paths.BepInExRootPath}");
- if (runtimePatchException != null)
- Log.LogWarning($"Failed to apply runtime patches for Mono. See more info in the output log. Error message: {runtimePatchException.Message}");
- Log.LogMessage("Preloader started");
- TypeLoader.SearchDirectories.Add(ManagedPath);
- using (var assemblyPatcher = new AssemblyPatcher())
- {
- assemblyPatcher.PatcherPlugins.Add(new PatcherPlugin
- {
- TargetDLLs = () => new[] { ConfigEntrypointAssembly.Value },
- Patcher = PatchEntrypoint,
- TypeName = "BepInEx.Chainloader"
- });
- assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
- Log.LogInfo($"{assemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded");
- assemblyPatcher.LoadAssemblyDirectory(ManagedPath);
- Log.LogInfo($"{assemblyPatcher.PatcherPlugins.Count} assemblies discovered");
- assemblyPatcher.PatchAndLoad();
- }
- Log.LogMessage("Preloader finished");
- Logger.Listeners.Remove(PreloaderLog);
- PreloaderLog.Dispose();
- Logger.Listeners.Add(new StdOutLogListener());
- }
- catch (Exception ex)
- {
- try
- {
- Log.LogFatal("Could not run preloader!");
- Log.LogFatal(ex);
- if (!ConsoleManager.ConsoleActive)
- {
-
- AllocateConsole();
- Console.Write(PreloaderLog);
- }
- }
- catch { }
- string log = string.Empty;
- try
- {
-
- log = string.Join("\r\n", PreloaderConsoleListener.LogEvents.Select(x => x.ToString()).ToArray());
- log += "\r\n";
- PreloaderLog?.Dispose();
- PreloaderLog = null;
- }
- catch { }
- File.WriteAllText(
- Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"),
- log + ex);
- }
- }
-
-
-
-
- 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 = ConfigEntrypointType.Value;
- string entrypointMethod = ConfigEntrypointMethod.Value;
- 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/BepInEx.cfg file");
- string chainloaderAssemblyPath = Path.Combine(Paths.BepInExAssemblyDirectory, "BepInEx.Unity.dll");
- var readerParameters = new ReaderParameters
- {
- AssemblyResolver = TypeLoader.CecilResolver
- };
- using (var chainloaderAssemblyDefinition = AssemblyDefinition.ReadAssembly(chainloaderAssemblyPath, readerParameters))
- {
- var chainloaderType = chainloaderAssemblyDefinition.MainModule.Types.First(x => x.Name == "UnityChainloader");
- var originalStartMethod = chainloaderType.EnumerateAllMethods().First(x => x.Name == "StaticStart");
- var startMethod = assembly.MainModule.ImportReference(originalStartMethod);
- var methods = new List<MethodDefinition>();
- 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.Ldnull));
- il.InsertBefore(ins,
- il.Create(OpCodes.Call, startMethod));
- }
- }
- }
-
-
-
- public static void AllocateConsole()
- {
- if (!ConsoleManager.ConfigConsoleEnabled.Value)
- return;
- try
- {
- ConsoleManager.CreateConsole();
- }
- catch (Exception ex)
- {
- Log.LogError("Failed to allocate console!");
- Log.LogError(ex);
- }
- }
- public static string GetUnityVersion()
- {
- if (Utility.CurrentPlatform == Platform.Windows)
- return FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion;
- return $"Unknown ({(IsPostUnity2017 ? "post" : "pre")}-2017)";
- }
- private static void InitializeHarmony()
- {
- switch (ConfigHarmonyBackend.Value)
- {
- case MonoModBackend.auto:
- break;
- case MonoModBackend.dynamicmethod:
- case MonoModBackend.methodbuilder:
- case MonoModBackend.cecil:
- Environment.SetEnvironmentVariable("MONOMOD_DMD_TYPE", ConfigHarmonyBackend.Value.ToString());
- break;
- default:
- throw new ArgumentOutOfRangeException(nameof(ConfigHarmonyBackend), ConfigHarmonyBackend.Value, "Unknown backend");
- }
- }
- private enum MonoModBackend
- {
-
- [Description("Auto")] auto = 0,
- [Description("DynamicMethod")] dynamicmethod,
- [Description("MethodBuilder")] methodbuilder,
- [Description("Cecil")] cecil
- }
- #region Config
- private static readonly ConfigEntry<string> ConfigEntrypointAssembly = ConfigFile.CoreConfig.Bind(
- "Preloader.Entrypoint", "Assembly",
- IsPostUnity2017 ? "UnityEngine.CoreModule.dll" : "UnityEngine.dll",
- "The local filename of the assembly to target.");
- private static readonly ConfigEntry<string> ConfigEntrypointType = ConfigFile.CoreConfig.Bind(
- "Preloader.Entrypoint", "Type",
- "Application",
- "The name of the type in the entrypoint assembly to search for the entrypoint method.");
- private static readonly ConfigEntry<string> ConfigEntrypointMethod = ConfigFile.CoreConfig.Bind(
- "Preloader.Entrypoint", "Method",
- ".cctor",
- "The name of the method in the specified entrypoint assembly and type to hook and load Chainloader from.");
- internal static readonly ConfigEntry<bool> ConfigApplyRuntimePatches = ConfigFile.CoreConfig.Bind(
- "Preloader", "ApplyRuntimePatches",
- true,
- "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.");
- private static readonly ConfigEntry<bool> ConfigPreloaderCOutLogging = ConfigFile.CoreConfig.Bind(
- "Logging", "PreloaderConsoleOutRedirection",
- true,
- "Redirects text from Console.Out during preloader patch loading to the BepInEx logging system.");
- private static readonly ConfigEntry<MonoModBackend> ConfigHarmonyBackend = ConfigFile.CoreConfig.Bind(
- "Preloader",
- "HarmonyBackend",
- MonoModBackend.auto,
- "Specifies which MonoMod backend to use for Harmony patches. Auto uses the best available backend.\nThis setting should only be used for development purposes (e.g. debugging in dnSpy). Other code might override this setting.");
- #endregion
- }
- }
|