Program.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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 harmony = Path.GetFullPath($"{managedDir}\\0Harmony.dll");
  73. File.WriteAllBytes(harmony, EmbeddedResource.Get("BepInEx.Patcher.0Harmony.dll"));
  74. string injectedDLL = Path.GetFullPath($"{managedDir}\\BepInEx.dll");
  75. File.WriteAllBytes(injectedDLL, EmbeddedResource.Get("BepInEx.Patcher.BepInEx.dll"));
  76. var defaultResolver = new DefaultAssemblyResolver();
  77. defaultResolver.AddSearchDirectory(managedDir);
  78. var rp = new ReaderParameters
  79. {
  80. AssemblyResolver = defaultResolver
  81. };
  82. string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll.bak");
  83. //determine which assembly to use as a base
  84. AssemblyDefinition unity = AssemblyDefinition.ReadAssembly(unityOutputDLL, rp);
  85. if (!VerifyAssembly(unity, out message))
  86. {
  87. //try and fall back to .bak if exists
  88. if (File.Exists(unityBackupDLL))
  89. {
  90. unity.Dispose();
  91. unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
  92. if (!VerifyAssembly(unity, out message))
  93. {
  94. //can't use anything
  95. unity.Dispose();
  96. message += "\r\nThe backup is not usable.";
  97. return false;
  98. }
  99. }
  100. else
  101. {
  102. //can't use anything
  103. unity.Dispose();
  104. message += "\r\nNo backup exists.";
  105. return false;
  106. }
  107. }
  108. else
  109. {
  110. //make a backup of the assembly
  111. File.Copy(unityOutputDLL, unityBackupDLL, true);
  112. unity.Dispose();
  113. unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
  114. }
  115. //patch
  116. using (unity)
  117. using (AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(injectedDLL, rp))
  118. {
  119. InjectAssembly(unity, injected);
  120. unity.Write(unityOutputDLL);
  121. }
  122. }
  123. catch (Exception e)
  124. {
  125. message = e.ToString();
  126. return false;
  127. }
  128. return true;
  129. }
  130. static void InjectAssembly(AssemblyDefinition unity, AssemblyDefinition injected)
  131. {
  132. //Entry point
  133. var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader")
  134. .Methods.First(x => x.Name == "Initialize");
  135. var injectMethod = unity.MainModule.ImportReference(originalInjectMethod);
  136. var sceneManager = unity.MainModule.Types.First(x => x.Name == "Application");
  137. var voidType = unity.MainModule.ImportReference(typeof(void));
  138. var cctor = new MethodDefinition(".cctor",
  139. MethodAttributes.Static
  140. | MethodAttributes.Private
  141. | MethodAttributes.HideBySig
  142. | MethodAttributes.SpecialName
  143. | MethodAttributes.RTSpecialName,
  144. voidType);
  145. var ilp = cctor.Body.GetILProcessor();
  146. ilp.Append(ilp.Create(OpCodes.Call, injectMethod));
  147. ilp.Append(ilp.Create(OpCodes.Ret));
  148. sceneManager.Methods.Add(cctor);
  149. }
  150. static bool VerifyAssembly(AssemblyDefinition unity, out string message)
  151. {
  152. bool canPatch = true;
  153. message = "";
  154. //check if already patched
  155. var sceneManager = unity.MainModule.Types.First(x => x.Name == "Application");
  156. if (sceneManager.Methods.Any(x => x.Name == ".cctor"))
  157. {
  158. //already patched by bepin
  159. canPatch = false;
  160. message += "This assembly has already been patched by BepInEx.";
  161. }
  162. return canPatch;
  163. }
  164. }
  165. }