290 lines
8.3 KiB
C#
290 lines
8.3 KiB
C#
|
using Dalamud.IoC;
|
|||
|
using MonoMod.Utils;
|
|||
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Reflection;
|
|||
|
using System.Runtime.CompilerServices;
|
|||
|
|
|||
|
namespace CustomResolution.WndProcHookManagerProxyApi;
|
|||
|
|
|||
|
public class WndProcHookManager
|
|||
|
{
|
|||
|
internal const BindingFlags _BindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
|
|||
|
|
|||
|
private object? _realManager;
|
|||
|
private Type? _realManagerType;
|
|||
|
|
|||
|
private WeakReference? _lastManager;
|
|||
|
private readonly Dictionary<(Type, string), FastReflectionDelegate> _methodCache = new();
|
|||
|
private readonly Dictionary<(Type, string), MemberInfo> _memberCache = new();
|
|||
|
|
|||
|
public WndProcHookManager()
|
|||
|
{
|
|||
|
Refresh();
|
|||
|
}
|
|||
|
|
|||
|
public bool IsAvailable => _realManager is not null;
|
|||
|
public object? ProxiedValue => _realManager;
|
|||
|
|
|||
|
public EventInfo? PreWndProc { get; private set; }
|
|||
|
|
|||
|
public bool Refresh()
|
|||
|
{
|
|||
|
if (IsAvailable)
|
|||
|
{
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
Service.PluginLog.Information("Refreshing WndProcHookManagerProxyApi");
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
if (typeof(PluginServiceAttribute).Assembly.GetType("Dalamud.Service`1") is not { } serviceContainerContainer)
|
|||
|
{
|
|||
|
Service.PluginLog.Warning("CustomResolution couldn't find the service container types.");
|
|||
|
|
|||
|
Unset();
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
if (typeof(PluginServiceAttribute).Assembly.GetType("Dalamud.Hooking.WndProcHook.WndProcHookManager") is not { } wndProcHookManagerType)
|
|||
|
{
|
|||
|
Service.PluginLog.Warning("CustomResolution couldn't find the WndProcHookManager type.");
|
|||
|
|
|||
|
Unset();
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
_realManagerType = wndProcHookManagerType;
|
|||
|
|
|||
|
serviceContainerContainer = serviceContainerContainer.MakeGenericType(wndProcHookManagerType);
|
|||
|
|
|||
|
if (serviceContainerContainer.GetMethod("Get")?.Invoke(null, Array.Empty<object>()) is not object manager)
|
|||
|
{
|
|||
|
Service.PluginLog.Warning("CustomResolution couldn't obtain the WndProcHookManager instance.");
|
|||
|
|
|||
|
Unset();
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
_realManager = manager;
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Service.PluginLog.Warning($"CustomResolution couldn't obtain the WndProcHookManager service: {e}");
|
|||
|
|
|||
|
Unset();
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
Service.PluginLog.Information($"WndProcHookManager found: {_realManagerType.Assembly.FullName}");
|
|||
|
|
|||
|
if (_lastManager?.Target != _realManager)
|
|||
|
{
|
|||
|
Service.PluginLog.Information($"Different WndProcHookManager, clearing cache");
|
|||
|
|
|||
|
ClearCache();
|
|||
|
|
|||
|
_lastManager = new WeakReference(_realManager);
|
|||
|
}
|
|||
|
|
|||
|
PreWndProc = _realManagerType.GetEvent("PreWndProc");
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
internal FastReflectionDelegate GetMethod(Type target, string name, Func<Type, MethodBase>? getMethod = null)
|
|||
|
{
|
|||
|
if (_methodCache.TryGetValue((target, name), out var fun))
|
|||
|
{
|
|||
|
return fun;
|
|||
|
}
|
|||
|
|
|||
|
return _methodCache[(target, name)] = FastReflectionHelper.CreateFastDelegate(getMethod?.Invoke(target) ?? target.GetMethods().FirstOrDefault(m => m.Name == name) ?? target.GetMethod(name, _BindingFlags)!);
|
|||
|
}
|
|||
|
|
|||
|
internal object? InvokeStatic(string typeName, string name, params object?[] args)
|
|||
|
{
|
|||
|
if (_realManagerType?.Assembly.GetType(typeName) is not { } type)
|
|||
|
{
|
|||
|
Service.PluginLog.Information($"GetStaticValue failed, type \"{typeName}\" not found");
|
|||
|
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
return GetMethod(type, name).Invoke(null, args);
|
|||
|
}
|
|||
|
|
|||
|
internal object? Invoke(object proxiedValue, string name, params object?[] args)
|
|||
|
{
|
|||
|
return GetMethod(proxiedValue.GetType(), name).Invoke(proxiedValue, args);
|
|||
|
}
|
|||
|
|
|||
|
internal object? GetStaticValue(string typeName, string name, params object?[] args)
|
|||
|
{
|
|||
|
if (_realManagerType?.Assembly.GetType(typeName) is not { } type)
|
|||
|
{
|
|||
|
Service.PluginLog.Information($"GetStaticValue failed, type \"{typeName}\" not found");
|
|||
|
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
if (!_memberCache.TryGetValue((type, name), out var member))
|
|||
|
{
|
|||
|
member = _memberCache[(type, name)] = type.GetMember(name, _BindingFlags).First();
|
|||
|
}
|
|||
|
|
|||
|
if (member is FieldInfo field)
|
|||
|
{
|
|||
|
return field.GetValue(null);
|
|||
|
}
|
|||
|
|
|||
|
if (member is PropertyInfo property)
|
|||
|
{
|
|||
|
return property.GetValue(null, args);
|
|||
|
}
|
|||
|
|
|||
|
return InvokeStatic(typeName, name, args);
|
|||
|
}
|
|||
|
|
|||
|
internal object? GetValue(object proxiedValue, string name, params object?[] args)
|
|||
|
{
|
|||
|
if (!_memberCache.TryGetValue((proxiedValue.GetType(), name), out var member))
|
|||
|
{
|
|||
|
member = _memberCache[(proxiedValue.GetType(), name)] = proxiedValue.GetType().GetMember(name, _BindingFlags).First();
|
|||
|
}
|
|||
|
|
|||
|
if (member is FieldInfo field)
|
|||
|
{
|
|||
|
return field.GetValue(proxiedValue);
|
|||
|
}
|
|||
|
|
|||
|
if (member is PropertyInfo property)
|
|||
|
{
|
|||
|
return property.GetValue(proxiedValue, args);
|
|||
|
}
|
|||
|
|
|||
|
return Invoke(proxiedValue, name, args);
|
|||
|
}
|
|||
|
|
|||
|
internal void SetStaticValue(string typeName, string name, params object?[] args)
|
|||
|
{
|
|||
|
if (_realManagerType?.Assembly.GetType(typeName) is not { } type)
|
|||
|
{
|
|||
|
Service.PluginLog.Information($"GetStaticValue failed, type \"{typeName}\" not found");
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (!_memberCache.TryGetValue((type, name), out var member))
|
|||
|
{
|
|||
|
member = _memberCache[(type, name)] = type.GetMember(name, _BindingFlags).First();
|
|||
|
}
|
|||
|
|
|||
|
if (member is FieldInfo field)
|
|||
|
{
|
|||
|
field.SetValue(null, args[0]);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (member is PropertyInfo property)
|
|||
|
{
|
|||
|
property.SetValue(null, args.First(), args.Length > 1 ? args.Skip(1).ToArray() : null);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
InvokeStatic(typeName, name, args);
|
|||
|
}
|
|||
|
|
|||
|
internal void SetValue(object proxiedValue, string name, params object?[] args)
|
|||
|
{
|
|||
|
if (!_memberCache.TryGetValue((proxiedValue.GetType(), name), out var member))
|
|||
|
{
|
|||
|
member = _memberCache[(proxiedValue.GetType(), name)] = proxiedValue.GetType().GetMember(name, _BindingFlags).First();
|
|||
|
}
|
|||
|
|
|||
|
if (member is FieldInfo field)
|
|||
|
{
|
|||
|
field.SetValue(proxiedValue, args[0]);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (member is PropertyInfo property)
|
|||
|
{
|
|||
|
property.SetValue(proxiedValue, args.First(), args.Length > 1 ? args.Skip(1).ToArray() : null);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
Invoke(proxiedValue, name, args);
|
|||
|
}
|
|||
|
|
|||
|
internal ITuple ConvertValueTuple(Type targetType, object proxiedValue)
|
|||
|
{
|
|||
|
var rawTuple = (ITuple) proxiedValue;
|
|||
|
var dstItems = new object?[rawTuple.Length];
|
|||
|
var rawTypes = proxiedValue.GetType().GenericTypeArguments;
|
|||
|
var dstTypes = targetType.GenericTypeArguments;
|
|||
|
|
|||
|
for (var i = 0; i < rawTuple.Length; i++)
|
|||
|
{
|
|||
|
var raw = rawTuple[i];
|
|||
|
|
|||
|
if (dstTypes[i].IsAssignableFrom(rawTypes[i]))
|
|||
|
{
|
|||
|
dstItems[i] = raw;
|
|||
|
}
|
|||
|
else if (raw is not null)
|
|||
|
{
|
|||
|
var dstType = dstTypes[i];
|
|||
|
|
|||
|
var isNullable = dstType.FullName?.StartsWith("System.Nullable`1[[") ?? false;
|
|||
|
|
|||
|
if (isNullable)
|
|||
|
{
|
|||
|
dstType = dstType.GenericTypeArguments[0];
|
|||
|
}
|
|||
|
|
|||
|
dstItems[i] = Activator.CreateInstance(dstType, new object[] { this, raw });
|
|||
|
|
|||
|
if (isNullable)
|
|||
|
{
|
|||
|
dstItems[i] = Activator.CreateInstance(dstTypes[i], new object[] { dstItems[i]! });
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
dstItems[i] = null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return (ITuple) Activator.CreateInstance(targetType, dstItems)!;
|
|||
|
}
|
|||
|
|
|||
|
private void Unset()
|
|||
|
{
|
|||
|
_realManager = default;
|
|||
|
_realManagerType = default;
|
|||
|
|
|||
|
PreWndProc = default;
|
|||
|
|
|||
|
ClearCache();
|
|||
|
}
|
|||
|
|
|||
|
private void ClearCache()
|
|||
|
{
|
|||
|
_lastManager = null;
|
|||
|
_methodCache.Clear();
|
|||
|
_memberCache.Clear();
|
|||
|
}
|
|||
|
}
|