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.

222 lines
7.0 KiB

  1. using Mono.Cecil;
  2. using Mono.Cecil.Rocks;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Reflection;
  7. namespace IPA.Injector
  8. {
  9. internal class VirtualizedModule : IDisposable
  10. {
  11. private readonly FileInfo file;
  12. private ModuleDefinition module;
  13. public static VirtualizedModule Load(string engineFile)
  14. {
  15. return new VirtualizedModule(engineFile);
  16. }
  17. private VirtualizedModule(string assemblyFile)
  18. {
  19. file = new FileInfo(assemblyFile);
  20. LoadModules();
  21. }
  22. private void LoadModules()
  23. {
  24. module = ModuleDefinition.ReadModule(file.FullName, new ReaderParameters
  25. {
  26. ReadWrite = false,
  27. InMemory = true,
  28. ReadingMode = ReadingMode.Immediate
  29. });
  30. }
  31. public void Virtualize(AssemblyName selfName, Action beforeChangeCallback = null)
  32. {
  33. var changed = false;
  34. var virtualize = true;
  35. foreach (var r in module.AssemblyReferences)
  36. {
  37. if (r.Name == selfName.Name)
  38. {
  39. virtualize = false;
  40. if (r.Version != selfName.Version)
  41. {
  42. r.Version = selfName.Version;
  43. changed = true;
  44. }
  45. }
  46. }
  47. if (virtualize)
  48. {
  49. changed = true;
  50. module.AssemblyReferences.Add(new AssemblyNameReference(selfName.Name, selfName.Version));
  51. foreach (var type in module.Types)
  52. {
  53. VirtualizeType(type);
  54. }
  55. }
  56. if (changed)
  57. {
  58. beforeChangeCallback?.Invoke();
  59. module.Write(file.FullName);
  60. }
  61. }
  62. private TypeReference inModreqRef;
  63. // private TypeReference outModreqRef;
  64. private void VirtualizeType(TypeDefinition type)
  65. {
  66. if(type.IsSealed)
  67. {
  68. // Unseal
  69. type.IsSealed = false;
  70. }
  71. if (type.IsNestedPrivate)
  72. {
  73. type.IsNestedPrivate = false;
  74. type.IsNestedPublic = true;
  75. }
  76. if (type.IsInterface) return;
  77. if (type.IsAbstract) return;
  78. // These two don't seem to work.
  79. if (type.Name == "SceneControl" || type.Name == "ConfigUI") return;
  80. // Take care of sub types
  81. foreach (var subType in type.NestedTypes)
  82. {
  83. VirtualizeType(subType);
  84. }
  85. foreach (var method in type.Methods)
  86. {
  87. if (method.IsManaged
  88. && method.IsIL
  89. && (!method.IsVirtual || method.IsFinal)
  90. && !method.IsAbstract
  91. && !method.IsAddOn
  92. && !method.IsConstructor
  93. && !method.IsSpecialName
  94. && !method.IsGenericInstance
  95. && !method.HasOverrides)
  96. {
  97. if (!method.IsStatic)
  98. {
  99. // fix In parameters to have the modreqs required by the compiler
  100. foreach (var param in method.Parameters)
  101. {
  102. if (param.IsIn)
  103. {
  104. inModreqRef ??= module.ImportReference(typeof(System.Runtime.InteropServices.InAttribute));
  105. param.ParameterType = AddModreqIfNotExist(param.ParameterType, inModreqRef);
  106. }
  107. // Breaks override methods if modreq is applied to `out` parameters
  108. //if (param.IsOut)
  109. //{
  110. // outModreqRef ??= module.ImportReference(typeof(System.Runtime.InteropServices.OutAttribute));
  111. // param.ParameterType = AddModreqIfNotExist(param.ParameterType, outModreqRef);
  112. //}
  113. }
  114. method.IsVirtual = true;
  115. method.IsFinal = false;
  116. method.IsNewSlot = true;
  117. method.IsHideBySig = true;
  118. }
  119. method.IsPublic = true;
  120. method.IsPrivate = false;
  121. }
  122. }
  123. foreach (var field in type.Fields)
  124. {
  125. if (field.IsPrivate) field.IsFamily = true;
  126. }
  127. }
  128. private TypeReference AddModreqIfNotExist(TypeReference type, TypeReference mod)
  129. {
  130. var (element, opt, req) = GetDecomposedModifiers(type);
  131. if (!req.Contains(mod))
  132. {
  133. req.Add(mod);
  134. }
  135. return BuildModifiedType(element, opt, req);
  136. }
  137. private (TypeReference Element, List<TypeReference> ModOpt, List<TypeReference> ModReq) GetDecomposedModifiers(TypeReference type)
  138. {
  139. var opt = new List<TypeReference>();
  140. var req = new List<TypeReference>();
  141. while (type is IModifierType modif)
  142. {
  143. if (type.IsOptionalModifier)
  144. opt.Add(modif.ModifierType);
  145. if (type.IsRequiredModifier)
  146. req.Add(modif.ModifierType);
  147. type = modif.ElementType;
  148. }
  149. return (type, opt, req);
  150. }
  151. private TypeReference BuildModifiedType(TypeReference type, IEnumerable<TypeReference> opt, IEnumerable<TypeReference> req)
  152. {
  153. foreach (var mod in req)
  154. {
  155. type = type.MakeRequiredModifierType(mod);
  156. }
  157. foreach (var mod in opt)
  158. {
  159. type = type.MakeOptionalModifierType(mod);
  160. }
  161. return type;
  162. }
  163. #region IDisposable Support
  164. private bool disposedValue = false; // To detect redundant calls
  165. protected virtual void Dispose(bool disposing)
  166. {
  167. if (!disposedValue)
  168. {
  169. if (disposing)
  170. {
  171. module.Dispose();
  172. }
  173. disposedValue = true;
  174. }
  175. }
  176. ~VirtualizedModule()
  177. {
  178. // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  179. Dispose(false);
  180. }
  181. // This code added to correctly implement the disposable pattern.
  182. public void Dispose()
  183. {
  184. // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  185. Dispose(true);
  186. GC.SuppressFinalize(this);
  187. }
  188. #endregion
  189. }
  190. }