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() { Service.PluginLog.Information("Refreshing PatMeProxyApi"); if (Service.GetPluginInstance?.Invoke("PatMe") is not { } realPatMe) { Service.PluginLog.Information("PatMe not found"); Unset(); return false; } _realPatMe = realPatMe; _realPatMeType = realPatMe.GetType(); _realPatMeServiceType = _realPatMeType.Assembly.GetType("PatMe.Service"); if (_realPatMeServiceType is null) { Service.PluginLog.Information("PatMe.Service not found"); Unset(); return false; } Service.PluginLog.Information($"PatMe found: {_realPatMeType.Assembly.FullName}"); if (_lastPatMe?.Target != realPatMe) { Service.PluginLog.Information($"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? 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) { 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 (_realPatMeType?.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 (_realPatMeType?.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() { _realPatMe = default; _realPatMeType = default; _realPatMeServiceType = default; EmoteCounters = default; ClearCache(); } private void ClearCache() { _lastPatMe = null; _methodCache.Clear(); _memberCache.Clear(); } } }