Program.cs 5.2 KB

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