using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; namespace Harmony { public class Patches { public readonly ReadOnlyCollection Prefixes; public readonly ReadOnlyCollection Postfixes; public readonly ReadOnlyCollection Transpilers; public ReadOnlyCollection Owners { get { var result = new HashSet(); result.UnionWith(Prefixes.Select(p => p.owner)); result.UnionWith(Postfixes.Select(p => p.owner)); result.UnionWith(Transpilers.Select(p => p.owner)); return result.ToList().AsReadOnly(); } } public Patches(Patch[] prefixes, Patch[] postfixes, Patch[] transpilers) { if (prefixes == null) prefixes = new Patch[0]; if (postfixes == null) postfixes = new Patch[0]; if (transpilers == null) transpilers = new Patch[0]; Prefixes = prefixes.ToList().AsReadOnly(); Postfixes = postfixes.ToList().AsReadOnly(); Transpilers = transpilers.ToList().AsReadOnly(); } } public class HarmonyInstance { readonly string id; public string Id => id; public static bool DEBUG = false; private static bool selfPatchingDone = false; HarmonyInstance(string id) { if (DEBUG) { var assembly = typeof(HarmonyInstance).Assembly; var version = assembly.GetName().Version; var location = assembly.Location; if (location == null || location == "") location = new Uri(assembly.CodeBase).LocalPath; FileLog.Log("### Harmony id=" + id + ", version=" + version + ", location=" + location); var callingMethod = GetOutsideCaller(); var callingAssembly = callingMethod.DeclaringType.Assembly; location = callingAssembly.Location; if (location == null || location == "") location = new Uri(callingAssembly.CodeBase).LocalPath; FileLog.Log("### Started from " + callingMethod.FullDescription() + ", location " + location); FileLog.Log("### At " + DateTime.Now.ToString("yyyy-MM-dd hh.mm.ss")); } this.id = id; if (!selfPatchingDone) { selfPatchingDone = true; } } public static HarmonyInstance Create(string id) { if (id == null) throw new Exception("id cannot be null"); return new HarmonyInstance(id); } private MethodBase GetOutsideCaller() { var trace = new StackTrace(true); foreach (var frame in trace.GetFrames()) { var method = frame.GetMethod(); if (method.DeclaringType.Namespace != typeof(HarmonyInstance).Namespace) return method; } throw new Exception("Unexpected end of stack trace"); } // public void PatchAll() { var method = new StackTrace().GetFrame(1).GetMethod(); var assembly = method.ReflectedType.Assembly; PatchAll(assembly); } public void PatchAll(Assembly assembly) { assembly.GetTypes().Do(type => { var parentMethodInfos = type.GetHarmonyMethods(); if (parentMethodInfos != null && parentMethodInfos.Count() > 0) { var info = HarmonyMethod.Merge(parentMethodInfos); var processor = new PatchProcessor(this, type, info); processor.Patch(); } }); } public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) { var processor = new PatchProcessor(this, new List { original }, prefix, postfix, transpiler); return processor.Patch().FirstOrDefault(); } public void UnpatchAll(string harmonyID = null) { bool IDCheck(Patch patchInfo) => harmonyID == null || patchInfo.owner == harmonyID; var originals = GetPatchedMethods().ToList(); foreach (var original in originals) { var info = GetPatchInfo(original); info.Prefixes.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.patch)); info.Postfixes.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.patch)); info.Transpilers.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.patch)); } } public void Unpatch(MethodBase original, HarmonyPatchType type, string harmonyID = null) { var processor = new PatchProcessor(this, new List { original }); processor.Unpatch(type, harmonyID); } public void Unpatch(MethodBase original, MethodInfo patch) { var processor = new PatchProcessor(this, new List { original }); processor.Unpatch(patch); } // public bool HasAnyPatches(string harmonyID) { return GetPatchedMethods() .Select(original => GetPatchInfo(original)) .Any(info => info.Owners.Contains(harmonyID)); } public Patches GetPatchInfo(MethodBase method) { return PatchProcessor.GetPatchInfo(method); } public IEnumerable GetPatchedMethods() { return HarmonySharedState.GetPatchedMethods(); } public Dictionary VersionInfo(out Version currentVersion) { currentVersion = typeof(HarmonyInstance).Assembly.GetName().Version; var assemblies = new Dictionary(); GetPatchedMethods().Do(method => { var info = HarmonySharedState.GetPatchInfo(method); info.prefixes.Do(fix => assemblies[fix.owner] = fix.patch.DeclaringType.Assembly); info.postfixes.Do(fix => assemblies[fix.owner] = fix.patch.DeclaringType.Assembly); info.transpilers.Do(fix => assemblies[fix.owner] = fix.patch.DeclaringType.Assembly); }); var result = new Dictionary(); assemblies.Do(info => { var assemblyName = info.Value.GetReferencedAssemblies().FirstOrDefault(a => a.FullName.StartsWith("0Harmony, Version")); if (assemblyName != null) result[info.Key] = assemblyName.Version; }); return result; } } }