Preloader.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Text;
  7. using BepInEx.Common;
  8. using BepInEx.Logging;
  9. using UnityInjector.ConsoleUtil;
  10. namespace BepInEx.Bootstrap
  11. {
  12. /// <summary>
  13. /// The main entrypoint of BepInEx, and initializes all patchers and the chainloader.
  14. /// </summary>
  15. public static class Preloader
  16. {
  17. #region Path Properties
  18. private static string executablePath;
  19. /// <summary>
  20. /// The path of the currently executing program BepInEx is encapsulated in.
  21. /// </summary>
  22. public static string ExecutablePath
  23. {
  24. get => executablePath;
  25. set
  26. {
  27. executablePath = value;
  28. GameRootPath = Path.GetDirectoryName(executablePath);
  29. ManagedPath = Utility.CombinePaths(GameRootPath, $"{ProcessName}_Data", "Managed");
  30. PluginPath = Utility.CombinePaths(GameRootPath, "BepInEx");
  31. PatcherPluginPath = Utility.CombinePaths(GameRootPath, "BepInEx", "patchers");
  32. BepInExAssemblyDirectory = Utility.CombinePaths(GameRootPath, "BepInEx", "core");
  33. BepInExAssemblyPath =
  34. Utility.CombinePaths(BepInExAssemblyDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.dll");
  35. }
  36. }
  37. /// <summary>
  38. /// The path to the core BepInEx DLL.
  39. /// </summary>
  40. public static string BepInExAssemblyPath { get; private set; }
  41. /// <summary>
  42. /// The directory that the core BepInEx DLLs reside in.
  43. /// </summary>
  44. public static string BepInExAssemblyDirectory { get; private set; }
  45. /// <summary>
  46. /// The name of the currently executing process.
  47. /// </summary>
  48. public static string ProcessName { get; } = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName);
  49. /// <summary>
  50. /// The directory that the currently executing process resides in.
  51. /// </summary>
  52. public static string GameRootPath { get; private set; }
  53. /// <summary>
  54. /// The path to the Managed folder of the currently running Unity game.
  55. /// </summary>
  56. public static string ManagedPath { get; private set; }
  57. /// <summary>
  58. /// The path to the main BepInEx folder.
  59. /// </summary>
  60. public static string PluginPath { get; private set; }
  61. /// <summary>
  62. /// The path to the patcher plugin folder which resides in the BepInEx folder.
  63. /// </summary>
  64. public static string PatcherPluginPath { get; private set; }
  65. #endregion
  66. /// <summary>
  67. /// The log writer that is specific to the preloader.
  68. /// </summary>
  69. public static PreloaderLogWriter PreloaderLog { get; private set; }
  70. /// <summary>
  71. /// Safely retrieves a boolean value from the config. Returns false if not able to retrieve safely.
  72. /// </summary>
  73. /// <param name="key">The key to retrieve from the config.</param>
  74. /// <param name="defaultValue">The default value to both return and set if the key does not exist in the config.</param>
  75. /// <returns>The value of the key if found in the config, or the default value specified if not found, or false if it was unable to safely retrieve the value from the config.</returns>
  76. private static bool SafeGetConfigBool(string key, string defaultValue)
  77. {
  78. try
  79. {
  80. string result = Config.GetEntry(key, defaultValue);
  81. return bool.Parse(result);
  82. }
  83. catch
  84. {
  85. return false;
  86. }
  87. }
  88. /// <summary>
  89. /// Allocates a console window for use by BepInEx safely.
  90. /// </summary>
  91. internal static void AllocateConsole()
  92. {
  93. bool console = SafeGetConfigBool("console", "false");
  94. bool shiftjis = SafeGetConfigBool("console-shiftjis", "false");
  95. if (console)
  96. {
  97. try
  98. {
  99. ConsoleWindow.Attach();
  100. uint encoding = (uint) Encoding.UTF8.CodePage;
  101. if (shiftjis)
  102. encoding = 932;
  103. ConsoleEncoding.ConsoleCodePage = encoding;
  104. Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
  105. }
  106. catch (Exception ex)
  107. {
  108. Logger.Log(LogLevel.Error, "Failed to allocate console!");
  109. Logger.Log(LogLevel.Error, ex);
  110. }
  111. }
  112. }
  113. /// <summary>
  114. /// The main entrypoint of BepInEx, called from Doorstop.
  115. /// </summary>
  116. /// <param name="args">The arguments passed in from Doorstop. First argument is the path of the currently executing process.</param>
  117. public static void Main(string[] args)
  118. {
  119. ExecutablePath = args[0];
  120. AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
  121. try
  122. {
  123. AllocateConsole();
  124. PreloaderLog = new PreloaderLogWriter(SafeGetConfigBool("preloader-logconsole", "false"));
  125. PreloaderLog.Enabled = true;
  126. string consoleTile =
  127. $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
  128. ConsoleWindow.Title = consoleTile;
  129. Logger.SetLogger(PreloaderLog);
  130. PreloaderLog.WriteLine(consoleTile);
  131. Logger.Log(LogLevel.Message, "Preloader started");
  132. PreloaderPatchManager.Run();
  133. }
  134. catch (Exception ex)
  135. {
  136. Logger.Log(LogLevel.Fatal, "Could not run preloader!");
  137. Logger.Log(LogLevel.Fatal, ex);
  138. PreloaderLog.Enabled = false;
  139. try
  140. {
  141. UnityInjector.ConsoleUtil.ConsoleWindow.Attach();
  142. Console.Write(PreloaderLog);
  143. }
  144. finally
  145. {
  146. File.WriteAllText(Path.Combine(GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"),
  147. PreloaderLog.ToString());
  148. PreloaderLog.Dispose();
  149. }
  150. }
  151. }
  152. /// <summary>
  153. /// A handler for <see cref="AppDomain"/>.AssemblyResolve to perform some special handling.
  154. /// <para>
  155. /// 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.
  156. /// </para>
  157. /// </summary>
  158. /// <param name="sender"></param>
  159. /// <param name="args"></param>
  160. /// <returns></returns>
  161. internal static Assembly LocalResolve(object sender, ResolveEventArgs args)
  162. {
  163. AssemblyName assemblyName = new AssemblyName(args.Name);
  164. var foundAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
  165. if (foundAssembly != null)
  166. return foundAssembly;
  167. if (Utility.TryResolveDllAssembly(assemblyName, BepInExAssemblyDirectory, out foundAssembly)
  168. || Utility.TryResolveDllAssembly(assemblyName, PatcherPluginPath, out foundAssembly)
  169. || Utility.TryResolveDllAssembly(assemblyName, PluginPath, out foundAssembly))
  170. return foundAssembly;
  171. return null;
  172. }
  173. }
  174. }