Siggingway/Siggingway/Siggingway.cs

131 lines
5.8 KiB
C#
Executable File

using System.Reflection;
using System.Runtime.InteropServices;
using Dalamud.Game;
using Dalamud.Hooking;
using Dalamud.Logging;
namespace Siggingway;
/// <summary>
/// The main entry point for Siggingway.
/// </summary>
public static class Siggingway {
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="scanner">the SigScanner to use for scanning for signatures</param>
/// <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(SigScanner scanner, object self, bool log = true) {
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) {
IntPtr ptr;
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})");
}
continue;
}
void LogInvalid(string message) {
PluginLog.Warning($"Invalid Signature attribute for {selfType.FullName}.{info.Name}: {message}");
}
var actualType = info.ActualType;
PluginLog.Information($"actualType: {actualType}");
if (actualType.IsGenericType && actualType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
// unwrap the nullable
actualType = actualType.GetGenericArguments()[0];
PluginLog.Information($"unwrapped actualType: {actualType}");
}
switch (sig.UseFlags) {
case SignatureUseFlags.Auto when actualType == typeof(IntPtr) || actualType.IsPointer || actualType.IsAssignableTo(typeof(Delegate)):
case SignatureUseFlags.Pointer: {
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>");
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) {
LogInvalid("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) {
LogInvalid($"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) {
LogInvalid($"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 Siggingway: could not find Hook constructor");
continue;
}
var hook = ctor.Invoke(new object?[] { ptr, detour });
info.SetValue(self, hook);
PluginLog.Information($"Hook {selfType.FullName}.{info.Name} set up");
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) {
LogInvalid("could not detect desired signature use, set SignatureUseFlags manually");
}
break;
}
}
}
}
}