mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
194 lines
7.6 KiB
C#
Executable file
194 lines
7.6 KiB
C#
Executable file
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
|
|
using Dalamud.Game;
|
|
using Dalamud.Hooking;
|
|
using Dalamud.Logging;
|
|
using Dalamud.Plugin.Services;
|
|
using Dalamud.Utility.Signatures.Wrappers;
|
|
using Serilog;
|
|
|
|
namespace Dalamud.Utility.Signatures;
|
|
|
|
/// <summary>
|
|
/// A utility class to help reduce signature boilerplate code.
|
|
/// </summary>
|
|
internal static class SignatureHelper
|
|
{
|
|
private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
|
|
|
|
/// <summary>
|
|
/// Initializes an object's fields and properties that are annotated with a
|
|
/// <see cref="SignatureAttribute"/>.
|
|
/// </summary>
|
|
/// <param name="self">The object to initialize.</param>
|
|
/// <param name="log">If warnings should be logged using <see cref="PluginLog"/>.</param>
|
|
/// <returns>Collection of created IDalamudHooks.</returns>
|
|
internal static IEnumerable<IDalamudHook> Initialize(object self, bool log = true)
|
|
{
|
|
var scanner = Service<TargetSigScanner>.Get();
|
|
var selfType = self.GetType();
|
|
var fields = selfType.GetFields(Flags).Select(field => (IFieldOrPropertyInfo)new FieldInfoWrapper(field))
|
|
.Concat(selfType.GetProperties(Flags).Select(prop => new PropertyInfoWrapper(prop)))
|
|
.Select(field => (field, field.GetCustomAttribute<SignatureAttribute>()))
|
|
.Where(field => field.Item2 != null);
|
|
|
|
var createdHooks = new List<IDalamudHook>();
|
|
|
|
foreach (var (info, sig) in fields)
|
|
{
|
|
var wasWrapped = false;
|
|
var actualType = info.ActualType;
|
|
if (actualType.IsGenericType && actualType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
|
{
|
|
// unwrap the nullable
|
|
actualType = actualType.GetGenericArguments()[0];
|
|
wasWrapped = true;
|
|
}
|
|
|
|
var fallibility = sig!.Fallibility;
|
|
if (fallibility == Fallibility.Auto)
|
|
{
|
|
fallibility = info.IsNullable || wasWrapped
|
|
? Fallibility.Fallible
|
|
: Fallibility.Infallible;
|
|
}
|
|
|
|
var fallible = fallibility == Fallibility.Fallible;
|
|
|
|
void Invalid(string message, bool prepend = true)
|
|
{
|
|
var errorMsg = prepend
|
|
? $"Invalid Signature attribute for {selfType.FullName}.{info.Name}: {message}"
|
|
: message;
|
|
if (fallible)
|
|
{
|
|
Log.Warning(errorMsg);
|
|
}
|
|
else
|
|
{
|
|
throw new SignatureException(errorMsg);
|
|
}
|
|
}
|
|
|
|
IntPtr ptr;
|
|
var success = sig.ScanType == ScanType.Text
|
|
? scanner.TryScanText(sig.Signature, out ptr)
|
|
: scanner.TryGetStaticAddressFromSig(sig.Signature, out ptr);
|
|
if (!success)
|
|
{
|
|
if (log)
|
|
{
|
|
Invalid($"Failed to find {sig.ScanType} signature \"{info.Name}\" for {selfType.FullName} ({sig.Signature})", false);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
switch (sig.UseFlags)
|
|
{
|
|
case SignatureUseFlags.Auto when actualType == typeof(IntPtr) || actualType.IsFunctionPointer || actualType.IsUnmanagedFunctionPointer || actualType.IsPointer || actualType.IsAssignableTo(typeof(Delegate)):
|
|
case SignatureUseFlags.Pointer:
|
|
{
|
|
if (actualType.IsAssignableTo(typeof(Delegate)))
|
|
{
|
|
info.SetValue(self, Marshal.GetDelegateForFunctionPointer(ptr, actualType));
|
|
}
|
|
else
|
|
{
|
|
info.SetValue(self, ptr);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SignatureUseFlags.Auto when actualType.IsGenericType && actualType.GetGenericTypeDefinition() == typeof(Hook<>):
|
|
case SignatureUseFlags.Hook:
|
|
{
|
|
if (!actualType.IsGenericType || actualType.GetGenericTypeDefinition() != typeof(Hook<>))
|
|
{
|
|
Invalid($"{actualType.Name} is not a Hook<T>");
|
|
continue;
|
|
}
|
|
|
|
var hookDelegateType = actualType.GenericTypeArguments[0];
|
|
|
|
Delegate? detour;
|
|
if (sig.DetourName == null)
|
|
{
|
|
var matches = selfType.GetMethods(Flags)
|
|
.Select(method => method.IsStatic
|
|
? Delegate.CreateDelegate(hookDelegateType, method, false)
|
|
: Delegate.CreateDelegate(hookDelegateType, self, method, false))
|
|
.Where(del => del != null)
|
|
.ToArray();
|
|
if (matches.Length != 1)
|
|
{
|
|
Invalid("Either found no matching detours or found more than one: specify a detour name");
|
|
continue;
|
|
}
|
|
|
|
detour = matches[0]!;
|
|
}
|
|
else
|
|
{
|
|
var method = selfType.GetMethod(sig.DetourName, Flags);
|
|
if (method == null)
|
|
{
|
|
Invalid($"Could not find detour \"{sig.DetourName}\"");
|
|
continue;
|
|
}
|
|
|
|
var del = method.IsStatic
|
|
? Delegate.CreateDelegate(hookDelegateType, method, false)
|
|
: Delegate.CreateDelegate(hookDelegateType, self, method, false);
|
|
if (del == null)
|
|
{
|
|
Invalid($"Method {sig.DetourName} was not compatible with delegate {hookDelegateType.Name}");
|
|
continue;
|
|
}
|
|
|
|
detour = del;
|
|
}
|
|
|
|
var creator = actualType.GetMethod("FromAddress", BindingFlags.Static | BindingFlags.NonPublic);
|
|
if (creator == null)
|
|
{
|
|
Log.Error("Error in SignatureHelper: could not find Hook creator");
|
|
continue;
|
|
}
|
|
|
|
var hook = creator.Invoke(null, new object?[] { ptr, detour, false }) as IDalamudHook;
|
|
info.SetValue(self, hook);
|
|
createdHooks.Add(hook);
|
|
|
|
break;
|
|
}
|
|
|
|
case SignatureUseFlags.Auto when actualType.IsPrimitive:
|
|
case SignatureUseFlags.Offset:
|
|
{
|
|
var offset = Marshal.PtrToStructure(ptr + sig.Offset, actualType);
|
|
info.SetValue(self, offset);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
if (log)
|
|
{
|
|
Invalid("could not detect desired signature use, set SignatureUseFlags manually");
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return createdHooks;
|
|
}
|
|
}
|