mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-15 05:04:15 +01:00
feat: add SignatureHelper
This commit is contained in:
parent
8624c6cffa
commit
2d5a54b92c
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.Loader;
|
||||||
using Dalamud.Plugin.Internal.Types;
|
using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
using Dalamud.Utility.Signatures;
|
||||||
|
|
||||||
namespace Dalamud.Plugin.Internal
|
namespace Dalamud.Plugin.Internal
|
||||||
{
|
{
|
||||||
|
|
@ -338,6 +339,8 @@ namespace Dalamud.Plugin.Internal
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SignatureHelper.Initialise(this.instance);
|
||||||
|
|
||||||
// In-case the manifest name was a placeholder. Can occur when no manifest was included.
|
// In-case the manifest name was a placeholder. Can occur when no manifest was included.
|
||||||
if (this.instance.Name != this.Manifest.Name)
|
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