HarmonyInstance.cs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Reflection.Emit;
  8. namespace Harmony
  9. {
  10. public class Patches
  11. {
  12. public readonly ReadOnlyCollection<Patch> Prefixes;
  13. public readonly ReadOnlyCollection<Patch> Postfixes;
  14. public readonly ReadOnlyCollection<Patch> Transpilers;
  15. public ReadOnlyCollection<string> Owners
  16. {
  17. get
  18. {
  19. var result = new HashSet<string>();
  20. result.UnionWith(Prefixes.Select(p => p.owner));
  21. result.UnionWith(Postfixes.Select(p => p.owner));
  22. result.UnionWith(Transpilers.Select(p => p.owner));
  23. return result.ToList().AsReadOnly();
  24. }
  25. }
  26. public Patches(Patch[] prefixes, Patch[] postfixes, Patch[] transpilers)
  27. {
  28. if (prefixes == null) prefixes = new Patch[0];
  29. if (postfixes == null) postfixes = new Patch[0];
  30. if (transpilers == null) transpilers = new Patch[0];
  31. Prefixes = prefixes.ToList().AsReadOnly();
  32. Postfixes = postfixes.ToList().AsReadOnly();
  33. Transpilers = transpilers.ToList().AsReadOnly();
  34. }
  35. }
  36. public class HarmonyInstance
  37. {
  38. readonly string id;
  39. public string Id => id;
  40. public static bool DEBUG = false;
  41. private static bool selfPatchingDone = false;
  42. HarmonyInstance(string id)
  43. {
  44. if (DEBUG)
  45. {
  46. var assembly = typeof(HarmonyInstance).Assembly;
  47. var version = assembly.GetName().Version;
  48. var location = assembly.Location;
  49. if (location == null || location == "") location = new Uri(assembly.CodeBase).LocalPath;
  50. FileLog.Log("### Harmony id=" + id + ", version=" + version + ", location=" + location);
  51. var callingMethod = GetOutsideCaller();
  52. var callingAssembly = callingMethod.DeclaringType.Assembly;
  53. location = callingAssembly.Location;
  54. if (location == null || location == "") location = new Uri(callingAssembly.CodeBase).LocalPath;
  55. FileLog.Log("### Started from " + callingMethod.FullDescription() + ", location " + location);
  56. FileLog.Log("### At " + DateTime.Now.ToString("yyyy-MM-dd hh.mm.ss"));
  57. }
  58. this.id = id;
  59. if (!selfPatchingDone)
  60. {
  61. selfPatchingDone = true;
  62. }
  63. }
  64. public static HarmonyInstance Create(string id)
  65. {
  66. if (id == null) throw new Exception("id cannot be null");
  67. return new HarmonyInstance(id);
  68. }
  69. private MethodBase GetOutsideCaller()
  70. {
  71. var trace = new StackTrace(true);
  72. foreach (var frame in trace.GetFrames())
  73. {
  74. var method = frame.GetMethod();
  75. if (method.DeclaringType.Namespace != typeof(HarmonyInstance).Namespace)
  76. return method;
  77. }
  78. throw new Exception("Unexpected end of stack trace");
  79. }
  80. //
  81. public void PatchAll()
  82. {
  83. var method = new StackTrace().GetFrame(1).GetMethod();
  84. var assembly = method.ReflectedType.Assembly;
  85. PatchAll(assembly);
  86. }
  87. public void PatchAll(Assembly assembly)
  88. {
  89. assembly.GetTypes().Do(type =>
  90. {
  91. var parentMethodInfos = type.GetHarmonyMethods();
  92. if (parentMethodInfos != null && parentMethodInfos.Count() > 0)
  93. {
  94. var info = HarmonyMethod.Merge(parentMethodInfos);
  95. var processor = new PatchProcessor(this, type, info);
  96. processor.Patch();
  97. }
  98. });
  99. }
  100. public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null)
  101. {
  102. var processor = new PatchProcessor(this, new List<MethodBase> { original }, prefix, postfix, transpiler);
  103. return processor.Patch().FirstOrDefault();
  104. }
  105. public void UnpatchAll(string harmonyID = null)
  106. {
  107. bool IDCheck(Patch patchInfo) => harmonyID == null || patchInfo.owner == harmonyID;
  108. var originals = GetPatchedMethods().ToList();
  109. foreach (var original in originals)
  110. {
  111. var info = GetPatchInfo(original);
  112. info.Prefixes.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.patch));
  113. info.Postfixes.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.patch));
  114. info.Transpilers.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.patch));
  115. }
  116. }
  117. public void Unpatch(MethodBase original, HarmonyPatchType type, string harmonyID = null)
  118. {
  119. var processor = new PatchProcessor(this, new List<MethodBase> { original });
  120. processor.Unpatch(type, harmonyID);
  121. }
  122. public void Unpatch(MethodBase original, MethodInfo patch)
  123. {
  124. var processor = new PatchProcessor(this, new List<MethodBase> { original });
  125. processor.Unpatch(patch);
  126. }
  127. //
  128. public bool HasAnyPatches(string harmonyID)
  129. {
  130. return GetPatchedMethods()
  131. .Select(original => GetPatchInfo(original))
  132. .Any(info => info.Owners.Contains(harmonyID));
  133. }
  134. public Patches GetPatchInfo(MethodBase method)
  135. {
  136. return PatchProcessor.GetPatchInfo(method);
  137. }
  138. public IEnumerable<MethodBase> GetPatchedMethods()
  139. {
  140. return HarmonySharedState.GetPatchedMethods();
  141. }
  142. public Dictionary<string, Version> VersionInfo(out Version currentVersion)
  143. {
  144. currentVersion = typeof(HarmonyInstance).Assembly.GetName().Version;
  145. var assemblies = new Dictionary<string, Assembly>();
  146. GetPatchedMethods().Do(method =>
  147. {
  148. var info = HarmonySharedState.GetPatchInfo(method);
  149. info.prefixes.Do(fix => assemblies[fix.owner] = fix.patch.DeclaringType.Assembly);
  150. info.postfixes.Do(fix => assemblies[fix.owner] = fix.patch.DeclaringType.Assembly);
  151. info.transpilers.Do(fix => assemblies[fix.owner] = fix.patch.DeclaringType.Assembly);
  152. });
  153. var result = new Dictionary<string, Version>();
  154. assemblies.Do(info =>
  155. {
  156. var assemblyName = info.Value.GetReferencedAssemblies().FirstOrDefault(a => a.FullName.StartsWith("0Harmony, Version"));
  157. if (assemblyName != null)
  158. result[info.Key] = assemblyName.Version;
  159. });
  160. return result;
  161. }
  162. }
  163. }