From 9f0216ae355449e165925d6abc92ad554473af67 Mon Sep 17 00:00:00 2001 From: Anna Date: Wed, 12 Jan 2022 02:51:10 -0500 Subject: [PATCH] feat: add fallibility to signature attribute Also fix delegate function signatures. --- Siggingway/NullabilityUtil.cs | 45 +++++++++++++++++++++++++++ Siggingway/Siggingway.cs | 52 +++++++++++++++++++++----------- Siggingway/SignatureAttribute.cs | 9 ++++++ Siggingway/SignatureException.cs | 9 ++++++ Siggingway/Wrappers.cs | 6 ++++ 5 files changed, 103 insertions(+), 18 deletions(-) create mode 100755 Siggingway/NullabilityUtil.cs create mode 100755 Siggingway/SignatureException.cs diff --git a/Siggingway/NullabilityUtil.cs b/Siggingway/NullabilityUtil.cs new file mode 100755 index 0000000..4bc215f --- /dev/null +++ b/Siggingway/NullabilityUtil.cs @@ -0,0 +1,45 @@ +using System.Collections.ObjectModel; +using System.Reflection; + +namespace Siggingway; + +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 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) 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; + } +} diff --git a/Siggingway/Siggingway.cs b/Siggingway/Siggingway.cs index cea497e..f4099fe 100755 --- a/Siggingway/Siggingway.cs +++ b/Siggingway/Siggingway.cs @@ -27,38 +27,54 @@ public static class Siggingway { .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 fallible = sig!.Fallible ?? info.IsNullable || wasWrapped; + + 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 + var success = sig.ScanType == ScanType.Text ? scanner.TryScanText(sig.Signature, out ptr) : scanner.TryGetStaticAddressFromSig(sig.Signature, out ptr); if (!success) { if (log) { - PluginLog.Warning($"Failed to find {sig.ScanType} signature \"{info.Name}\" for {selfType.FullName} ({sig.Signature})"); + Invalid($"Failed to find {sig.ScanType} signature \"{info.Name}\" for {selfType.FullName} ({sig.Signature})", false); } continue; } - void LogInvalid(string message) { - PluginLog.Warning($"Invalid Signature attribute for {selfType.FullName}.{info.Name}: {message}"); - } - - var actualType = info.ActualType; - if (actualType.IsGenericType && actualType.GetGenericTypeDefinition() == typeof(Nullable<>)) { - // unwrap the nullable - actualType = actualType.GetGenericArguments()[0]; - } - switch (sig.UseFlags) { case SignatureUseFlags.Auto when actualType == typeof(IntPtr) || actualType.IsPointer || actualType.IsAssignableTo(typeof(Delegate)): case SignatureUseFlags.Pointer: { - info.SetValue(self, ptr); + 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<>)) { - LogInvalid($"{actualType.Name} is not a Hook"); + Invalid($"{actualType.Name} is not a Hook"); continue; } @@ -73,7 +89,7 @@ public static class Siggingway { .Where(del => del != null) .ToArray(); if (matches.Length != 1) { - LogInvalid("Either found no matching detours or found more than one: specify a detour name"); + Invalid("Either found no matching detours or found more than one: specify a detour name"); continue; } @@ -81,7 +97,7 @@ public static class Siggingway { } else { var method = selfType.GetMethod(sig.DetourName, Flags); if (method == null) { - LogInvalid($"Could not find detour \"{sig.DetourName}\""); + Invalid($"Could not find detour \"{sig.DetourName}\""); continue; } @@ -89,7 +105,7 @@ public static class Siggingway { ? Delegate.CreateDelegate(hookDelegateType, method, false) : Delegate.CreateDelegate(hookDelegateType, self, method, false); if (del == null) { - LogInvalid($"Method {sig.DetourName} was not compatible with delegate {hookDelegateType.Name}"); + Invalid($"Method {sig.DetourName} was not compatible with delegate {hookDelegateType.Name}"); continue; } @@ -116,7 +132,7 @@ public static class Siggingway { } default: { if (log) { - LogInvalid("could not detect desired signature use, set SignatureUseFlags manually"); + Invalid("could not detect desired signature use, set SignatureUseFlags manually"); } break; diff --git a/Siggingway/SignatureAttribute.cs b/Siggingway/SignatureAttribute.cs index 79bcb3c..0e764ed 100755 --- a/Siggingway/SignatureAttribute.cs +++ b/Siggingway/SignatureAttribute.cs @@ -46,6 +46,15 @@ public sealed class SignatureAttribute : Attribute { /// public int Offset; + /// + /// 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. + /// + public bool? Fallible; + /// /// Create a with the given signature. /// diff --git a/Siggingway/SignatureException.cs b/Siggingway/SignatureException.cs new file mode 100755 index 0000000..373cabf --- /dev/null +++ b/Siggingway/SignatureException.cs @@ -0,0 +1,9 @@ +namespace Siggingway; + +/// +/// An exception for signatures. +/// +public class SignatureException : Exception { + internal SignatureException(string message) : base(message) { + } +} diff --git a/Siggingway/Wrappers.cs b/Siggingway/Wrappers.cs index 983a1e4..0ee4b6a 100755 --- a/Siggingway/Wrappers.cs +++ b/Siggingway/Wrappers.cs @@ -7,6 +7,8 @@ internal interface IFieldOrPropertyInfo { Type ActualType { get; } + bool IsNullable { get; } + void SetValue(object? self, object? value); T? GetCustomAttribute() where T : Attribute; @@ -23,6 +25,8 @@ internal sealed class FieldInfoWrapper : IFieldOrPropertyInfo { public Type ActualType => this.Info.FieldType; + public bool IsNullable => NullabilityUtil.IsNullable(this.Info); + public void SetValue(object? self, object? value) { this.Info.SetValue(self, value); } @@ -43,6 +47,8 @@ internal sealed class PropertyInfoWrapper : IFieldOrPropertyInfo { public Type ActualType => this.Info.PropertyType; + public bool IsNullable => NullabilityUtil.IsNullable(this.Info); + public void SetValue(object? self, object? value) { this.Info.SetValue(self, value); }