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.

365 lines
14 KiB

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