AssemblyPatcher.cs 3.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  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(AssemblyDefinition assembly);
  10. public static class AssemblyPatcher
  11. {
  12. public static void PatchAll(string directory, Dictionary<string, IList<AssemblyPatcherDelegate>> patcherMethodDictionary)
  13. {
  14. //load all the requested assemblies
  15. List<AssemblyDefinition> assemblies = new List<AssemblyDefinition>();
  16. Dictionary<AssemblyDefinition, string> assemblyFilenames = new Dictionary<AssemblyDefinition, string>();
  17. foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
  18. {
  19. var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
  20. //NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky
  21. //System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency
  22. //It's also generally dangerous to change system.dll since so many things rely on it,
  23. // and it's already loaded into the appdomain since this loader references it, so we might as well skip it
  24. if (assembly.Name.Name == "System"
  25. || assembly.Name.Name == "mscorlib") //mscorlib is already loaded into the appdomain so it can't be patched
  26. {
  27. #if CECIL_10
  28. assembly.Dispose();
  29. #endif
  30. continue;
  31. }
  32. assemblies.Add(assembly);
  33. assemblyFilenames[assembly] = Path.GetFileName(assemblyPath);
  34. }
  35. //generate a dictionary of each assembly's dependencies
  36. Dictionary<AssemblyDefinition, IList<AssemblyDefinition>> assemblyDependencyDict = new Dictionary<AssemblyDefinition, IList<AssemblyDefinition>>();
  37. foreach (AssemblyDefinition assembly in assemblies)
  38. {
  39. assemblyDependencyDict[assembly] = new List<AssemblyDefinition>();
  40. foreach (var dependencyRef in assembly.MainModule.AssemblyReferences)
  41. {
  42. var dependencyAssembly = assemblies.FirstOrDefault(x => x.FullName == dependencyRef.FullName);
  43. if (dependencyAssembly != null)
  44. assemblyDependencyDict[assembly].Add(dependencyAssembly);
  45. }
  46. }
  47. //sort the assemblies so load the assemblies that are dependant upon first
  48. IEnumerable<AssemblyDefinition> sortedAssemblies = Utility.TopologicalSort(assemblies, x => assemblyDependencyDict[x]);
  49. //call the patchers on the assemblies
  50. foreach (var assembly in sortedAssemblies)
  51. {
  52. #if CECIL_10
  53. using (assembly)
  54. #endif
  55. {
  56. //skip if we aren't patching it
  57. if (!patcherMethodDictionary.TryGetValue(Path.GetFileName(assemblyFilenames[assembly]), out IList<AssemblyPatcherDelegate> patcherMethods))
  58. continue;
  59. Patch(assembly, patcherMethods);
  60. }
  61. }
  62. }
  63. public static void Patch(AssemblyDefinition assembly, IEnumerable<AssemblyPatcherDelegate> patcherMethods)
  64. {
  65. using (MemoryStream assemblyStream = new MemoryStream())
  66. {
  67. foreach (AssemblyPatcherDelegate method in patcherMethods)
  68. method.Invoke(assembly);
  69. assembly.Write(assemblyStream);
  70. Assembly.Load(assemblyStream.ToArray());
  71. }
  72. }
  73. }
  74. }