using System.Reflection; using HarmonyLib; namespace SPTarkov.Reflection.Patching; /// /// Harmony patch wrapper class. See mod example 6.1 for usage. /// public abstract class AbstractPatch { /// /// Method this patch targets /// public MethodBase? TargetMethod { get; private set; } /// /// Is this patch active? /// public bool IsActive { get; private set; } /// /// The harmony Id assigned to this patch, usually the name of the patch class. /// public string HarmonyId { get { return _harmony.Id; } } private readonly Harmony _harmony; private readonly List _prefixList; private readonly List _postfixList; private readonly List _transpilerList; private readonly List _finalizerList; private readonly List _ilManipulatorList; /// /// Constructor /// /// Name protected AbstractPatch(string? name = null) { _harmony = new Harmony(name ?? GetType().Name); _prefixList = GetPatchMethods(typeof(PatchPrefixAttribute)); _postfixList = GetPatchMethods(typeof(PatchPostfixAttribute)); _transpilerList = GetPatchMethods(typeof(PatchTranspilerAttribute)); _finalizerList = GetPatchMethods(typeof(PatchFinalizerAttribute)); _ilManipulatorList = GetPatchMethods(typeof(PatchIlManipulatorAttribute)); if ( _prefixList.Count == 0 && _postfixList.Count == 0 && _transpilerList.Count == 0 && _finalizerList.Count == 0 && _ilManipulatorList.Count == 0 ) { throw new Exception($"{_harmony.Id}: At least one of the patch methods must be specified"); } } /// /// Get original method /// /// Method protected abstract MethodBase GetTargetMethod(); /// /// Get HarmonyMethod from string /// /// Attribute type /// Method private List GetPatchMethods(Type attributeType) { var T = GetType(); var methods = new List(); foreach (var method in T.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly)) { if (method.GetCustomAttribute(attributeType) != null) { methods.Add(new HarmonyMethod(method)); } } return methods; } /// /// Apply patch to target /// public void Enable() { // We never want to have duplicated patches, prevent it. if (IsActive) { return; } TargetMethod = GetTargetMethod(); if (TargetMethod == null) { throw new InvalidOperationException($"{_harmony.Id}: TargetMethod is null"); } try { foreach (var prefix in _prefixList) { _harmony.Patch(TargetMethod, prefix: prefix); } foreach (var postfix in _postfixList) { _harmony.Patch(TargetMethod, postfix: postfix); } foreach (var transpiler in _transpilerList) { _harmony.Patch(TargetMethod, transpiler: transpiler); } foreach (var finalizer in _finalizerList) { _harmony.Patch(TargetMethod, finalizer: finalizer); } foreach (var ilmanipulator in _ilManipulatorList) { _harmony.Patch(TargetMethod, ilmanipulator: ilmanipulator); } ModPatchCache.AddPatch(this); IsActive = true; } catch (Exception ex) { throw new Exception($"{_harmony.Id}:", ex); } } /// /// Remove applied patch from target /// public void Disable() { // Nothing to disable if (!IsActive) { return; } var target = GetTargetMethod(); if (target == null) { throw new InvalidOperationException($"{_harmony.Id}: TargetMethod is null"); } try { _harmony.Unpatch(target, HarmonyPatchType.All, _harmony.Id); } catch (Exception ex) { throw new Exception($"{_harmony.Id}:", ex); } if (!ModPatchCache.RemovePatch(this)) { throw new Exception($"{_harmony.Id}: Target patch not present in cache, a mod is likely externally altering it."); } IsActive = false; } }