mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
commit
5d935f5ae8
11 changed files with 502 additions and 0 deletions
|
|
@ -11,6 +11,7 @@ using Dalamud.Plugin.Internal.Exceptions;
|
|||
using Dalamud.Plugin.Internal.Loader;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Signatures;
|
||||
|
||||
namespace Dalamud.Plugin.Internal
|
||||
{
|
||||
|
|
@ -338,6 +339,8 @@ namespace Dalamud.Plugin.Internal
|
|||
return;
|
||||
}
|
||||
|
||||
SignatureHelper.Initialise(this.instance);
|
||||
|
||||
// In-case the manifest name was a placeholder. Can occur when no manifest was included.
|
||||
if (this.instance.Name != this.Manifest.Name)
|
||||
{
|
||||
|
|
|
|||
24
Dalamud/Utility/Signatures/Fallibility.cs
Executable file
24
Dalamud/Utility/Signatures/Fallibility.cs
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
namespace Dalamud.Utility.Signatures
|
||||
{
|
||||
/// <summary>
|
||||
/// The fallibility of a signature.
|
||||
/// </summary>
|
||||
public enum Fallibility
|
||||
{
|
||||
/// <summary>
|
||||
/// The fallibility of the signature is determined by the field/property's
|
||||
/// nullability.
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// The signature is fallible.
|
||||
/// </summary>
|
||||
Fallible,
|
||||
|
||||
/// <summary>
|
||||
/// The signature is infallible.
|
||||
/// </summary>
|
||||
Infallible,
|
||||
}
|
||||
}
|
||||
59
Dalamud/Utility/Signatures/NullabilityUtil.cs
Executable file
59
Dalamud/Utility/Signatures/NullabilityUtil.cs
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Dalamud.Utility.Signatures
|
||||
{
|
||||
internal static class NullabilityUtil
|
||||
{
|
||||
internal static bool IsNullable(PropertyInfo property) => IsNullableHelper(property.PropertyType, property.DeclaringType, property.CustomAttributes);
|
||||
|
||||
internal static bool IsNullable(FieldInfo field) => IsNullableHelper(field.FieldType, field.DeclaringType, field.CustomAttributes);
|
||||
|
||||
internal static bool IsNullable(ParameterInfo parameter) => IsNullableHelper(parameter.ParameterType, parameter.Member, parameter.CustomAttributes);
|
||||
|
||||
private static bool IsNullableHelper(Type memberType, MemberInfo? declaringType, IEnumerable<CustomAttributeData> customAttributes)
|
||||
{
|
||||
if (memberType.IsValueType)
|
||||
{
|
||||
return Nullable.GetUnderlyingType(memberType) != null;
|
||||
}
|
||||
|
||||
var nullable = customAttributes
|
||||
.FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
|
||||
if (nullable != null && nullable.ConstructorArguments.Count == 1)
|
||||
{
|
||||
var attributeArgument = nullable.ConstructorArguments[0];
|
||||
if (attributeArgument.ArgumentType == typeof(byte[]))
|
||||
{
|
||||
var args = (ReadOnlyCollection<CustomAttributeTypedArgument>)attributeArgument.Value!;
|
||||
if (args.Count > 0 && args[0].ArgumentType == typeof(byte))
|
||||
{
|
||||
return (byte)args[0].Value! == 2;
|
||||
}
|
||||
}
|
||||
else if (attributeArgument.ArgumentType == typeof(byte))
|
||||
{
|
||||
return (byte)attributeArgument.Value! == 2;
|
||||
}
|
||||
}
|
||||
|
||||
for (var type = declaringType; type != null; type = type.DeclaringType)
|
||||
{
|
||||
var context = type.CustomAttributes
|
||||
.FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
|
||||
if (context != null &&
|
||||
context.ConstructorArguments.Count == 1 &&
|
||||
context.ConstructorArguments[0].ArgumentType == typeof(byte))
|
||||
{
|
||||
return (byte)context.ConstructorArguments[0].Value! == 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't find a suitable attribute
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Dalamud/Utility/Signatures/ScanType.cs
Executable file
20
Dalamud/Utility/Signatures/ScanType.cs
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
namespace Dalamud.Utility.Signatures
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of scan to perform with a signature.
|
||||
/// </summary>
|
||||
public enum ScanType
|
||||
{
|
||||
/// <summary>
|
||||
/// Scan the text section of the executable. Uses
|
||||
/// <see cref="SigScanner.TryScanText"/>.
|
||||
/// </summary>
|
||||
Text,
|
||||
|
||||
/// <summary>
|
||||
/// Scans the text section of the executable in order to find a data section
|
||||
/// address. Uses <see cref="SigScanner.TryGetStaticAddressFromSig"/>
|
||||
/// </summary>
|
||||
StaticAddress,
|
||||
}
|
||||
}
|
||||
70
Dalamud/Utility/Signatures/SignatureAttribute.cs
Executable file
70
Dalamud/Utility/Signatures/SignatureAttribute.cs
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Dalamud.Utility.Signatures
|
||||
{
|
||||
/// <summary>
|
||||
/// The main way to use SignatureHelper. Apply this attribute to any field/property
|
||||
/// that should make use of a signature. See the field documentation for more
|
||||
/// information.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
[MeansImplicitUse(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Itself)]
|
||||
// ReSharper disable once ClassNeverInstantiated.Global
|
||||
public sealed class SignatureAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The memory signature for this field/property.
|
||||
/// </summary>
|
||||
public readonly string Signature;
|
||||
|
||||
/// <summary>
|
||||
/// The way this signature should be used. By default, this is guessed using
|
||||
/// simple heuristics, but it can be manually specified if SignatureHelper can't
|
||||
/// figure it out.
|
||||
///
|
||||
/// <seealso cref="SignatureUseFlags"/>
|
||||
/// </summary>
|
||||
public SignatureUseFlags UseFlags = SignatureUseFlags.Auto;
|
||||
|
||||
/// <summary>
|
||||
/// The type of scan to perform. By default, this scans the text section of
|
||||
/// the executable, but this should be set to StaticAddress for static
|
||||
/// addresses.
|
||||
/// </summary>
|
||||
public ScanType ScanType = ScanType.Text;
|
||||
|
||||
/// <summary>
|
||||
/// The detour name if this signature is for a hook. SignatureHelper will search
|
||||
/// the type containing this field/property for a method that matches the
|
||||
/// hook's delegate type, but if it doesn't find one or finds more than one,
|
||||
/// it will fail. You can specify the name of the method here to avoid this.
|
||||
/// </summary>
|
||||
public string? DetourName;
|
||||
|
||||
/// <summary>
|
||||
/// When <see cref="UseFlags"/> is set to Offset, this is the offset from
|
||||
/// the signature to read memory from.
|
||||
/// </summary>
|
||||
public int Offset;
|
||||
|
||||
/// <summary>
|
||||
/// When a signature is fallible, any errors while resolving it will be
|
||||
/// logged in the Dalamud log and the field/property will not have its value
|
||||
/// set. When a signature is not fallible, any errors will be thrown as
|
||||
/// exceptions instead. If fallibility is not specified, it is inferred
|
||||
/// based on if the field/property is nullable.
|
||||
/// </summary>
|
||||
public Fallibility Fallibility = Fallibility.Auto;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SignatureAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="signature">signature to scan for, see <see cref="Signature"/></param>
|
||||
public SignatureAttribute(string signature)
|
||||
{
|
||||
this.Signature = signature;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Dalamud/Utility/Signatures/SignatureException.cs
Executable file
17
Dalamud/Utility/Signatures/SignatureException.cs
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Utility.Signatures
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception for signatures.
|
||||
/// </summary>
|
||||
public class SignatureException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SignatureException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">Message.</param>
|
||||
internal SignatureException(string message)
|
||||
: base(message) { }
|
||||
}
|
||||
}
|
||||
186
Dalamud/Utility/Signatures/SignatureHelper.cs
Executable file
186
Dalamud/Utility/Signatures/SignatureHelper.cs
Executable file
|
|
@ -0,0 +1,186 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures.Wrappers;
|
||||
|
||||
namespace Dalamud.Utility.Signatures
|
||||
{
|
||||
/// <summary>
|
||||
/// A utility class to help reduce signature boilerplate code.
|
||||
/// </summary>
|
||||
public static class SignatureHelper
|
||||
{
|
||||
private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
/// <summary>
|
||||
/// Initialises an object's fields and properties that are annotated with a
|
||||
/// <see cref="SignatureAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="self">The object to initialise.</param>
|
||||
/// <param name="log">If warnings should be logged using <see cref="PluginLog"/>.</param>
|
||||
public static void Initialise(object self, bool log = true)
|
||||
{
|
||||
var scanner = Service<SigScanner>.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);
|
||||
|
||||
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)
|
||||
{
|
||||
PluginLog.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.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 ctor = actualType.GetConstructor(new[] { typeof(IntPtr), hookDelegateType });
|
||||
if (ctor == null)
|
||||
{
|
||||
PluginLog.Error("Error in SignatureHelper: could not find Hook constructor");
|
||||
continue;
|
||||
}
|
||||
|
||||
var hook = ctor.Invoke(new object?[] { ptr, detour });
|
||||
info.SetValue(self, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Dalamud/Utility/Signatures/SignatureUseFlags.cs
Executable file
40
Dalamud/Utility/Signatures/SignatureUseFlags.cs
Executable file
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
|
||||
namespace Dalamud.Utility.Signatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Use flags for a signature attribute. This tells SignatureHelper how to use the
|
||||
/// result of the signature.
|
||||
/// </summary>
|
||||
public enum SignatureUseFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// SignatureHelper will use simple heuristics to determine the best signature
|
||||
/// use for the field/property.
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// The signature should be used as a plain pointer. This is correct for
|
||||
/// static addresses, functions, or anything else that's an
|
||||
/// <see cref="IntPtr"/> at heart.
|
||||
/// </summary>
|
||||
Pointer,
|
||||
|
||||
/// <summary>
|
||||
/// The signature should be used as a hook. This is correct for
|
||||
/// <see cref="Hook{T}"/> fields/properties.
|
||||
/// </summary>
|
||||
Hook,
|
||||
|
||||
/// <summary>
|
||||
/// The signature should be used to determine an offset. This is the default
|
||||
/// for all primitive types. SignatureHelper will read from the memory at this
|
||||
/// signature and store the result in the field/property. An offset from the
|
||||
/// signature can be specified in the <see cref="SignatureAttribute"/>.
|
||||
/// </summary>
|
||||
Offset,
|
||||
}
|
||||
}
|
||||
31
Dalamud/Utility/Signatures/Wrappers/FieldInfoWrapper.cs
Executable file
31
Dalamud/Utility/Signatures/Wrappers/FieldInfoWrapper.cs
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Dalamud.Utility.Signatures.Wrappers
|
||||
{
|
||||
internal sealed class FieldInfoWrapper : IFieldOrPropertyInfo
|
||||
{
|
||||
public FieldInfoWrapper(FieldInfo info)
|
||||
{
|
||||
this.Info = info;
|
||||
}
|
||||
|
||||
public string Name => this.Info.Name;
|
||||
|
||||
public Type ActualType => this.Info.FieldType;
|
||||
|
||||
public bool IsNullable => NullabilityUtil.IsNullable(this.Info);
|
||||
|
||||
private FieldInfo Info { get; }
|
||||
|
||||
public void SetValue(object? self, object? value)
|
||||
{
|
||||
this.Info.SetValue(self, value);
|
||||
}
|
||||
|
||||
public T? GetCustomAttribute<T>() where T : Attribute
|
||||
{
|
||||
return this.Info.GetCustomAttribute<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Dalamud/Utility/Signatures/Wrappers/IFieldOrPropertyInfo.cs
Executable file
17
Dalamud/Utility/Signatures/Wrappers/IFieldOrPropertyInfo.cs
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Utility.Signatures.Wrappers
|
||||
{
|
||||
internal interface IFieldOrPropertyInfo
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
Type ActualType { get; }
|
||||
|
||||
bool IsNullable { get; }
|
||||
|
||||
void SetValue(object? self, object? value);
|
||||
|
||||
T? GetCustomAttribute<T>() where T : Attribute;
|
||||
}
|
||||
}
|
||||
35
Dalamud/Utility/Signatures/Wrappers/PropertyInfoWrapper.cs
Executable file
35
Dalamud/Utility/Signatures/Wrappers/PropertyInfoWrapper.cs
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Dalamud.Utility.Signatures.Wrappers
|
||||
{
|
||||
internal sealed class PropertyInfoWrapper : IFieldOrPropertyInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PropertyInfoWrapper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="info">PropertyInfo.</param>
|
||||
public PropertyInfoWrapper(PropertyInfo info)
|
||||
{
|
||||
this.Info = info;
|
||||
}
|
||||
|
||||
public string Name => this.Info.Name;
|
||||
|
||||
public Type ActualType => this.Info.PropertyType;
|
||||
|
||||
public bool IsNullable => NullabilityUtil.IsNullable(this.Info);
|
||||
|
||||
private PropertyInfo Info { get; }
|
||||
|
||||
public void SetValue(object? self, object? value)
|
||||
{
|
||||
this.Info.SetValue(self, value);
|
||||
}
|
||||
|
||||
public T? GetCustomAttribute<T>() where T : Attribute
|
||||
{
|
||||
return this.Info.GetCustomAttribute<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue