AssemblyPatcher.cs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  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<AssemblyPatcherDelegate, IEnumerable<string>> patcherMethodDictionary)
  14. {
  15. //run all initializers
  16. Preloader.Initializers.ForEach(x => x.Invoke());
  17. //load all the requested assemblies
  18. List<AssemblyDefinition> assemblies = new List<AssemblyDefinition>();
  19. Dictionary<AssemblyDefinition, string> assemblyFilenames = new Dictionary<AssemblyDefinition, string>();
  20. foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
  21. {
  22. var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
  23. //NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky
  24. //System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency
  25. //It's also generally dangerous to change system.dll since so many things rely on it,
  26. // and it's already loaded into the appdomain since this loader references it, so we might as well skip it
  27. if (assembly.Name.Name == "System"
  28. || assembly.Name.Name == "mscorlib") //mscorlib is already loaded into the appdomain so it can't be patched
  29. {
  30. #if CECIL_10
  31. assembly.Dispose();
  32. #endif
  33. continue;
  34. }
  35. assemblies.Add(assembly);
  36. assemblyFilenames[assembly] = Path.GetFileName(assemblyPath);
  37. }
  38. //generate a dictionary of each assembly's dependencies
  39. Dictionary<AssemblyDefinition, IList<AssemblyDefinition>> assemblyDependencyDict = new Dictionary<AssemblyDefinition, IList<AssemblyDefinition>>();
  40. foreach (AssemblyDefinition assembly in assemblies)
  41. {
  42. assemblyDependencyDict[assembly] = new List<AssemblyDefinition>();
  43. foreach (var dependencyRef in assembly.MainModule.AssemblyReferences)
  44. {
  45. var dependencyAssembly = assemblies.FirstOrDefault(x => x.FullName == dependencyRef.FullName);
  46. if (dependencyAssembly != null)
  47. assemblyDependencyDict[assembly].Add(dependencyAssembly);
  48. }
  49. }
  50. //sort the assemblies so load the assemblies that are dependant upon first
  51. AssemblyDefinition[] sortedAssemblies = Utility.TopologicalSort(assemblies, x => assemblyDependencyDict[x]).ToArray();
  52. List<string> sortedAssemblyFilenames = sortedAssemblies.Select(x => assemblyFilenames[x]).ToList();
  53. //call the patchers on the assemblies
  54. foreach (var patcherMethod in patcherMethodDictionary)
  55. {
  56. foreach (string assemblyFilename in patcherMethod.Value)
  57. {
  58. int index = sortedAssemblyFilenames.FindIndex(x => x == assemblyFilename);
  59. if (index < 0)
  60. continue;
  61. Patch(ref sortedAssemblies[index], patcherMethod.Key);
  62. }
  63. }
  64. for (int i = 0; i < sortedAssemblies.Length; i++)
  65. {
  66. string filename = Path.GetFileName(assemblyFilenames[sortedAssemblies[i]]);
  67. if (DumpingEnabled)
  68. {
  69. using (MemoryStream mem = new MemoryStream())
  70. {
  71. string dirPath = Path.Combine(Preloader.PluginPath, "DumpedAssemblies");
  72. if (!Directory.Exists(dirPath))
  73. Directory.CreateDirectory(dirPath);
  74. sortedAssemblies[i].Write(mem);
  75. File.WriteAllBytes(Path.Combine(dirPath, filename), mem.ToArray());
  76. }
  77. }
  78. Load(sortedAssemblies[i]);
  79. #if CECIL_10
  80. sortedAssemblies[i].Dispose();
  81. #endif
  82. }
  83. //run all initializers
  84. Preloader.Initializers.ForEach(x => x.Invoke());
  85. }
  86. public static void Patch(ref AssemblyDefinition assembly, AssemblyPatcherDelegate patcherMethod)
  87. {
  88. patcherMethod.Invoke(ref assembly);
  89. }
  90. public static void Load(AssemblyDefinition assembly)
  91. {
  92. using (MemoryStream assemblyStream = new MemoryStream())
  93. {
  94. assembly.Write(assemblyStream);
  95. Assembly.Load(assemblyStream.ToArray());
  96. }
  97. }
  98. }
  99. }