You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

134 lines
4.3 KiB

  1. using Mono.Cecil;
  2. using Mono.Cecil.Cil;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. namespace IPA.Patcher
  8. {
  9. internal class PatchedModule
  10. {
  11. private static readonly string[] EntryTypes = { "Input", "Display" };
  12. private readonly FileInfo _file;
  13. private ModuleDefinition _module;
  14. internal struct PatchData {
  15. public bool IsPatched;
  16. public Version Version;
  17. }
  18. public static PatchedModule Load(string engineFile)
  19. {
  20. return new PatchedModule(engineFile);
  21. }
  22. private PatchedModule(string engineFile)
  23. {
  24. _file = new FileInfo(engineFile);
  25. LoadModules();
  26. }
  27. private void LoadModules()
  28. {
  29. var resolver = new DefaultAssemblyResolver();
  30. resolver.AddSearchDirectory(_file.DirectoryName);
  31. var parameters = new ReaderParameters
  32. {
  33. AssemblyResolver = resolver,
  34. };
  35. _module = ModuleDefinition.ReadModule(_file.FullName, parameters);
  36. }
  37. public PatchData Data
  38. {
  39. get
  40. {
  41. var data = new PatchData { IsPatched = false, Version = null };
  42. foreach (var @ref in _module.AssemblyReferences) {
  43. switch (@ref.Name)
  44. {
  45. case "IllusionInjector":
  46. case "IllusionPlugin":
  47. data = new PatchData { IsPatched = true, Version = new Version(0, 0, 0, 0) };
  48. break;
  49. case "IPA.Injector":
  50. return new PatchData { IsPatched = true, Version = @ref.Version };
  51. }
  52. }
  53. return data;
  54. }
  55. }
  56. public void Patch(Version v)
  57. {
  58. // First, let's add the reference
  59. var nameReference = new AssemblyNameReference("IPA.Injector", v);
  60. var injectorPath = Path.Combine(_file.DirectoryName ?? throw new InvalidOperationException(), "IPA.Injector.dll");
  61. var injector = ModuleDefinition.ReadModule(injectorPath);
  62. bool hasIPAInjector = false;
  63. for (int i = 0; i < _module.AssemblyReferences.Count; i++)
  64. {
  65. if (_module.AssemblyReferences[i].Name == "IllusionInjector")
  66. _module.AssemblyReferences.RemoveAt(i--);
  67. if (_module.AssemblyReferences[i].Name == "IllusionPlugin")
  68. _module.AssemblyReferences.RemoveAt(i--);
  69. if (_module.AssemblyReferences[i].Name == "IPA.Injector")
  70. {
  71. hasIPAInjector = true;
  72. _module.AssemblyReferences[i].Version = v;
  73. }
  74. }
  75. if (!hasIPAInjector)
  76. {
  77. _module.AssemblyReferences.Add(nameReference);
  78. int patched = 0;
  79. foreach (var type in FindEntryTypes())
  80. {
  81. if (PatchType(type, injector))
  82. {
  83. patched++;
  84. }
  85. }
  86. if (patched > 0)
  87. {
  88. _module.Write(_file.FullName);
  89. }
  90. else
  91. {
  92. throw new Exception("Could not find any entry type!");
  93. }
  94. }
  95. else
  96. {
  97. _module.Write(_file.FullName);
  98. }
  99. }
  100. private bool PatchType(TypeDefinition targetType, ModuleDefinition injector)
  101. {
  102. var targetMethod = targetType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic);
  103. if (targetMethod != null)
  104. {
  105. var methodReference = _module.ImportReference(injector.GetType("IPA.Injector.Injector").Methods.First(m => m.Name == "Inject"));
  106. targetMethod.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Call, methodReference));
  107. return true;
  108. }
  109. return false;
  110. }
  111. private IEnumerable<TypeDefinition> FindEntryTypes()
  112. {
  113. return _module.GetTypes().Where(m => EntryTypes.Contains(m.Name));
  114. }
  115. }
  116. }