AssemblyPatcher.cs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. using System.Collections.Generic;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Reflection;
  5. using BepInEx.Common;
  6. using Mono.Cecil;
  7. namespace BepInEx.Bootstrap
  8. {
  9. public delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly);
  10. public static class AssemblyPatcher
  11. {
  12. private static bool DumpingEnabled => bool.TryParse(Config.GetEntry("preloader-dumpassemblies", "false"), out bool result) ? result : false;
  13. public static void PatchAll(string directory, Dictionary<string, IList<AssemblyPatcherDelegate>> patcherMethodDictionary)
  14. {
  15. //load all the requested assemblies
  16. List<AssemblyDefinition> assemblies = new List<AssemblyDefinition>();
  17. Dictionary<AssemblyDefinition, string> assemblyFilenames = new Dictionary<AssemblyDefinition, string>();
  18. foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
  19. {
  20. var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
  21. //NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky
  22. //System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency
  23. //It's also generally dangerous to change system.dll since so many things rely on it,
  24. // and it's already loaded into the appdomain since this loader references it, so we might as well skip it
  25. if (assembly.Name.Name == "System"
  26. || assembly.Name.Name == "mscorlib") //mscorlib is already loaded into the appdomain so it can't be patched
  27. {
  28. #if CECIL_10
  29. assembly.Dispose();
  30. #endif
  31. continue;
  32. }
  33. assemblies.Add(assembly);
  34. assemblyFilenames[assembly] = Path.GetFileName(assemblyPath);
  35. }
  36. //generate a dictionary of each assembly's dependencies
  37. Dictionary<AssemblyDefinition, IList<AssemblyDefinition>> assemblyDependencyDict = new Dictionary<AssemblyDefinition, IList<AssemblyDefinition>>();
  38. foreach (AssemblyDefinition assembly in assemblies)
  39. {
  40. assemblyDependencyDict[assembly] = new List<AssemblyDefinition>();
  41. foreach (var dependencyRef in assembly.MainModule.AssemblyReferences)
  42. {
  43. var dependencyAssembly = assemblies.FirstOrDefault(x => x.FullName == dependencyRef.FullName);
  44. if (dependencyAssembly != null)
  45. assemblyDependencyDict[assembly].Add(dependencyAssembly);
  46. }
  47. }
  48. //sort the assemblies so load the assemblies that are dependant upon first
  49. AssemblyDefinition[] sortedAssemblies = Utility.TopologicalSort(assemblies, x => assemblyDependencyDict[x]).ToArray();
  50. //call the patchers on the assemblies
  51. for (int i = 0; i < sortedAssemblies.Length; i++)
  52. {
  53. string filename = Path.GetFileName(assemblyFilenames[sortedAssemblies[i]]);
  54. //skip if we aren't patching it
  55. if (!patcherMethodDictionary.TryGetValue(filename, out IList<AssemblyPatcherDelegate> patcherMethods))
  56. continue;
  57. Patch(ref sortedAssemblies[i], patcherMethods);
  58. if (DumpingEnabled)
  59. {
  60. using (MemoryStream mem = new MemoryStream())
  61. {
  62. string dirPath = Path.Combine(Preloader.PluginPath, "DumpedAssemblies");
  63. if (!Directory.Exists(dirPath))
  64. Directory.CreateDirectory(dirPath);
  65. sortedAssemblies[i].Write(mem);
  66. File.WriteAllBytes(Path.Combine(dirPath, filename), mem.ToArray());
  67. }
  68. }
  69. #if CECIL_10
  70. sortedAssemblies[i].Dispose();
  71. #endif
  72. }
  73. }
  74. public static void Patch(ref AssemblyDefinition assembly, IEnumerable<AssemblyPatcherDelegate> patcherMethods)
  75. {
  76. using (MemoryStream assemblyStream = new MemoryStream())
  77. {
  78. foreach (AssemblyPatcherDelegate method in patcherMethods)
  79. method.Invoke(ref assembly);
  80. assembly.Write(assemblyStream);
  81. Assembly.Load(assemblyStream.ToArray());
  82. }
  83. }
  84. }
  85. }