Program.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. using Mono.Cecil;
  2. using Mono.Cecil.Cil;
  3. using System;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using MethodAttributes = Mono.Cecil.MethodAttributes;
  8. namespace BepInEx.Patcher
  9. {
  10. class Program
  11. {
  12. static void WriteError(string message)
  13. {
  14. Console.ForegroundColor = ConsoleColor.Red;
  15. Console.WriteLine("Failed");
  16. Console.ResetColor();
  17. Console.WriteLine(message);
  18. Console.WriteLine();
  19. }
  20. static void WriteSuccess()
  21. {
  22. Console.ForegroundColor = ConsoleColor.Green;
  23. Console.WriteLine("Success");
  24. Console.ResetColor();
  25. }
  26. static string GetUnityEngineAssembly(string managedDir)
  27. {
  28. var path = Path.Combine(managedDir, "UnityEngine.CoreModule.dll");
  29. if (File.Exists(path))
  30. return path;
  31. return Path.Combine(managedDir, "UnityEngine.dll");
  32. }
  33. static void Main(string[] args)
  34. {
  35. Console.WriteLine($"BepInEx Patcher v{Assembly.GetExecutingAssembly().GetName().Version}");
  36. if (args.Length >= 1) //short circuit for specific dll patch
  37. Environment.Exit(PatchUnityExe(Path.GetDirectoryName(args[0]), args[0], out string message) ? 0 : 9999);
  38. bool hasFound = false;
  39. bool hasFailure = false;
  40. int patchCount = 0;
  41. foreach (string exePath in Directory.GetFiles(Directory.GetCurrentDirectory()))
  42. {
  43. string gameName = Path.GetFileNameWithoutExtension(exePath);
  44. string managedDir = Environment.CurrentDirectory + $@"\{gameName}_Data\Managed";
  45. string unityOutputDLL = GetUnityEngineAssembly(managedDir);
  46. if (!Directory.Exists(managedDir) || !File.Exists(unityOutputDLL))
  47. continue;
  48. hasFound = true;
  49. Console.Write($"Patching {gameName}... ");
  50. if (PatchUnityExe(managedDir, unityOutputDLL, out string message))
  51. {
  52. WriteSuccess();
  53. patchCount++;
  54. }
  55. else
  56. {
  57. WriteError(message);
  58. hasFailure = true;
  59. }
  60. }
  61. Console.WriteLine();
  62. if (!hasFound)
  63. Console.WriteLine("Didn't find any games to patch! Exiting.");
  64. else
  65. Console.WriteLine($"Patched {patchCount} assemblies!");
  66. if (hasFailure)
  67. {
  68. Console.WriteLine("Press any key to continue...");
  69. Console.ReadKey();
  70. }
  71. else
  72. System.Threading.Thread.Sleep(3000);
  73. }
  74. static bool PatchUnityExe(string managedDir, string unityOutputDLL, out string message)
  75. {
  76. message = null;
  77. try
  78. {
  79. string injectedDLL = Path.GetFullPath($"{managedDir}\\BepInEx.Bootstrap.dll");
  80. File.WriteAllBytes(injectedDLL, EmbeddedResource.Get("BepInEx.Patcher.BepInEx.Bootstrap.dll"));
  81. var defaultResolver = new DefaultAssemblyResolver();
  82. defaultResolver.AddSearchDirectory(managedDir);
  83. var rp = new ReaderParameters
  84. {
  85. AssemblyResolver = defaultResolver
  86. };
  87. string unityBackupDLL = $"{GetUnityEngineAssembly(managedDir)}.bak";
  88. //determine which assembly to use as a base
  89. AssemblyDefinition unity = AssemblyDefinition.ReadAssembly(unityOutputDLL, rp);
  90. if (!VerifyAssembly(unity, out message))
  91. {
  92. //try and fall back to .bak if exists
  93. if (File.Exists(unityBackupDLL))
  94. {
  95. unity.Dispose();
  96. unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
  97. if (!VerifyAssembly(unity, out message))
  98. {
  99. //can't use anything
  100. unity.Dispose();
  101. message += "\r\nThe backup is not usable.";
  102. return false;
  103. }
  104. }
  105. else
  106. {
  107. //can't use anything
  108. unity.Dispose();
  109. message += "\r\nNo backup exists.";
  110. return false;
  111. }
  112. }
  113. else
  114. {
  115. //make a backup of the assembly
  116. File.Copy(unityOutputDLL, unityBackupDLL, true);
  117. unity.Dispose();
  118. unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
  119. }
  120. //patch
  121. using (unity)
  122. using (AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(injectedDLL, rp))
  123. {
  124. InjectAssembly(unity, injected);
  125. unity.Write(unityOutputDLL);
  126. }
  127. }
  128. catch (Exception e)
  129. {
  130. message = e.ToString();
  131. return false;
  132. }
  133. return true;
  134. }
  135. static void InjectAssembly(AssemblyDefinition unity, AssemblyDefinition injected)
  136. {
  137. //Entry point
  138. var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Entrypoint")
  139. .Methods.First(x => x.Name == "Init");
  140. var injectMethod = unity.MainModule.ImportReference(originalInjectMethod);
  141. var sceneManager = unity.MainModule.Types.First(x => x.Name == "Application");
  142. var voidType = unity.MainModule.ImportReference(typeof(void));
  143. var cctor = new MethodDefinition(".cctor",
  144. MethodAttributes.Static
  145. | MethodAttributes.Private
  146. | MethodAttributes.HideBySig
  147. | MethodAttributes.SpecialName
  148. | MethodAttributes.RTSpecialName,
  149. voidType);
  150. var ilp = cctor.Body.GetILProcessor();
  151. ilp.Append(ilp.Create(OpCodes.Call, injectMethod));
  152. ilp.Append(ilp.Create(OpCodes.Ret));
  153. sceneManager.Methods.Add(cctor);
  154. }
  155. static bool VerifyAssembly(AssemblyDefinition unity, out string message)
  156. {
  157. bool canPatch = true;
  158. message = "";
  159. //check if already patched
  160. if (unity.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx")))
  161. {
  162. canPatch = false;
  163. message += "This assembly has already been patched by BepInEx.\r\n";
  164. }
  165. message = message.Trim();
  166. return canPatch;
  167. }
  168. }
  169. }