Preloader.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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 BepInEx.Common;
  8. using Mono.Cecil;
  9. using Mono.Cecil.Cil;
  10. using MethodAttributes = Mono.Cecil.MethodAttributes;
  11. namespace BepInEx.Bootstrap
  12. {
  13. public static class Preloader
  14. {
  15. #region Path Properties
  16. public static string ExecutablePath { get; private set; }
  17. public static string CurrentExecutingAssemblyPath => Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", "").Replace('/', '\\');
  18. public static string CurrentExecutingAssemblyDirectoryPath => Path.GetDirectoryName(CurrentExecutingAssemblyPath);
  19. public static string GameName => Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName);
  20. public static string GameRootPath => Path.GetDirectoryName(ExecutablePath);
  21. public static string ManagedPath => Utility.CombinePaths(GameRootPath, $"{GameName}_Data", "Managed");
  22. public static string PluginPath => Utility.CombinePaths(GameRootPath, "BepInEx");
  23. public static string PatcherPluginPath => Utility.CombinePaths(GameRootPath, "BepInEx", "patchers");
  24. public static string PreloaderLog { get; private set; } = $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version}\r\n";
  25. #endregion
  26. public static Dictionary<string, IList<AssemblyPatcherDelegate>> PatcherDictionary = new Dictionary<string, IList<AssemblyPatcherDelegate>>(StringComparer.OrdinalIgnoreCase);
  27. public static void AddPatcher(string dllName, AssemblyPatcherDelegate patcher)
  28. {
  29. if (PatcherDictionary.TryGetValue(dllName, out IList<AssemblyPatcherDelegate> patcherList))
  30. patcherList.Add(patcher);
  31. else
  32. {
  33. patcherList = new List<AssemblyPatcherDelegate>();
  34. patcherList.Add(patcher);
  35. PatcherDictionary[dllName] = patcherList;
  36. }
  37. }
  38. public static void Main(string[] args)
  39. {
  40. try
  41. {
  42. PreloaderLog += "Preloader started\r\n";
  43. ExecutablePath = args[0];
  44. AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
  45. AddPatcher("UnityEngine.dll", PatchEntrypoint);
  46. if (Directory.Exists(PatcherPluginPath))
  47. foreach (string assemblyPath in Directory.GetFiles(PatcherPluginPath, "*.dll"))
  48. {
  49. try
  50. {
  51. var assembly = Assembly.LoadFrom(assemblyPath);
  52. foreach (var kv in GetPatcherMethods(assembly))
  53. foreach (var patcher in kv.Value)
  54. AddPatcher(kv.Key, patcher);
  55. }
  56. catch (BadImageFormatException) { } //unmanaged DLL
  57. catch (ReflectionTypeLoadException) { } //invalid references
  58. }
  59. AssemblyPatcher.PatchAll(ManagedPath, PatcherDictionary);
  60. }
  61. catch (Exception ex)
  62. {
  63. PreloaderLog += $"FATAL ERROR: COULD NOT LOAD PATCHER!\r\n{ex}";
  64. try
  65. {
  66. UnityInjector.ConsoleUtil.ConsoleWindow.Attach();
  67. Console.Write(PreloaderLog);
  68. }
  69. finally
  70. {
  71. File.WriteAllText(Path.Combine(CurrentExecutingAssemblyDirectoryPath, "fatalerror.log"), PreloaderLog);
  72. }
  73. }
  74. }
  75. internal static IDictionary<string, IList<AssemblyPatcherDelegate>> GetPatcherMethods(Assembly assembly)
  76. {
  77. var patcherMethods = new Dictionary<string, IList<AssemblyPatcherDelegate>>(StringComparer.OrdinalIgnoreCase);
  78. foreach (var type in assembly.GetExportedTypes())
  79. {
  80. try
  81. {
  82. if (type.IsInterface)
  83. continue;
  84. PropertyInfo targetsProperty = type.GetProperty(
  85. "TargetDLLs",
  86. BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
  87. null,
  88. typeof(IEnumerable<string>),
  89. Type.EmptyTypes,
  90. null);
  91. MethodInfo patcher = type.GetMethod(
  92. "Patch",
  93. BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
  94. null,
  95. CallingConventions.Any,
  96. new[] { typeof(AssemblyDefinition) },
  97. null);
  98. if (targetsProperty == null || !targetsProperty.CanRead || patcher == null)
  99. continue;
  100. AssemblyPatcherDelegate patchDelegate = (ass) => { patcher.Invoke(null, new object[] {ass}); };
  101. IEnumerable<string> targets = (IEnumerable<string>)targetsProperty.GetValue(null, null);
  102. foreach (string target in targets)
  103. {
  104. if (patcherMethods.TryGetValue(target, out IList<AssemblyPatcherDelegate> patchers))
  105. patchers.Add(patchDelegate);
  106. else
  107. {
  108. patchers = new List<AssemblyPatcherDelegate>{ patchDelegate };
  109. patcherMethods[target] = patchers;
  110. }
  111. }
  112. }
  113. catch (Exception ex)
  114. {
  115. PreloaderLog += $"Could not load patcher methods from {assembly.GetName().Name}";
  116. }
  117. }
  118. PreloaderLog += $"Loaded {patcherMethods.SelectMany(x => x.Value).Distinct().Count()} patcher methods from {assembly.GetName().Name}";
  119. return patcherMethods;
  120. }
  121. internal static void PatchEntrypoint(AssemblyDefinition assembly)
  122. {
  123. if (assembly.Name.Name == "UnityEngine")
  124. {
  125. #if CECIL_10
  126. using (AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(CurrentExecutingAssemblyPath))
  127. #elif CECIL_9
  128. AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(CurrentExecutingAssemblyPath);
  129. #endif
  130. {
  131. var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader")
  132. .Methods.First(x => x.Name == "Initialize");
  133. var injectMethod = assembly.MainModule.ImportReference(originalInjectMethod);
  134. var sceneManager = assembly.MainModule.Types.First(x => x.Name == "Application");
  135. var voidType = assembly.MainModule.ImportReference(typeof(void));
  136. var cctor = new MethodDefinition(".cctor",
  137. MethodAttributes.Static
  138. | MethodAttributes.Private
  139. | MethodAttributes.HideBySig
  140. | MethodAttributes.SpecialName
  141. | MethodAttributes.RTSpecialName,
  142. voidType);
  143. var ilp = cctor.Body.GetILProcessor();
  144. ilp.Append(ilp.Create(OpCodes.Call, injectMethod));
  145. ilp.Append(ilp.Create(OpCodes.Ret));
  146. sceneManager.Methods.Add(cctor);
  147. }
  148. }
  149. }
  150. internal static Assembly LocalResolve(object sender, ResolveEventArgs args)
  151. {
  152. if (args.Name == "0Harmony, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null")
  153. return Assembly.LoadFile(Path.Combine(CurrentExecutingAssemblyDirectoryPath, "0Harmony.dll"));
  154. if (Utility.TryResolveDllAssembly(args.Name, CurrentExecutingAssemblyDirectoryPath, out var assembly) ||
  155. Utility.TryResolveDllAssembly(args.Name, PatcherPluginPath, out assembly) ||
  156. Utility.TryResolveDllAssembly(args.Name, PluginPath, out assembly))
  157. return assembly;
  158. return null;
  159. }
  160. }
  161. }