Merge pull request #530 from CJ-SPT/patch-cache
[Feat/QOL] AbstractPatch cache
This commit is contained in:
@@ -3,8 +3,29 @@ using HarmonyLib;
|
||||
|
||||
namespace SPTarkov.Reflection.Patching;
|
||||
|
||||
/// <summary>
|
||||
/// Harmony patch wrapper class. See mod example 6.1 for usage.
|
||||
/// </summary>
|
||||
public abstract class AbstractPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Method this patch targets
|
||||
/// </summary>
|
||||
public MethodBase? TargetMethod { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is this patch active?
|
||||
/// </summary>
|
||||
public bool IsActive { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The harmony Id assigned to this patch, usually the name of the patch class.
|
||||
/// </summary>
|
||||
public string HarmonyId
|
||||
{
|
||||
get { return _harmony.Id; }
|
||||
}
|
||||
|
||||
private readonly Harmony _harmony;
|
||||
private readonly List<HarmonyMethod> _prefixList;
|
||||
private readonly List<HarmonyMethod> _postfixList;
|
||||
@@ -69,9 +90,15 @@ public abstract class AbstractPatch
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
var target = GetTargetMethod();
|
||||
// We never want to have duplicated patches, prevent it.
|
||||
if (IsActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
TargetMethod = GetTargetMethod();
|
||||
|
||||
if (TargetMethod == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{_harmony.Id}: TargetMethod is null");
|
||||
}
|
||||
@@ -80,28 +107,31 @@ public abstract class AbstractPatch
|
||||
{
|
||||
foreach (var prefix in _prefixList)
|
||||
{
|
||||
_harmony.Patch(target, prefix: prefix);
|
||||
_harmony.Patch(TargetMethod, prefix: prefix);
|
||||
}
|
||||
|
||||
foreach (var postfix in _postfixList)
|
||||
{
|
||||
_harmony.Patch(target, postfix: postfix);
|
||||
_harmony.Patch(TargetMethod, postfix: postfix);
|
||||
}
|
||||
|
||||
foreach (var transpiler in _transpilerList)
|
||||
{
|
||||
_harmony.Patch(target, transpiler: transpiler);
|
||||
_harmony.Patch(TargetMethod, transpiler: transpiler);
|
||||
}
|
||||
|
||||
foreach (var finalizer in _finalizerList)
|
||||
{
|
||||
_harmony.Patch(target, finalizer: finalizer);
|
||||
_harmony.Patch(TargetMethod, finalizer: finalizer);
|
||||
}
|
||||
|
||||
foreach (var ilmanipulator in _ilManipulatorList)
|
||||
{
|
||||
_harmony.Patch(target, ilmanipulator: ilmanipulator);
|
||||
_harmony.Patch(TargetMethod, ilmanipulator: ilmanipulator);
|
||||
}
|
||||
|
||||
ModPatchCache.AddPatch(this);
|
||||
IsActive = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -114,6 +144,12 @@ public abstract class AbstractPatch
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
// Nothing to disable
|
||||
if (!IsActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var target = GetTargetMethod();
|
||||
|
||||
if (target == null)
|
||||
@@ -129,5 +165,12 @@ public abstract class AbstractPatch
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
namespace SPTarkov.Reflection.Patching;
|
||||
|
||||
/// <summary>
|
||||
/// Cache of active patches for mod developers to use for compatibility reasons
|
||||
/// </summary>
|
||||
public static class ModPatchCache
|
||||
{
|
||||
private static readonly List<AbstractPatch> _activePatches = [];
|
||||
|
||||
// This class contains tighter access rules than we usually would implement in the project,
|
||||
// the reason for this is so that the data is a true representation of what's happening with patches without any external interference.
|
||||
|
||||
// TODO: Mod GUID/Name associations to patches, need to think on that.
|
||||
// Required parameter on AbstractPatch ctor maybe?
|
||||
|
||||
/// <summary>
|
||||
/// Get all active patches
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// List of active patches
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This should never be called before PreSptLoad is completed, otherwise could be empty.
|
||||
/// </remarks>
|
||||
public static IReadOnlyList<AbstractPatch> GetActivePatches()
|
||||
{
|
||||
// We're not exposing _activePatches so it cant be altered outside of this class. Do NOT implement this as a property.
|
||||
// Mod developers can still enable/disable these patches at will, this is fine, just don't allow external removal from the cache.
|
||||
return _activePatches.AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all actively patched target method names
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// List of fully quantified method names; including namespace, type and method name
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This should never be called before PreSptLoad is completed, otherwise could be empty.
|
||||
/// </remarks>
|
||||
public static List<string> GetActivePatchedMethodNames()
|
||||
{
|
||||
var result = new List<string>();
|
||||
|
||||
foreach (var patch in _activePatches)
|
||||
{
|
||||
// Fullname includes namespace
|
||||
var typeName = patch.TargetMethod?.DeclaringType?.FullName;
|
||||
var methodName = patch.TargetMethod?.Name;
|
||||
|
||||
if (typeName != null && methodName != null)
|
||||
{
|
||||
result.Add($"{typeName}.{methodName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add($"{patch.HarmonyId}: Type or method is null for this patch.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a patch to the cache
|
||||
/// </summary>
|
||||
/// <param name="patch">Patch to add to cache</param>
|
||||
/// <remarks>
|
||||
/// DO NOT PATCH THIS METHOD, IT IS INTERNAL FOR A REASON. YOU ARE ONLY HARMING OTHER MOD DEVELOPERS BY DOING SO.
|
||||
/// </remarks>
|
||||
internal static void AddPatch(AbstractPatch patch)
|
||||
{
|
||||
_activePatches.Add(patch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a patch from the cache
|
||||
/// </summary>
|
||||
/// <param name="patch">Patch to remove</param>
|
||||
/// <returns>
|
||||
/// True if patch was removed
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// DO NOT PATCH THIS METHOD, IT IS INTERNAL FOR A REASON. YOU ARE ONLY HARMING OTHER MOD DEVELOPERS BY DOING SO.
|
||||
/// </remarks>
|
||||
internal static bool RemovePatch(AbstractPatch patch)
|
||||
{
|
||||
return _activePatches.Remove(patch);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user