Dalamud/Dalamud/Hooking/Internal/Verification/HookVerifier.cs
goaaats a56d2cf40b Add verifier for hook signatures
This one is real bad, so we should make sure everyone using a canonical signature
2025-12-18 20:28:03 +01:00

107 lines
3.5 KiB
C#

using System.Linq;
using Dalamud.Game;
using Dalamud.Logging.Internal;
namespace Dalamud.Hooking.Internal.Verification;
/// <summary>
/// Global utility that can verify whether hook delegates are correctly declared.
/// Initialized out-of-band, since Hook is instantiated all over the place without a service, so this cannot be
/// a service either.
/// </summary>
internal static class HookVerifier
{
private static readonly ModuleLog Log = new("HookVerifier");
private static readonly VerificationEntry[] ToVerify =
[
new(
"ActorControlSelf",
"E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64",
typeof(ActorControlSelfDelegate),
"Signature changed in Patch 7.4") // 7.4 (new parameters)
];
private delegate void ActorControlSelfDelegate(uint category, uint eventId, uint param1, uint param2, uint param3, uint param4, uint param5, uint param6, uint param7, uint param8, ulong targetId, byte param9);
/// <summary>
/// Initializes a new instance of the <see cref="HookVerifier"/> class.
/// </summary>
/// <param name="scanner">Process to scan in.</param>
public static void Initialize(TargetSigScanner scanner)
{
foreach (var entry in ToVerify)
{
if (!scanner.TryScanText(entry.Signature, out var address))
{
Log.Error("Could not resolve signature for hook {Name} ({Sig})", entry.Name, entry.Signature);
continue;
}
entry.Address = address;
}
}
/// <summary>
/// Verify the hook with the provided address and exception.
/// </summary>
/// <param name="address">The address of the function we are hooking.</param>
/// <typeparam name="T">The delegate type passed by the creator of the hook.</typeparam>
/// <exception cref="HookVerificationException">Exception thrown when we think the hook is not correctly declared.</exception>
public static void Verify<T>(IntPtr address) where T : Delegate
{
var entry = ToVerify.FirstOrDefault(x => x.Address == address);
// Nothing to verify for this hook?
if (entry == null)
{
return;
}
var passedType = typeof(T);
// Directly compare delegates
if (passedType == entry.TargetDelegateType)
{
return;
}
var passedInvoke = passedType.GetMethod("Invoke")!;
var enforcedInvoke = entry.TargetDelegateType.GetMethod("Invoke")!;
// Compare Return Type
var mismatch = passedInvoke.ReturnType != enforcedInvoke.ReturnType;
// Compare Parameter Count
var passedParams = passedInvoke.GetParameters();
var enforcedParams = enforcedInvoke.GetParameters();
if (passedParams.Length != enforcedParams.Length)
{
mismatch = true;
}
else
{
// Compare Parameter Types
for (var i = 0; i < passedParams.Length; i++)
{
if (passedParams[i].ParameterType != enforcedParams[i].ParameterType)
{
mismatch = true;
break;
}
}
}
if (mismatch)
{
throw HookVerificationException.Create(address, passedType, entry.TargetDelegateType, entry.Message);
}
}
private record VerificationEntry(string Name, string Signature, Type TargetDelegateType, string Message)
{
public nint Address { get; set; }
}
}