You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

338 lines
13 KiB

  1. #nullable enable
  2. using IPA.AntiMalware;
  3. using IPA.Config;
  4. using IPA.Injector.Backups;
  5. using IPA.Loader;
  6. using IPA.Logging;
  7. using IPA.Utilities;
  8. using Mono.Cecil;
  9. using Mono.Cecil.Cil;
  10. using System;
  11. using System.Diagnostics;
  12. using System.IO;
  13. using System.Linq;
  14. using System.Reflection;
  15. using System.Threading.Tasks;
  16. using UnityEngine;
  17. using static IPA.Logging.Logger;
  18. using MethodAttributes = Mono.Cecil.MethodAttributes;
  19. #if NET3
  20. using Net3_Proxy;
  21. using Path = Net3_Proxy.Path;
  22. using File = Net3_Proxy.File;
  23. using Directory = Net3_Proxy.Directory;
  24. #endif
  25. namespace IPA.Injector
  26. {
  27. /// <summary>
  28. /// The entry point type for BSIPA's Doorstop injector.
  29. /// </summary>
  30. // ReSharper disable once UnusedMember.Global
  31. internal static class Injector
  32. {
  33. private static Task? pluginAsyncLoadTask;
  34. private static Task? permissionFixTask;
  35. //private static string otherNewtonsoftJson = null;
  36. // ReSharper disable once UnusedParameter.Global
  37. internal static void Main(string[] args)
  38. { // entry point for doorstop
  39. // At this point, literally nothing but mscorlib is loaded,
  40. // and since this class doesn't have any static fields that
  41. // aren't defined in mscorlib, we can control exactly what
  42. // gets loaded.
  43. _ = args;
  44. try
  45. {
  46. if (Environment.GetCommandLineArgs().Contains("--verbose"))
  47. WinConsole.Initialize();
  48. SetupLibraryLoading();
  49. EnsureDirectories();
  50. // this is weird, but it prevents Mono from having issues loading the type.
  51. // IMPORTANT: NO CALLS TO ANY LOGGER CAN HAPPEN BEFORE THIS
  52. var unused = StandardLogger.PrintFilter;
  53. #region // Above hack explaination
  54. /*
  55. * Due to an unknown bug in the version of Mono that Unity uses, if the first access to StandardLogger
  56. * is a call to a constructor, then Mono fails to load the type correctly. However, if the first access is to
  57. * the above static property (or maybe any, but I don't really know) it behaves as expected and works fine.
  58. */
  59. #endregion
  60. Default.Debug("Initializing logger");
  61. SelfConfig.ReadCommandLine(Environment.GetCommandLineArgs());
  62. SelfConfig.Load();
  63. DisabledConfig.Load();
  64. if (AntiPiracy.IsInvalid(Environment.CurrentDirectory))
  65. {
  66. Default.Error("Invalid installation; please buy the game to run BSIPA.");
  67. return;
  68. }
  69. CriticalSection.Configure();
  70. Logging.Logger.Injector.Debug("Prepping bootstrapper");
  71. // updates backup
  72. InstallBootstrapPatch();
  73. GameVersionEarly.Load();
  74. AntiMalwareEngine.Initialize();
  75. Updates.InstallPendingUpdates();
  76. Loader.LibLoader.SetupAssemblyFilenames(true);
  77. pluginAsyncLoadTask = PluginLoader.LoadTask();
  78. permissionFixTask = PermissionFix.FixPermissions(new DirectoryInfo(Environment.CurrentDirectory));
  79. }
  80. catch (Exception e)
  81. {
  82. Console.WriteLine(e);
  83. }
  84. }
  85. private static void EnsureDirectories()
  86. {
  87. string path;
  88. if (!Directory.Exists(path = Path.Combine(Environment.CurrentDirectory, "UserData")))
  89. _ = Directory.CreateDirectory(path);
  90. if (!Directory.Exists(path = Path.Combine(Environment.CurrentDirectory, "Plugins")))
  91. _ = Directory.CreateDirectory(path);
  92. }
  93. private static void SetupLibraryLoading()
  94. {
  95. if (loadingDone) return;
  96. loadingDone = true;
  97. Loader.LibLoader.Configure();
  98. }
  99. private static void InstallHarmonyProtections()
  100. { // proxy function to delay resolution
  101. HarmonyProtectorProxy.ProtectNull();
  102. }
  103. private static void InstallBootstrapPatch()
  104. {
  105. var sw = Stopwatch.StartNew();
  106. var cAsmName = Assembly.GetExecutingAssembly().GetName();
  107. var managedPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
  108. var dataDir = new DirectoryInfo(managedPath).Parent.Name;
  109. var gameName = dataDir.Substring(0, dataDir.Length - 5);
  110. Logging.Logger.Injector.Debug("Finding backup");
  111. var backupPath = Path.Combine(Environment.CurrentDirectory, "IPA", "Backups", gameName);
  112. var bkp = BackupManager.FindLatestBackup(backupPath);
  113. if (bkp == null)
  114. Logging.Logger.Injector.Warn("No backup found! Was BSIPA installed using the installer?");
  115. Logging.Logger.Injector.Debug("Ensuring patch on UnityEngine.CoreModule exists");
  116. #region Insert patch into UnityEngine.CoreModule.dll
  117. {
  118. var unityPath = Path.Combine(managedPath,
  119. "UnityEngine.CoreModule.dll");
  120. // this is a critical section because if you exit in here, CoreModule can die
  121. using var critSec = CriticalSection.ExecuteSection();
  122. using var unityAsmDef = AssemblyDefinition.ReadAssembly(unityPath, new ReaderParameters
  123. {
  124. ReadWrite = false,
  125. InMemory = true,
  126. ReadingMode = ReadingMode.Immediate
  127. });
  128. var unityModDef = unityAsmDef.MainModule;
  129. bool modified = false;
  130. foreach (var asmref in unityModDef.AssemblyReferences)
  131. {
  132. if (asmref.Name == cAsmName.Name)
  133. {
  134. if (asmref.Version != cAsmName.Version)
  135. {
  136. asmref.Version = cAsmName.Version;
  137. modified = true;
  138. }
  139. }
  140. }
  141. var application = unityModDef.GetType("UnityEngine", "Camera");
  142. if (application == null)
  143. {
  144. Logging.Logger.Injector.Critical("UnityEngine.CoreModule doesn't have a definition for UnityEngine.Camera!"
  145. + "Nothing to patch to get ourselves into the Unity run cycle!");
  146. goto endPatchCoreModule;
  147. }
  148. MethodDefinition? cctor = null;
  149. foreach (var m in application.Methods)
  150. if (m.IsRuntimeSpecialName && m.Name == ".cctor")
  151. cctor = m;
  152. var cbs = unityModDef.ImportReference(((Action)CreateBootstrapper).Method);
  153. if (cctor == null)
  154. {
  155. cctor = new MethodDefinition(".cctor",
  156. MethodAttributes.RTSpecialName | MethodAttributes.Static | MethodAttributes.SpecialName,
  157. unityModDef.TypeSystem.Void);
  158. application.Methods.Add(cctor);
  159. modified = true;
  160. var ilp = cctor.Body.GetILProcessor();
  161. ilp.Emit(OpCodes.Call, cbs);
  162. ilp.Emit(OpCodes.Ret);
  163. }
  164. else
  165. {
  166. var ilp = cctor.Body.GetILProcessor();
  167. for (var i = 0; i < Math.Min(2, cctor.Body.Instructions.Count); i++)
  168. {
  169. var ins = cctor.Body.Instructions[i];
  170. switch (i)
  171. {
  172. case 0 when ins.OpCode != OpCodes.Call:
  173. ilp.Replace(ins, ilp.Create(OpCodes.Call, cbs));
  174. modified = true;
  175. break;
  176. case 0:
  177. {
  178. var methodRef = ins.Operand as MethodReference;
  179. if (methodRef?.FullName != cbs.FullName)
  180. {
  181. ilp.Replace(ins, ilp.Create(OpCodes.Call, cbs));
  182. modified = true;
  183. }
  184. break;
  185. }
  186. case 1 when ins.OpCode != OpCodes.Ret:
  187. ilp.Replace(ins, ilp.Create(OpCodes.Ret));
  188. modified = true;
  189. break;
  190. }
  191. }
  192. }
  193. if (modified)
  194. {
  195. bkp?.Add(unityPath);
  196. unityAsmDef.Write(unityPath);
  197. }
  198. }
  199. endPatchCoreModule:
  200. #endregion Insert patch into UnityEngine.CoreModule.dll
  201. Logging.Logger.Injector.Debug("Ensuring game assemblies are virtualized");
  202. #region Virtualize game assemblies
  203. bool isFirst = true;
  204. foreach (var name in SelfConfig.GameAssemblies_)
  205. {
  206. var ascPath = Path.Combine(managedPath, name);
  207. using var execSec = CriticalSection.ExecuteSection();
  208. try
  209. {
  210. Logging.Logger.Injector.Debug($"Virtualizing {name}");
  211. using var ascModule = VirtualizedModule.Load(ascPath);
  212. ascModule.Virtualize(cAsmName, () => bkp?.Add(ascPath));
  213. }
  214. catch (Exception e)
  215. {
  216. Logging.Logger.Injector.Error($"Could not virtualize {ascPath}");
  217. if (SelfConfig.Debug_.ShowHandledErrorStackTraces_)
  218. Logging.Logger.Injector.Error(e);
  219. }
  220. #if BeatSaber
  221. if (isFirst)
  222. {
  223. try
  224. {
  225. Logging.Logger.Injector.Debug("Applying anti-yeet patch");
  226. using var ascAsmDef = AssemblyDefinition.ReadAssembly(ascPath, new ReaderParameters
  227. {
  228. ReadWrite = false,
  229. InMemory = true,
  230. ReadingMode = ReadingMode.Immediate
  231. });
  232. var ascModDef = ascAsmDef.MainModule;
  233. var deleter = ascModDef.GetType("IPAPluginsDirDeleter");
  234. deleter.Methods.Clear(); // delete all methods
  235. ascAsmDef.Write(ascPath);
  236. isFirst = false;
  237. }
  238. catch (Exception e)
  239. {
  240. Logging.Logger.Injector.Warn($"Could not apply anti-yeet patch to {ascPath}");
  241. if (SelfConfig.Debug_.ShowHandledErrorStackTraces_)
  242. Logging.Logger.Injector.Warn(e);
  243. }
  244. }
  245. #endif
  246. }
  247. #endregion
  248. sw.Stop();
  249. Logging.Logger.Injector.Info($"Installing bootstrapper took {sw.Elapsed}");
  250. }
  251. private static bool bootstrapped;
  252. private static void CreateBootstrapper()
  253. {
  254. if (bootstrapped) return;
  255. bootstrapped = true;
  256. Application.logMessageReceived += delegate (string condition, string stackTrace, LogType type)
  257. {
  258. var level = UnityLogRedirector.LogTypeToLevel(type);
  259. UnityLogProvider.UnityLogger.Log(level, $"{condition}");
  260. UnityLogProvider.UnityLogger.Log(level, $"{stackTrace}");
  261. };
  262. StdoutInterceptor.EnsureHarmonyLogging();
  263. // need to reinit streams singe Unity seems to redirect stdout
  264. StdoutInterceptor.RedirectConsole();
  265. InstallHarmonyProtections();
  266. var bootstrapper = new GameObject("NonDestructiveBootstrapper").AddComponent<Bootstrapper>();
  267. bootstrapper.Destroyed += Bootstrapper_Destroyed;
  268. }
  269. private static bool loadingDone;
  270. private static void Bootstrapper_Destroyed()
  271. {
  272. // wait for plugins to finish loading
  273. pluginAsyncLoadTask?.Wait();
  274. permissionFixTask?.Wait();
  275. Default.Debug("Plugins loaded");
  276. Default.Debug(string.Join(", ", PluginLoader.PluginsMetadata.StrJP()));
  277. _ = PluginComponent.Create();
  278. }
  279. }
  280. }