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.

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