DP-PatMe2Mqtt/PatMe2Mqtt/PatMeProxyApi/PatMe.cs
2023-05-26 15:55:33 +02:00

265 lines
8.1 KiB
C#

using Dalamud.Logging;
using MonoMod.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
namespace PatMe2Mqtt.PatMeProxyApi
{
public class PatMe
{
internal const BindingFlags _BindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
private object? _realPatMe;
private Type? _realPatMeType;
private Type? _realPatMeServiceType;
private WeakReference? _lastPatMe;
private readonly Dictionary<(Type, string), FastReflectionDelegate> _methodCache = new();
private readonly Dictionary<(Type, string), MemberInfo> _memberCache = new();
public PatMe()
{
// Refresh();
}
public bool IsAvailable => _realPatMe is not null;
public EmoteCountersList EmoteCounters { get; private set; }
public bool Refresh()
{
PluginLog.LogInformation("Refreshing PatMeProxyApi");
if (Service.GetPluginInstance?.Invoke("PatMe") is not { } realPatMe)
{
PluginLog.LogInformation("PatMe not found");
Unset();
return false;
}
_realPatMe = realPatMe;
_realPatMeType = realPatMe.GetType();
_realPatMeServiceType = _realPatMeType.Assembly.GetType("PatMe.Service");
if (_realPatMeServiceType is null)
{
PluginLog.LogInformation("PatMe.Service not found");
Unset();
return false;
}
PluginLog.LogInformation($"PatMe found: {_realPatMeType.Assembly.FullName}");
if (_lastPatMe?.Target != realPatMe)
{
PluginLog.LogInformation($"Different PatMe, clearing cache");
ClearCache();
_lastPatMe = new WeakReference(realPatMe);
}
EmoteCounters = new(this, _realPatMeServiceType.GetField("emoteCounters")?.GetValue(null)!);
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 (_realPatMeType?.Assembly.GetType(typeName) is not { } type)
{
PluginLog.LogInformation($"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 (_realPatMeType?.Assembly.GetType(typeName) is not { } type)
{
PluginLog.LogInformation($"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 (_realPatMeType?.Assembly.GetType(typeName) is not { } type)
{
PluginLog.LogInformation($"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()
{
_realPatMe = default;
_realPatMeType = default;
_realPatMeServiceType = default;
EmoteCounters = default;
ClearCache();
}
private void ClearCache()
{
_lastPatMe = null;
_methodCache.Clear();
_memberCache.Clear();
}
}
}