PatchProcessor.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using System.Reflection.Emit;
  6. namespace Harmony
  7. {
  8. public class PatchProcessor
  9. {
  10. static object locker = new object();
  11. readonly HarmonyInstance instance;
  12. readonly Type container;
  13. readonly HarmonyMethod containerAttributes;
  14. List<MethodBase> originals = new List<MethodBase>();
  15. HarmonyMethod prefix;
  16. HarmonyMethod postfix;
  17. HarmonyMethod transpiler;
  18. public PatchProcessor(HarmonyInstance instance, Type type, HarmonyMethod attributes)
  19. {
  20. this.instance = instance;
  21. container = type;
  22. containerAttributes = attributes ?? new HarmonyMethod(null);
  23. prefix = containerAttributes.Clone();
  24. postfix = containerAttributes.Clone();
  25. transpiler = containerAttributes.Clone();
  26. PrepareType();
  27. }
  28. public PatchProcessor(HarmonyInstance instance, List<MethodBase> originals, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null)
  29. {
  30. this.instance = instance;
  31. this.originals = originals;
  32. this.prefix = prefix ?? new HarmonyMethod(null);
  33. this.postfix = postfix ?? new HarmonyMethod(null);
  34. this.transpiler = transpiler ?? new HarmonyMethod(null);
  35. }
  36. public static Patches GetPatchInfo(MethodBase method)
  37. {
  38. lock (locker)
  39. {
  40. var patchInfo = HarmonySharedState.GetPatchInfo(method);
  41. if (patchInfo == null) return null;
  42. return new Patches(patchInfo.prefixes, patchInfo.postfixes, patchInfo.transpilers);
  43. }
  44. }
  45. public static IEnumerable<MethodBase> AllPatchedMethods()
  46. {
  47. lock (locker)
  48. {
  49. return HarmonySharedState.GetPatchedMethods();
  50. }
  51. }
  52. public List<DynamicMethod> Patch()
  53. {
  54. lock (locker)
  55. {
  56. var dynamicMethods = new List<DynamicMethod>();
  57. foreach (var original in originals)
  58. {
  59. if (original == null)
  60. throw new NullReferenceException("original");
  61. var individualPrepareResult = RunMethod<HarmonyPrepare, bool>(true, original);
  62. if (individualPrepareResult)
  63. {
  64. var patchInfo = HarmonySharedState.GetPatchInfo(original);
  65. if (patchInfo == null) patchInfo = new PatchInfo();
  66. PatchFunctions.AddPrefix(patchInfo, instance.Id, prefix);
  67. PatchFunctions.AddPostfix(patchInfo, instance.Id, postfix);
  68. PatchFunctions.AddTranspiler(patchInfo, instance.Id, transpiler);
  69. PatchHandler.Get(original).Apply();
  70. RunMethod<HarmonyCleanup>(original);
  71. }
  72. }
  73. return dynamicMethods;
  74. }
  75. }
  76. public void Unpatch(HarmonyPatchType type, string harmonyID)
  77. {
  78. lock (locker)
  79. {
  80. foreach (var original in originals)
  81. {
  82. var patchInfo = HarmonySharedState.GetPatchInfo(original);
  83. if (patchInfo == null) patchInfo = new PatchInfo();
  84. if (type == HarmonyPatchType.All || type == HarmonyPatchType.Prefix)
  85. PatchFunctions.RemovePrefix(patchInfo, harmonyID);
  86. if (type == HarmonyPatchType.All || type == HarmonyPatchType.Postfix)
  87. PatchFunctions.RemovePostfix(patchInfo, harmonyID);
  88. if (type == HarmonyPatchType.All || type == HarmonyPatchType.Transpiler)
  89. PatchFunctions.RemoveTranspiler(patchInfo, harmonyID);
  90. PatchHandler.Get(original).Apply();
  91. }
  92. }
  93. }
  94. public void Unpatch(MethodInfo patch)
  95. {
  96. lock (locker)
  97. {
  98. foreach (var original in originals)
  99. {
  100. var patchInfo = HarmonySharedState.GetPatchInfo(original);
  101. if (patchInfo == null) patchInfo = new PatchInfo();
  102. PatchFunctions.RemovePatch(patchInfo, patch);
  103. PatchHandler.Get(original).Apply();
  104. }
  105. }
  106. }
  107. void PrepareType()
  108. {
  109. var mainPrepareResult = RunMethod<HarmonyPrepare, bool>(true);
  110. if (mainPrepareResult == false)
  111. return;
  112. var customOriginals = RunMethod<HarmonyTargetMethods, IEnumerable<MethodBase>>(null);
  113. if (customOriginals != null)
  114. {
  115. originals = customOriginals.ToList();
  116. }
  117. else
  118. {
  119. var originalMethodType = containerAttributes.methodType;
  120. // MethodType default is Normal
  121. if (containerAttributes.methodType == null)
  122. containerAttributes.methodType = MethodType.Normal;
  123. var isPatchAll = Attribute.GetCustomAttribute(container, typeof(HarmonyPatchAll)) != null;
  124. if (isPatchAll)
  125. {
  126. var type = containerAttributes.declaringType;
  127. originals.AddRange(AccessTools.GetDeclaredConstructors(type).Cast<MethodBase>());
  128. originals.AddRange(AccessTools.GetDeclaredMethods(type).Cast<MethodBase>());
  129. }
  130. else
  131. {
  132. var original = RunMethod<HarmonyTargetMethod, MethodBase>(null);
  133. if (original == null)
  134. original = GetOriginalMethod();
  135. if (original == null)
  136. {
  137. var info = "(";
  138. info += "declaringType=" + containerAttributes.declaringType + ", ";
  139. info += "methodName =" + containerAttributes.methodName + ", ";
  140. info += "methodType=" + originalMethodType + ", ";
  141. info += "argumentTypes=" + containerAttributes.argumentTypes.Description();
  142. info += ")";
  143. throw new ArgumentException("No target method specified for class " + container.FullName + " " + info);
  144. }
  145. originals.Add(original);
  146. }
  147. }
  148. PatchTools.GetPatches(container, out prefix.method, out postfix.method, out transpiler.method);
  149. if (prefix.method != null)
  150. {
  151. if (prefix.method.IsStatic == false)
  152. throw new ArgumentException("Patch method " + prefix.method.FullDescription() + " must be static");
  153. var prefixAttributes = prefix.method.GetHarmonyMethods();
  154. containerAttributes.Merge(HarmonyMethod.Merge(prefixAttributes)).CopyTo(prefix);
  155. }
  156. if (postfix.method != null)
  157. {
  158. if (postfix.method.IsStatic == false)
  159. throw new ArgumentException("Patch method " + postfix.method.FullDescription() + " must be static");
  160. var postfixAttributes = postfix.method.GetHarmonyMethods();
  161. containerAttributes.Merge(HarmonyMethod.Merge(postfixAttributes)).CopyTo(postfix);
  162. }
  163. if (transpiler.method != null)
  164. {
  165. if (transpiler.method.IsStatic == false)
  166. throw new ArgumentException("Patch method " + transpiler.method.FullDescription() + " must be static");
  167. var infixAttributes = transpiler.method.GetHarmonyMethods();
  168. containerAttributes.Merge(HarmonyMethod.Merge(infixAttributes)).CopyTo(transpiler);
  169. }
  170. }
  171. MethodBase GetOriginalMethod()
  172. {
  173. var attr = containerAttributes;
  174. if (attr.declaringType == null) return null;
  175. switch (attr.methodType)
  176. {
  177. case MethodType.Normal:
  178. if (attr.methodName == null)
  179. return null;
  180. return AccessTools.DeclaredMethod(attr.declaringType, attr.methodName, attr.argumentTypes);
  181. case MethodType.Getter:
  182. if (attr.methodName == null)
  183. return null;
  184. return AccessTools.DeclaredProperty(attr.declaringType, attr.methodName).GetGetMethod(true);
  185. case MethodType.Setter:
  186. if (attr.methodName == null)
  187. return null;
  188. return AccessTools.DeclaredProperty(attr.declaringType, attr.methodName).GetSetMethod(true);
  189. case MethodType.Constructor:
  190. return AccessTools.DeclaredConstructor(attr.declaringType, attr.argumentTypes);
  191. case MethodType.StaticConstructor:
  192. return AccessTools.GetDeclaredConstructors(attr.declaringType)
  193. .Where(c => c.IsStatic)
  194. .FirstOrDefault();
  195. }
  196. return null;
  197. }
  198. T RunMethod<S, T>(T defaultIfNotExisting, params object[] parameters)
  199. {
  200. if (container == null)
  201. return defaultIfNotExisting;
  202. var methodName = typeof(S).Name.Replace("Harmony", "");
  203. var paramList = new List<object> { instance };
  204. paramList.AddRange(parameters);
  205. var paramTypes = AccessTools.GetTypes(paramList.ToArray());
  206. var method = PatchTools.GetPatchMethod<S>(container, methodName, paramTypes);
  207. if (method != null && typeof(T).IsAssignableFrom(method.ReturnType))
  208. return (T)method.Invoke(null, paramList.ToArray());
  209. method = PatchTools.GetPatchMethod<S>(container, methodName, new Type[] { typeof(HarmonyInstance) });
  210. if (method != null && typeof(T).IsAssignableFrom(method.ReturnType))
  211. return (T)method.Invoke(null, new object[] { instance });
  212. method = PatchTools.GetPatchMethod<S>(container, methodName, Type.EmptyTypes);
  213. if (method != null)
  214. {
  215. if (typeof(T).IsAssignableFrom(method.ReturnType))
  216. return (T)method.Invoke(null, Type.EmptyTypes);
  217. method.Invoke(null, Type.EmptyTypes);
  218. return defaultIfNotExisting;
  219. }
  220. return defaultIfNotExisting;
  221. }
  222. void RunMethod<S>(params object[] parameters)
  223. {
  224. if (container == null)
  225. return;
  226. var methodName = typeof(S).Name.Replace("Harmony", "");
  227. var paramList = new List<object> { instance };
  228. paramList.AddRange(parameters);
  229. var paramTypes = AccessTools.GetTypes(paramList.ToArray());
  230. var method = PatchTools.GetPatchMethod<S>(container, methodName, paramTypes);
  231. if (method != null)
  232. {
  233. method.Invoke(null, paramList.ToArray());
  234. return;
  235. }
  236. method = PatchTools.GetPatchMethod<S>(container, methodName, new Type[] { typeof(HarmonyInstance) });
  237. if (method != null)
  238. {
  239. method.Invoke(null, new object[] { instance });
  240. return;
  241. }
  242. method = PatchTools.GetPatchMethod<S>(container, methodName, Type.EmptyTypes);
  243. if (method != null)
  244. {
  245. method.Invoke(null, Type.EmptyTypes);
  246. return;
  247. }
  248. }
  249. }
  250. }