feat: add fallibility to signature attribute

Also fix delegate function signatures.
This commit is contained in:
Anna 2022-01-12 02:51:10 -05:00
parent ba3d7a7bc1
commit ab7f032239
Signed by: anna
GPG Key ID: 0B391D8F06FCD9E0
5 changed files with 103 additions and 18 deletions

45
Siggingway/NullabilityUtil.cs Executable file
View File

@ -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<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;
}
}

View File

@ -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<T>");
Invalid($"{actualType.Name} is not a Hook<T>");
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;

View File

@ -46,6 +46,15 @@ public sealed class SignatureAttribute : Attribute {
/// </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 bool? Fallible;
/// <summary>
/// Create a <see cref="SignatureAttribute"/> with the given signature.
/// </summary>

View File

@ -0,0 +1,9 @@
namespace Siggingway;
/// <summary>
/// An exception for signatures.
/// </summary>
public class SignatureException : Exception {
internal SignatureException(string message) : base(message) {
}
}

View File

@ -7,6 +7,8 @@ internal interface IFieldOrPropertyInfo {
Type ActualType { get; }
bool IsNullable { get; }
void SetValue(object? self, object? value);
T? GetCustomAttribute<T>() 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);
}