Program.cs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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. string unityOutputDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll");
  39. if (!Directory.Exists(managedDir) || !File.Exists(unityOutputDLL))
  40. continue;
  41. hasFound = true;
  42. Console.Write($"Patching {gameName}... ");
  43. if (PatchUnityExe(managedDir, unityOutputDLL, out string message))
  44. {
  45. WriteSuccess();
  46. patchCount++;
  47. }
  48. else
  49. {
  50. WriteError(message);
  51. hasFailure = true;
  52. }
  53. }
  54. Console.WriteLine();
  55. if (!hasFound)
  56. Console.WriteLine("Didn't find any games to patch! Exiting.");
  57. else
  58. Console.WriteLine($"Patched {patchCount} assemblies!");
  59. if (hasFailure)
  60. {
  61. Console.WriteLine("Press any key to continue...");
  62. Console.ReadKey();
  63. }
  64. else
  65. System.Threading.Thread.Sleep(3000);
  66. }
  67. static bool PatchUnityExe(string managedDir, string unityOutputDLL, out string message)
  68. {
  69. message = null;
  70. try
  71. {
  72. string injectedDLL = Path.GetFullPath($"{managedDir}\\BepInEx.Bootstrap.dll");
  73. File.WriteAllBytes(injectedDLL, EmbeddedResource.Get("BepInEx.Patcher.BepInEx.Bootstrap.dll"));
  74. var defaultResolver = new DefaultAssemblyResolver();
  75. defaultResolver.AddSearchDirectory(managedDir);
  76. var rp = new ReaderParameters
  77. {
  78. AssemblyResolver = defaultResolver
  79. };
  80. string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll.bak");
  81. //determine which assembly to use as a base
  82. AssemblyDefinition unity = AssemblyDefinition.ReadAssembly(unityOutputDLL, rp);
  83. if (!VerifyAssembly(unity, out message))
  84. {
  85. //try and fall back to .bak if exists
  86. if (File.Exists(unityBackupDLL))
  87. {
  88. unity.Dispose();
  89. unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
  90. if (!VerifyAssembly(unity, out message))
  91. {
  92. //can't use anything
  93. unity.Dispose();
  94. message += "\r\nThe backup is not usable.";
  95. return false;
  96. }
  97. }
  98. else
  99. {
  100. //can't use anything
  101. unity.Dispose();
  102. message += "\r\nNo backup exists.";
  103. return false;
  104. }
  105. }
  106. else
  107. {
  108. //make a backup of the assembly
  109. File.Copy(unityOutputDLL, unityBackupDLL, true);
  110. unity.Dispose();
  111. unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
  112. }
  113. //patch
  114. using (unity)
  115. using (AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(injectedDLL, rp))
  116. {
  117. InjectAssembly(unity, injected);
  118. unity.Write(unityOutputDLL);
  119. }
  120. }
  121. catch (Exception e)
  122. {
  123. message = e.ToString();
  124. return false;
  125. }
  126. return true;
  127. }
  128. static void InjectAssembly(AssemblyDefinition unity, AssemblyDefinition injected)
  129. {
  130. //Entry point
  131. var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Entrypoint")
  132. .Methods.First(x => x.Name == "Init");
  133. var injectMethod = unity.MainModule.ImportReference(originalInjectMethod);
  134. var sceneManager = unity.MainModule.Types.First(x => x.Name == "Application");
  135. var voidType = unity.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. static bool VerifyAssembly(AssemblyDefinition unity, out string message)
  149. {
  150. bool canPatch = true;
  151. message = "";
  152. //check if already patched
  153. if (unity.MainModule.AssemblyReferences.Any(x => x.Name == "BepInEx"))
  154. {
  155. canPatch = false;
  156. message += "This assembly has already been patched by BepInEx.\r\n";
  157. }
  158. message = message.Trim();
  159. return canPatch;
  160. }
  161. }
  162. }