Preloader.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Text;
  8. using BepInEx.Common;
  9. using BepInEx.Logging;
  10. using Mono.Cecil;
  11. using Mono.Cecil.Cil;
  12. using UnityInjector.ConsoleUtil;
  13. using MethodAttributes = Mono.Cecil.MethodAttributes;
  14. namespace BepInEx.Bootstrap
  15. {
  16. /// <summary>
  17. /// The main entrypoint of BepInEx, and initializes all patchers and the chainloader.
  18. /// </summary>
  19. internal static class Preloader
  20. {
  21. /// <summary>
  22. /// The list of finalizers that were loaded from the patcher contract.
  23. /// </summary>
  24. public static List<Action> Finalizers { get; } = new List<Action>();
  25. /// <summary>
  26. /// The list of initializers that were loaded from the patcher contract.
  27. /// </summary>
  28. public static List<Action> Initializers { get; } = new List<Action>();
  29. /// <summary>
  30. /// The dictionary of currently loaded patchers. The key is the patcher delegate that will be used to patch, and the
  31. /// value is a list of filenames of assemblies that the patcher is targeting.
  32. /// </summary>
  33. public static Dictionary<AssemblyPatcherDelegate, IEnumerable<string>> PatcherDictionary { get; } =
  34. new Dictionary<AssemblyPatcherDelegate, IEnumerable<string>>();
  35. /// <summary>
  36. /// The log writer that is specific to the preloader.
  37. /// </summary>
  38. public static PreloaderLogWriter PreloaderLog { get; private set; }
  39. public static void Run()
  40. {
  41. try
  42. {
  43. AllocateConsole();
  44. PreloaderLog = new PreloaderLogWriter(Utility.SafeParseBool(Config.GetEntry("preloader-logconsole", "false", "BepInEx")));
  45. PreloaderLog.Enabled = true;
  46. string consoleTile =
  47. $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
  48. ConsoleWindow.Title = consoleTile;
  49. Logger.SetLogger(PreloaderLog);
  50. PreloaderLog.WriteLine(consoleTile);
  51. Logger.Log(LogLevel.Message, "Preloader started");
  52. string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader");
  53. AddPatcher(new[] {entrypointAssembly}, PatchEntrypoint);
  54. if (Directory.Exists(Paths.PatcherPluginPath))
  55. {
  56. var sortedPatchers = new SortedDictionary<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>>();
  57. foreach (string assemblyPath in Directory.GetFiles(Paths.PatcherPluginPath, "*.dll"))
  58. try
  59. {
  60. var assembly = Assembly.LoadFrom(assemblyPath);
  61. foreach (KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>> kv in GetPatcherMethods(assembly))
  62. sortedPatchers.Add(assembly.GetName().Name, kv);
  63. }
  64. catch (BadImageFormatException) { } //unmanaged DLL
  65. catch (ReflectionTypeLoadException) { } //invalid references
  66. foreach (KeyValuePair<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>> kv in sortedPatchers)
  67. AddPatcher(kv.Value.Value, kv.Value.Key);
  68. }
  69. AssemblyPatcher.PatchAll(Paths.ManagedPath, PatcherDictionary, Initializers, Finalizers);
  70. }
  71. catch (Exception ex)
  72. {
  73. Logger.Log(LogLevel.Fatal, "Could not run preloader!");
  74. Logger.Log(LogLevel.Fatal, ex);
  75. PreloaderLog.Enabled = false;
  76. try
  77. {
  78. AllocateConsole();
  79. Console.Write(PreloaderLog);
  80. }
  81. finally
  82. {
  83. File.WriteAllText(Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"),
  84. PreloaderLog.ToString());
  85. PreloaderLog.Dispose();
  86. }
  87. }
  88. }
  89. /// <summary>
  90. /// Scans the assembly for classes that use the patcher contract, and returns a dictionary of the patch methods.
  91. /// </summary>
  92. /// <param name="assembly">The assembly to scan.</param>
  93. /// <returns>A dictionary of delegates which will be used to patch the targeted assemblies.</returns>
  94. public static Dictionary<AssemblyPatcherDelegate, IEnumerable<string>> GetPatcherMethods(Assembly assembly)
  95. {
  96. var patcherMethods = new Dictionary<AssemblyPatcherDelegate, IEnumerable<string>>();
  97. foreach (var type in assembly.GetExportedTypes())
  98. try
  99. {
  100. if (type.IsInterface)
  101. continue;
  102. var targetsProperty = type.GetProperty("TargetDLLs",
  103. BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
  104. null,
  105. typeof(IEnumerable<string>),
  106. Type.EmptyTypes,
  107. null);
  108. //first try get the ref patcher method
  109. var patcher = type.GetMethod("Patch",
  110. BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
  111. null,
  112. CallingConventions.Any,
  113. new[] {typeof(AssemblyDefinition).MakeByRefType()},
  114. null);
  115. if (patcher == null) //otherwise try getting the non-ref patcher method
  116. patcher = type.GetMethod("Patch",
  117. BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
  118. null,
  119. CallingConventions.Any,
  120. new[] {typeof(AssemblyDefinition)},
  121. null);
  122. if (targetsProperty == null || !targetsProperty.CanRead || patcher == null)
  123. continue;
  124. AssemblyPatcherDelegate patchDelegate = (ref AssemblyDefinition ass) =>
  125. {
  126. //we do the array fuckery here to get the ref result out
  127. object[] args = {ass};
  128. patcher.Invoke(null, args);
  129. ass = (AssemblyDefinition) args[0];
  130. };
  131. var targets = (IEnumerable<string>) targetsProperty.GetValue(null, null);
  132. patcherMethods[patchDelegate] = targets;
  133. var initMethod = type.GetMethod("Initialize",
  134. BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
  135. null,
  136. CallingConventions.Any,
  137. Type.EmptyTypes,
  138. null);
  139. if (initMethod != null)
  140. Initializers.Add(() => initMethod.Invoke(null, null));
  141. var finalizeMethod = type.GetMethod("Finish",
  142. BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
  143. null,
  144. CallingConventions.Any,
  145. Type.EmptyTypes,
  146. null);
  147. if (finalizeMethod != null)
  148. Finalizers.Add(() => finalizeMethod.Invoke(null, null));
  149. }
  150. catch (Exception ex)
  151. {
  152. Logger.Log(LogLevel.Warning, $"Could not load patcher methods from {assembly.GetName().Name}");
  153. Logger.Log(LogLevel.Warning, $"{ex}");
  154. }
  155. Logger.Log(LogLevel.Info,
  156. $"Loaded {patcherMethods.Select(x => x.Key).Distinct().Count()} patcher methods from {assembly.GetName().Name}");
  157. return patcherMethods;
  158. }
  159. /// <summary>
  160. /// Inserts BepInEx's own chainloader entrypoint into UnityEngine.
  161. /// </summary>
  162. /// <param name="assembly">The assembly that will be attempted to be patched.</param>
  163. public static void PatchEntrypoint(ref AssemblyDefinition assembly)
  164. {
  165. string entrypointType = Config.GetEntry("entrypoint-type", "Application", "Preloader");
  166. string entrypointMethod = Config.HasEntry("entrypoint-method") ? Config.GetEntry("entrypoint-method", section: "Preloader") : "";
  167. bool isCctor = entrypointMethod.IsNullOrWhiteSpace() || entrypointMethod == ".cctor";
  168. #if CECIL_10
  169. using (var injected = AssemblyDefinition.ReadAssembly(Paths.BepInExAssemblyPath))
  170. #elif CECIL_9
  171. AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(BepInExAssemblyPath);
  172. #endif
  173. {
  174. var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
  175. .First(x => x.Name == "Initialize");
  176. var injectMethod = assembly.MainModule.ImportReference(originalInjectMethod);
  177. var entryType = assembly.MainModule.Types.First(x => x.Name == entrypointType);
  178. if (isCctor)
  179. {
  180. MethodDefinition cctor = entryType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic);
  181. ILProcessor il;
  182. if (cctor == null)
  183. {
  184. cctor = new MethodDefinition(".cctor",
  185. MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig
  186. | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
  187. assembly.MainModule.ImportReference(typeof(void)));
  188. entryType.Methods.Add(cctor);
  189. il = cctor.Body.GetILProcessor();
  190. il.Append(il.Create(OpCodes.Ret));
  191. }
  192. Instruction ins = cctor.Body.Instructions.First();
  193. il = cctor.Body.GetILProcessor();
  194. il.InsertBefore(ins, il.Create(OpCodes.Call, injectMethod));
  195. }
  196. else
  197. {
  198. foreach (var loadScene in entryType.Methods.Where(x => x.Name == entrypointMethod))
  199. {
  200. var il = loadScene.Body.GetILProcessor();
  201. il.InsertBefore(loadScene.Body.Instructions[0], il.Create(OpCodes.Call, injectMethod));
  202. }
  203. }
  204. }
  205. }
  206. /// <summary>
  207. /// Allocates a console window for use by BepInEx safely.
  208. /// </summary>
  209. public static void AllocateConsole()
  210. {
  211. bool console = Utility.SafeParseBool(Config.GetEntry("console", "false", "BepInEx"));
  212. bool shiftjis = Utility.SafeParseBool(Config.GetEntry("console-shiftjis", "false", "BepInEx"));
  213. if (console)
  214. try
  215. {
  216. ConsoleWindow.Attach();
  217. var encoding = (uint) Encoding.UTF8.CodePage;
  218. if (shiftjis)
  219. encoding = 932;
  220. ConsoleEncoding.ConsoleCodePage = encoding;
  221. Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
  222. }
  223. catch (Exception ex)
  224. {
  225. Logger.Log(LogLevel.Error, "Failed to allocate console!");
  226. Logger.Log(LogLevel.Error, ex);
  227. }
  228. }
  229. /// <summary>
  230. /// Adds the patcher to the patcher dictionary.
  231. /// </summary>
  232. /// <param name="dllNames">The list of DLL filenames to be patched.</param>
  233. /// <param name="patcher">The method that will perform the patching.</param>
  234. public static void AddPatcher(IEnumerable<string> dllNames, AssemblyPatcherDelegate patcher)
  235. {
  236. PatcherDictionary[patcher] = dllNames;
  237. }
  238. }
  239. }