2023-05-26 15:26:16 +02:00
|
|
|
|
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()
|
|
|
|
|
{
|
2023-11-14 00:07:11 +01:00
|
|
|
|
Service.PluginLog.Information("Refreshing PatMeProxyApi");
|
2023-05-26 15:26:16 +02:00
|
|
|
|
|
|
|
|
|
if (Service.GetPluginInstance?.Invoke("PatMe") is not { } realPatMe)
|
|
|
|
|
{
|
2023-11-14 00:07:11 +01:00
|
|
|
|
Service.PluginLog.Information("PatMe not found");
|
2023-05-26 15:26:16 +02:00
|
|
|
|
|
|
|
|
|
Unset();
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_realPatMe = realPatMe;
|
|
|
|
|
_realPatMeType = realPatMe.GetType();
|
|
|
|
|
_realPatMeServiceType = _realPatMeType.Assembly.GetType("PatMe.Service");
|
|
|
|
|
|
|
|
|
|
if (_realPatMeServiceType is null)
|
|
|
|
|
{
|
2023-11-14 00:07:11 +01:00
|
|
|
|
Service.PluginLog.Information("PatMe.Service not found");
|
2023-05-26 15:26:16 +02:00
|
|
|
|
|
|
|
|
|
Unset();
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-14 00:07:11 +01:00
|
|
|
|
Service.PluginLog.Information($"PatMe found: {_realPatMeType.Assembly.FullName}");
|
2023-05-26 15:26:16 +02:00
|
|
|
|
|
|
|
|
|
if (_lastPatMe?.Target != realPatMe)
|
|
|
|
|
{
|
2023-11-14 00:07:11 +01:00
|
|
|
|
Service.PluginLog.Information($"Different PatMe, clearing cache");
|
2023-05-26 15:26:16 +02:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2023-11-14 00:07:11 +01:00
|
|
|
|
Service.PluginLog.Information($"GetStaticValue failed, type \"{typeName}\" not found");
|
2023-05-26 15:26:16 +02:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2023-11-14 00:07:11 +01:00
|
|
|
|
Service.PluginLog.Information($"GetStaticValue failed, type \"{typeName}\" not found");
|
2023-05-26 15:26:16 +02:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2023-11-14 00:07:11 +01:00
|
|
|
|
Service.PluginLog.Information($"GetStaticValue failed, type \"{typeName}\" not found");
|
2023-05-26 15:26:16 +02:00
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|