Decompiled source of WhySoLaggy v1.0.5

WhySoLaggy.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
using UnityEngine.Profiling;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("WhySoLaggy")]
[assembly: AssemblyDescription("BepInEx performance monitor: FPS tracking, plugin Update profiling, Harmony patch profiling.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("WhySoLaggy")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: InternalsVisibleTo("WhySoLaggy.Tests")]
[assembly: ComVisible(false)]
[assembly: Guid("7a0d965a-ed09-40ae-9680-c8917710a150")]
[assembly: AssemblyFileVersion("1.0.5.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.5.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace WhySoLaggy
{
	internal static class AbuseLogger
	{
		private static StreamWriter _writer;

		private static readonly object _lock = new object();

		public static void Initialize(string bepInExDir)
		{
			try
			{
				string text = Path.Combine(bepInExDir, "WhySoLaggy_Abuse.log");
				_writer = new StreamWriter(text, append: false, Encoding.UTF8)
				{
					AutoFlush = true
				};
				_writer.WriteLine("[" + Timestamp() + "] WhySoLaggy Abuse Detection Monitor started");
				_writer.WriteLine("[" + Timestamp() + "] Log file: " + text);
				_writer.WriteLine(new string('=', 60));
			}
			catch (Exception ex)
			{
				Logger.CreateLogSource("WhySoLaggy").LogError((object)("Failed to create abuse log file: " + ex.Message));
			}
		}

		public static void Info(string message)
		{
			if (_writer == null)
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					_writer.WriteLine("[" + Timestamp() + "] " + message);
				}
				catch
				{
				}
			}
		}

		public static void Write(string message)
		{
			if (_writer == null || !LogFilter.AllowAbuse())
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					_writer.WriteLine("[" + Timestamp() + "] " + message);
				}
				catch
				{
				}
			}
		}

		public static void Alert(string message)
		{
			string text = "⚠ ABUSE ALERT: " + message;
			if (_writer != null)
			{
				lock (_lock)
				{
					try
					{
						_writer.WriteLine("[" + Timestamp() + "] " + text);
					}
					catch
					{
					}
				}
			}
			ManualLogSource log = WhySoLaggyPlugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("[WHY_LAG] " + text));
			}
			try
			{
				PerformanceDashboard.ReportAlert(message);
			}
			catch
			{
			}
		}

		public static void AlertDetail(string message)
		{
			if (_writer == null)
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					_writer.WriteLine("[" + Timestamp() + "] " + message);
				}
				catch
				{
				}
			}
		}

		public static void AlertDetailRaw(string line)
		{
			if (_writer == null)
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					_writer.WriteLine(line);
				}
				catch
				{
				}
			}
		}

		public static void WriteRaw(string line)
		{
			if (_writer == null || !LogFilter.AllowAbuse())
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					_writer.WriteLine(line);
				}
				catch
				{
				}
			}
		}

		public static void Shutdown()
		{
			if (_writer == null)
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					_writer.WriteLine("[" + Timestamp() + "] WhySoLaggy Abuse Detection shutting down");
					_writer.Flush();
					_writer.Close();
					_writer = null;
				}
				catch
				{
				}
			}
		}

		private static string Timestamp()
		{
			return DateTime.Now.ToString("HH:mm:ss.fff");
		}
	}
	internal static class AbuseNotificationUI
	{
		private struct Notification
		{
			public string Message;

			public float ExpireTime;
		}

		private const float DisplayDuration = 12f;

		private const int MaxVisibleMessages = 8;

		private const float BoxWidth = 520f;

		private const float LineHeight = 22f;

		private const float Padding = 8f;

		private const float TopMargin = 10f;

		private const float LeftMargin = 10f;

		private static readonly List<Notification> _notifications = new List<Notification>();

		private static GUIStyle _boxStyle;

		private static GUIStyle _textStyle;

		public static void Show(string message)
		{
			_notifications.Add(new Notification
			{
				Message = message,
				ExpireTime = Time.unscaledTime + 12f
			});
			while (_notifications.Count > 8)
			{
				_notifications.RemoveAt(0);
			}
		}

		public static void DrawGUI()
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			float now = Time.unscaledTime;
			_notifications.RemoveAll((Notification n) => now >= n.ExpireTime);
			if (_notifications.Count == 0)
			{
				return;
			}
			EnsureStyles();
			float num = (float)_notifications.Count * 22f + 16f;
			GUI.Box(new Rect(10f, 10f, 520f, num), GUIContent.none, _boxStyle);
			float num2 = 18f;
			foreach (Notification notification in _notifications)
			{
				float num3 = notification.ExpireTime - now;
				float a = ((num3 < 3f) ? (num3 / 3f) : 1f);
				Color textColor = _textStyle.normal.textColor;
				textColor.a = a;
				_textStyle.normal.textColor = textColor;
				GUI.Label(new Rect(18f, num2, 504f, 22f), notification.Message, _textStyle);
				num2 += 22f;
			}
		}

		private static void EnsureStyles()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Expected O, but got Unknown
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			if (_boxStyle == null)
			{
				Texture2D val = new Texture2D(1, 1);
				val.SetPixel(0, 0, new Color(0f, 0f, 0f, 0.75f));
				val.Apply();
				_boxStyle = new GUIStyle(GUI.skin.box);
				_boxStyle.normal.background = val;
				_boxStyle.border = new RectOffset(0, 0, 0, 0);
				_textStyle = new GUIStyle(GUI.skin.label);
				_textStyle.fontSize = 14;
				_textStyle.normal.textColor = new Color(1f, 0.35f, 0.3f, 1f);
				_textStyle.fontStyle = (FontStyle)1;
				_textStyle.wordWrap = true;
			}
		}

		public static void Reset()
		{
			_notifications.Clear();
			try
			{
				GUIStyle boxStyle = _boxStyle;
				object obj;
				if (boxStyle == null)
				{
					obj = null;
				}
				else
				{
					GUIStyleState normal = boxStyle.normal;
					obj = ((normal != null) ? normal.background : null);
				}
				if ((Object)obj != (Object)null)
				{
					Object.Destroy((Object)(object)_boxStyle.normal.background);
				}
			}
			catch
			{
			}
			_boxStyle = null;
			_textStyle = null;
		}
	}
	internal struct AlertCooldownDecision
	{
		public bool Emit;

		public int SuppressedCount;

		public float PeakValue;
	}
	internal sealed class AlertCooldownTracker
	{
		private struct State
		{
			public float LastEmission;

			public int SuppressedCount;

			public float PeakValue;

			public bool HasEmission;
		}

		private readonly Dictionary<string, State> _states = new Dictionary<string, State>(StringComparer.Ordinal);

		public AlertCooldownDecision Observe(string key, float now, float value, float cooldownSeconds)
		{
			if (string.IsNullOrEmpty(key))
			{
				throw new ArgumentException("Alert key is required.", "key");
			}
			if (cooldownSeconds < 0f)
			{
				cooldownSeconds = 0f;
			}
			if (!_states.TryGetValue(key, out var value2))
			{
				value2 = new State
				{
					PeakValue = value
				};
			}
			if (value2.HasEmission && now - value2.LastEmission < cooldownSeconds)
			{
				value2.SuppressedCount++;
				if (value > value2.PeakValue)
				{
					value2.PeakValue = value;
				}
				_states[key] = value2;
				return new AlertCooldownDecision
				{
					Emit = false,
					SuppressedCount = value2.SuppressedCount,
					PeakValue = value2.PeakValue
				};
			}
			float peakValue = (value2.HasEmission ? Math.Max(value2.PeakValue, value) : value);
			int suppressedCount = (value2.HasEmission ? value2.SuppressedCount : 0);
			_states[key] = new State
			{
				LastEmission = now,
				SuppressedCount = 0,
				PeakValue = 0f,
				HasEmission = true
			};
			return new AlertCooldownDecision
			{
				Emit = true,
				SuppressedCount = suppressedCount,
				PeakValue = peakValue
			};
		}

		public void Clear()
		{
			_states.Clear();
		}
	}
	internal sealed class BoundedConcurrentQueue<T>
	{
		private readonly Queue<T> _items = new Queue<T>();

		private readonly object _lock = new object();

		private int _capacity;

		private int _peakCount;

		private long _droppedInWindow;

		public int Capacity
		{
			get
			{
				lock (_lock)
				{
					return _capacity;
				}
			}
			set
			{
				if (value < 1)
				{
					throw new ArgumentOutOfRangeException("value");
				}
				lock (_lock)
				{
					_capacity = value;
					while (_items.Count > _capacity)
					{
						_items.Dequeue();
						_droppedInWindow++;
					}
					if (_items.Count > _peakCount)
					{
						_peakCount = _items.Count;
					}
				}
			}
		}

		public int Count
		{
			get
			{
				lock (_lock)
				{
					return _items.Count;
				}
			}
		}

		public BoundedConcurrentQueue(int capacity)
		{
			Capacity = capacity;
		}

		public QueueEnqueueResult Enqueue(T item)
		{
			lock (_lock)
			{
				_items.Enqueue(item);
				bool dropped = false;
				if (_items.Count > _capacity)
				{
					_items.Dequeue();
					_droppedInWindow++;
					dropped = true;
				}
				if (_items.Count > _peakCount)
				{
					_peakCount = _items.Count;
				}
				return new QueueEnqueueResult(_items.Count, dropped);
			}
		}

		public bool TryDequeue(out T item)
		{
			lock (_lock)
			{
				if (_items.Count == 0)
				{
					item = default(T);
					return false;
				}
				item = _items.Dequeue();
				return true;
			}
		}

		public QueueWindowStats TakeWindowStats()
		{
			lock (_lock)
			{
				QueueWindowStats result = new QueueWindowStats(_capacity, _items.Count, _peakCount, _droppedInWindow);
				_peakCount = _items.Count;
				_droppedInWindow = 0L;
				return result;
			}
		}

		public void Clear()
		{
			lock (_lock)
			{
				_items.Clear();
				_peakCount = 0;
				_droppedInWindow = 0L;
			}
		}
	}
	internal struct QueueEnqueueResult
	{
		public int Depth { get; }

		public bool Dropped { get; }

		public QueueEnqueueResult(int depth, bool dropped)
		{
			Depth = depth;
			Dropped = dropped;
		}
	}
	internal struct QueueWindowStats
	{
		public int Capacity { get; }

		public int Depth { get; }

		public int Peak { get; }

		public long Dropped { get; }

		public QueueWindowStats(int capacity, int depth, int peak, long dropped)
		{
			Capacity = capacity;
			Depth = depth;
			Peak = peak;
			Dropped = dropped;
		}
	}
	internal static class ExpressionEvaluator
	{
		public enum RootKind
		{
			Instance,
			Arg,
			Args,
			Result,
			Exception,
			StaticType
		}

		public struct Step
		{
			public string Name;

			public bool NullSafe;

			public bool IsIndex;

			public int Index;
		}

		public sealed class CompiledExpr
		{
			public string Source;

			public RootKind Root;

			public int ArgIndex;

			public Type StaticRootType;

			public List<Step> Steps;

			public string CompileError;
		}

		private sealed class MemberReader
		{
			public FieldInfo Field;

			public PropertyInfo Property;

			public bool IsNone;

			public object Read(object instance)
			{
				if (Field != null)
				{
					return Field.GetValue(instance);
				}
				if (Property != null)
				{
					return Property.GetValue(instance, null);
				}
				return null;
			}
		}

		private static readonly Dictionary<string, Type> _typeCache = new Dictionary<string, Type>(StringComparer.Ordinal);

		private static readonly Dictionary<string, MemberReader> _memberCache = new Dictionary<string, MemberReader>(StringComparer.Ordinal);

		private static readonly object _cacheLock = new object();

		public static CompiledExpr Compile(string expr)
		{
			CompiledExpr compiledExpr = new CompiledExpr
			{
				Source = expr,
				Steps = new List<Step>()
			};
			if (string.IsNullOrEmpty(expr))
			{
				compiledExpr.CompileError = "empty";
				return compiledExpr;
			}
			int pos = 0;
			string text = ReadIdent(expr, ref pos);
			if (string.IsNullOrEmpty(text))
			{
				compiledExpr.CompileError = "no root token";
				return compiledExpr;
			}
			switch (text)
			{
			case "__instance":
				compiledExpr.Root = RootKind.Instance;
				break;
			case "__args":
				compiledExpr.Root = RootKind.Args;
				break;
			case "__result":
				compiledExpr.Root = RootKind.Result;
				break;
			case "__exception":
				compiledExpr.Root = RootKind.Exception;
				break;
			default:
			{
				if (text.StartsWith("__arg", StringComparison.Ordinal) && int.TryParse(text.Substring(5), out var result) && result >= 0)
				{
					compiledExpr.Root = RootKind.Arg;
					compiledExpr.ArgIndex = result;
					break;
				}
				Type type = ResolveType(text);
				if (type == null)
				{
					compiledExpr.CompileError = "unknown root/type: " + text;
					return compiledExpr;
				}
				compiledExpr.Root = RootKind.StaticType;
				compiledExpr.StaticRootType = type;
				break;
			}
			}
			while (pos < expr.Length)
			{
				char c = expr[pos];
				switch (c)
				{
				case '.':
				{
					pos++;
					string text4 = ReadIdent(expr, ref pos);
					if (string.IsNullOrEmpty(text4))
					{
						compiledExpr.CompileError = "expected member after '.'";
						return compiledExpr;
					}
					compiledExpr.Steps.Add(new Step
					{
						Name = text4,
						NullSafe = false
					});
					break;
				}
				case '?':
				{
					if (pos + 1 >= expr.Length || expr[pos + 1] != '.')
					{
						compiledExpr.CompileError = "expected '?.' at " + pos;
						return compiledExpr;
					}
					pos += 2;
					string text3 = ReadIdent(expr, ref pos);
					if (string.IsNullOrEmpty(text3))
					{
						compiledExpr.CompileError = "expected member after '?.'";
						return compiledExpr;
					}
					compiledExpr.Steps.Add(new Step
					{
						Name = text3,
						NullSafe = true
					});
					break;
				}
				case '[':
				{
					pos++;
					int num = pos;
					for (; pos < expr.Length && expr[pos] != ']'; pos++)
					{
					}
					if (pos >= expr.Length)
					{
						compiledExpr.CompileError = "unterminated [index]";
						return compiledExpr;
					}
					string text2 = expr.Substring(num, pos - num).Trim();
					pos++;
					if (!int.TryParse(text2, out var result2))
					{
						compiledExpr.CompileError = "bad index '" + text2 + "'";
						return compiledExpr;
					}
					compiledExpr.Steps.Add(new Step
					{
						IsIndex = true,
						Index = result2
					});
					break;
				}
				default:
					compiledExpr.CompileError = "unexpected '" + c + "' at " + pos;
					return compiledExpr;
				}
			}
			return compiledExpr;
		}

		public static string Evaluate(CompiledExpr ce, object instance, object[] args, object result, Exception ex, int maxLen)
		{
			if (ce == null)
			{
				return "err:null_expr";
			}
			if (!string.IsNullOrEmpty(ce.CompileError))
			{
				return "err:compile_" + ce.CompileError;
			}
			object obj;
			try
			{
				obj = GetRoot(ce, instance, args, result, ex);
			}
			catch (Exception ex2)
			{
				return "err:root_" + ex2.GetType().Name;
			}
			int num = ce.Steps?.Count ?? 0;
			for (int i = 0; i < num; i++)
			{
				Step step = ce.Steps[i];
				bool flag = i == 0 && ce.Root == RootKind.StaticType;
				if (!flag && (obj == null || IsUnityNull(obj)))
				{
					if (!step.NullSafe)
					{
						return "null-deref:" + (step.IsIndex ? ("[" + step.Index + "]") : step.Name);
					}
					return "null";
				}
				if (step.IsIndex)
				{
					if (!(obj is IList list))
					{
						return "err:not_ilist";
					}
					if (step.Index < 0 || step.Index >= list.Count)
					{
						return "err:idx_oor";
					}
					try
					{
						obj = list[step.Index];
					}
					catch (Exception ex3)
					{
						return "err:idx_" + ex3.GetType().Name;
					}
					continue;
				}
				if ((step.Name == "Count" || step.Name == "Length") && TryGetCollectionSize(obj, out var size))
				{
					obj = size;
					continue;
				}
				Type type;
				object instance2;
				if (flag)
				{
					type = ce.StaticRootType;
					instance2 = null;
				}
				else
				{
					type = obj.GetType();
					instance2 = obj;
				}
				MemberReader member = GetMember(type, step.Name, flag);
				if (member.IsNone)
				{
					return "err:no_member_" + step.Name;
				}
				try
				{
					obj = member.Read(instance2);
				}
				catch (Exception ex4)
				{
					return "err:read_" + ex4.GetType().Name;
				}
			}
			if (num == 0 && ce.Root == RootKind.Args)
			{
				return FormatArgs(args, maxLen);
			}
			return ValueFormatter.Format(obj, maxLen);
		}

		private static string FormatArgs(object[] args, int maxLen)
		{
			if (args == null)
			{
				return "null";
			}
			if (args.Length == 0)
			{
				return "[]";
			}
			StringBuilder stringBuilder = new StringBuilder(64);
			stringBuilder.Append('[');
			for (int i = 0; i < args.Length; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(ValueFormatter.Format(args[i], 32));
			}
			stringBuilder.Append(']');
			string text = stringBuilder.ToString();
			if (text.Length <= maxLen)
			{
				return text;
			}
			return text.Substring(0, maxLen) + "...";
		}

		private static object GetRoot(CompiledExpr ce, object instance, object[] args, object result, Exception ex)
		{
			switch (ce.Root)
			{
			case RootKind.Instance:
				return instance;
			case RootKind.Args:
				return args;
			case RootKind.Result:
				return result;
			case RootKind.Exception:
				return ex;
			case RootKind.Arg:
				if (args == null || ce.ArgIndex >= args.Length)
				{
					return null;
				}
				return args[ce.ArgIndex];
			case RootKind.StaticType:
				return null;
			default:
				return null;
			}
		}

		private static Type ResolveType(string name)
		{
			lock (_cacheLock)
			{
				if (_typeCache.TryGetValue(name, out var value))
				{
					return value;
				}
				Type type = null;
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					Type[] types;
					try
					{
						types = assembly.GetTypes();
					}
					catch
					{
						continue;
					}
					Type[] array = types;
					foreach (Type type2 in array)
					{
						if (type2.Name == name)
						{
							type = type2;
							break;
						}
					}
					if (type != null)
					{
						break;
					}
				}
				_typeCache[name] = type;
				return type;
			}
		}

		private static MemberReader GetMember(Type type, string name, bool isStatic)
		{
			string key = (isStatic ? "S|" : "I|") + type.FullName + "|" + name;
			lock (_cacheLock)
			{
				if (_memberCache.TryGetValue(key, out var value))
				{
					return value;
				}
				BindingFlags bindingFlags = (BindingFlags)(0x30 | (isStatic ? 8 : 4));
				MemberReader memberReader = new MemberReader();
				FieldInfo fieldInfo = null;
				PropertyInfo propertyInfo = null;
				Type type2 = type;
				while (type2 != null && fieldInfo == null && propertyInfo == null)
				{
					try
					{
						fieldInfo = type2.GetField(name, bindingFlags | BindingFlags.DeclaredOnly);
					}
					catch
					{
					}
					if (fieldInfo == null)
					{
						try
						{
							propertyInfo = type2.GetProperty(name, bindingFlags | BindingFlags.DeclaredOnly);
						}
						catch
						{
						}
					}
					type2 = type2.BaseType;
				}
				memberReader.Field = fieldInfo;
				memberReader.Property = propertyInfo;
				memberReader.IsNone = fieldInfo == null && propertyInfo == null;
				_memberCache[key] = memberReader;
				return memberReader;
			}
		}

		private static bool TryGetCollectionSize(object v, out int size)
		{
			size = 0;
			if (v is Array array)
			{
				size = array.Length;
				return true;
			}
			if (v is ICollection collection)
			{
				size = collection.Count;
				return true;
			}
			PropertyInfo property = v.GetType().GetProperty("Count");
			if (property != null && property.PropertyType == typeof(int))
			{
				try
				{
					size = (int)property.GetValue(v, null);
					return true;
				}
				catch
				{
				}
			}
			return false;
		}

		private static bool IsUnityNull(object v)
		{
			if (v == null)
			{
				return true;
			}
			Object val = (Object)((v is Object) ? v : null);
			if (val == null)
			{
				return false;
			}
			return val == (Object)null;
		}

		private static string ReadIdent(string s, ref int pos)
		{
			int num = pos;
			for (; pos < s.Length; pos++)
			{
				char c = s[pos];
				switch (c)
				{
				default:
					if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
					{
						continue;
					}
					break;
				case '_':
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
				case 'g':
				case 'h':
				case 'i':
				case 'j':
				case 'k':
				case 'l':
				case 'm':
				case 'n':
				case 'o':
				case 'p':
				case 'q':
				case 'r':
				case 's':
				case 't':
				case 'u':
				case 'v':
				case 'w':
				case 'x':
				case 'y':
				case 'z':
					continue;
				}
				break;
			}
			if (pos <= num)
			{
				return "";
			}
			return s.Substring(num, pos - num);
		}

		public static void ClearCaches()
		{
			lock (_cacheLock)
			{
				_typeCache.Clear();
				_memberCache.Clear();
			}
		}
	}
	internal static class FieldProbe
	{
		private sealed class Rule
		{
			public int Id;

			public string TargetTypeName;

			public string TargetMethodName;

			public string TargetKey;

			public string[] RawFields;

			public ExpressionEvaluator.CompiledExpr[] Compiled;

			public int RateLimit;

			public int MaxValueLen;

			public bool IncludeStack;

			public int StackMaxDepth;

			public string Note;

			public bool Enabled;

			public bool NeedsPostfix;

			public bool NeedsFinalizer;

			public bool NeedsPrefix;
		}

		public static bool Enabled = false;

		public static string RulesFilePath = "";

		public static int DefaultRateLimit = 60;

		public static int DefaultMaxValueLen = 128;

		public static bool DefaultIncludeStack = false;

		public static int DefaultStackMaxDepth = 5;

		private static readonly Dictionary<MethodBase, List<Rule>> _methodToRules = new Dictionary<MethodBase, List<Rule>>();

		private static readonly object _rateLock = new object();

		private static readonly Dictionary<int, int> _counter = new Dictionary<int, int>();

		private static readonly Dictionary<int, long> _windowStart = new Dictionary<int, long>();

		private static readonly HashSet<int> _overflowWarned = new HashSet<int>();

		private static readonly long _ticksPerSec = 10000000L;

		private static int _hookedRules = 0;

		private static int _hookedMethods = 0;

		private static bool _inited;

		public static void Initialize(Harmony harmony)
		{
			//IL_04b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c9: Expected O, but got Unknown
			//IL_04d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_04de: Unknown result type (might be due to invalid IL or missing references)
			//IL_04eb: Expected O, but got Unknown
			//IL_04fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0500: Unknown result type (might be due to invalid IL or missing references)
			//IL_050d: Expected O, but got Unknown
			//IL_051d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0522: Unknown result type (might be due to invalid IL or missing references)
			//IL_052f: Expected O, but got Unknown
			if (_inited)
			{
				return;
			}
			_inited = true;
			if (!Enabled || string.IsNullOrWhiteSpace(RulesFilePath))
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)"[WHY_LAG] FieldProbe disabled or rules file empty, skip");
				}
				return;
			}
			if (!File.Exists(RulesFilePath))
			{
				ManualLogSource log2 = WhySoLaggyPlugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[WHY_LAG] FieldProbe rules file not found: " + RulesFilePath));
				}
				return;
			}
			string json;
			try
			{
				json = File.ReadAllText(RulesFilePath, Encoding.UTF8);
			}
			catch (Exception ex)
			{
				ManualLogSource log3 = WhySoLaggyPlugin.Log;
				if (log3 != null)
				{
					log3.LogWarning((object)("[WHY_LAG] FieldProbe read rules failed: " + ex.Message));
				}
				return;
			}
			if (!MiniJson.TryParse(json, out var value, out var error))
			{
				ManualLogSource log4 = WhySoLaggyPlugin.Log;
				if (log4 != null)
				{
					log4.LogWarning((object)("[WHY_LAG] FieldProbe JSON parse failed: " + error));
				}
				return;
			}
			Dictionary<string, object> dictionary = MiniJson.AsObject(value);
			if (dictionary == null)
			{
				ManualLogSource log5 = WhySoLaggyPlugin.Log;
				if (log5 != null)
				{
					log5.LogWarning((object)"[WHY_LAG] FieldProbe rules root is not object");
				}
				return;
			}
			if (!MiniJson.GetBool(dictionary, "enabled", defVal: true))
			{
				ManualLogSource log6 = WhySoLaggyPlugin.Log;
				if (log6 != null)
				{
					log6.LogInfo((object)"[WHY_LAG] FieldProbe rules file disabled by 'enabled:false'");
				}
				return;
			}
			int defVal = MiniJson.GetInt(dictionary, "rateLimitPerRule", DefaultRateLimit);
			int defVal2 = MiniJson.GetInt(dictionary, "maxValueLen", DefaultMaxValueLen);
			bool defVal3 = MiniJson.GetBool(dictionary, "includeStack", DefaultIncludeStack);
			int defVal4 = MiniJson.GetInt(dictionary, "stackMaxDepth", DefaultStackMaxDepth);
			object value2;
			List<object> list = MiniJson.AsArray(dictionary.TryGetValue("rules", out value2) ? value2 : null);
			if (list == null || list.Count == 0)
			{
				ManualLogSource log7 = WhySoLaggyPlugin.Log;
				if (log7 != null)
				{
					log7.LogInfo((object)"[WHY_LAG] FieldProbe: no rules in file");
				}
				return;
			}
			int num = 0;
			List<Rule> list2 = new List<Rule>();
			foreach (object item in list)
			{
				Dictionary<string, object> dictionary2 = MiniJson.AsObject(item);
				if (dictionary2 == null || !MiniJson.GetBool(dictionary2, "enabled", defVal: true))
				{
					continue;
				}
				string text = MiniJson.GetString(dictionary2, "target");
				if (string.IsNullOrEmpty(text) || text.IndexOf('.') <= 0)
				{
					ManualLogSource log8 = WhySoLaggyPlugin.Log;
					if (log8 != null)
					{
						log8.LogWarning((object)("[WHY_LAG] FieldProbe rule skipped: bad target '" + text + "'"));
					}
					continue;
				}
				object value3;
				List<object> list3 = MiniJson.AsArray(dictionary2.TryGetValue("fields", out value3) ? value3 : null);
				if (list3 == null || list3.Count == 0)
				{
					ManualLogSource log9 = WhySoLaggyPlugin.Log;
					if (log9 != null)
					{
						log9.LogWarning((object)("[WHY_LAG] FieldProbe rule skipped (no fields): " + text));
					}
					continue;
				}
				int num2 = text.IndexOf('.');
				Rule rule = new Rule();
				num = (rule.Id = num + 1);
				rule.TargetTypeName = text.Substring(0, num2);
				rule.TargetMethodName = text.Substring(num2 + 1);
				rule.TargetKey = text;
				rule.RateLimit = MiniJson.GetInt(dictionary2, "rateLimit", defVal);
				rule.MaxValueLen = MiniJson.GetInt(dictionary2, "maxValueLen", defVal2);
				rule.IncludeStack = MiniJson.GetBool(dictionary2, "includeStack", defVal3);
				rule.StackMaxDepth = MiniJson.GetInt(dictionary2, "stackMaxDepth", defVal4);
				rule.Note = MiniJson.GetString(dictionary2, "note", "");
				rule.Enabled = true;
				Rule rule2 = rule;
				List<string> list4 = new List<string>();
				List<ExpressionEvaluator.CompiledExpr> list5 = new List<ExpressionEvaluator.CompiledExpr>();
				bool flag = false;
				bool flag2 = false;
				foreach (object item2 in list3)
				{
					string text2 = item2 as string;
					if (string.IsNullOrEmpty(text2))
					{
						continue;
					}
					text2 = text2.Trim();
					ExpressionEvaluator.CompiledExpr compiledExpr = ExpressionEvaluator.Compile(text2);
					if (!string.IsNullOrEmpty(compiledExpr.CompileError))
					{
						ManualLogSource log10 = WhySoLaggyPlugin.Log;
						if (log10 != null)
						{
							log10.LogWarning((object)("[WHY_LAG] FieldProbe compile err (" + text + "): '" + text2 + "' → " + compiledExpr.CompileError));
						}
					}
					else
					{
						if (compiledExpr.Root == ExpressionEvaluator.RootKind.Result)
						{
							flag = true;
						}
						if (compiledExpr.Root == ExpressionEvaluator.RootKind.Exception)
						{
							flag2 = true;
						}
						list4.Add(text2);
						list5.Add(compiledExpr);
					}
				}
				if (list5.Count != 0)
				{
					rule2.RawFields = list4.ToArray();
					rule2.Compiled = list5.ToArray();
					rule2.NeedsFinalizer = flag2;
					rule2.NeedsPostfix = flag && !flag2;
					rule2.NeedsPrefix = !rule2.NeedsPostfix && !rule2.NeedsFinalizer;
					list2.Add(rule2);
				}
			}
			if (list2.Count == 0)
			{
				ManualLogSource log11 = WhySoLaggyPlugin.Log;
				if (log11 != null)
				{
					log11.LogInfo((object)"[WHY_LAG] FieldProbe: no valid rules after parsing");
				}
				return;
			}
			HarmonyMethod val = new HarmonyMethod(typeof(FieldProbe), "OnPrefix", (Type[])null)
			{
				priority = 800
			};
			HarmonyMethod val2 = new HarmonyMethod(typeof(FieldProbe), "OnArgsCapture", (Type[])null)
			{
				priority = 800
			};
			HarmonyMethod val3 = new HarmonyMethod(typeof(FieldProbe), "OnPostfix", (Type[])null)
			{
				priority = 800
			};
			HarmonyMethod val4 = new HarmonyMethod(typeof(FieldProbe), "OnFinalizer", (Type[])null)
			{
				priority = 800
			};
			Dictionary<MethodBase, List<Rule>> dictionary3 = new Dictionary<MethodBase, List<Rule>>();
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				Type[] types;
				try
				{
					types = assembly.GetTypes();
				}
				catch
				{
					continue;
				}
				Type[] array = types;
				foreach (Type type in array)
				{
					if (type == null)
					{
						continue;
					}
					string name = type.Name;
					foreach (Rule item3 in list2)
					{
						if (name != item3.TargetTypeName)
						{
							continue;
						}
						MethodInfo[] methods;
						try
						{
							methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
						}
						catch
						{
							continue;
						}
						int num3 = 0;
						MethodInfo[] array2 = methods;
						foreach (MethodInfo methodInfo in array2)
						{
							if (!(methodInfo == null) && !(methodInfo.Name != item3.TargetMethodName) && !methodInfo.IsAbstract && !methodInfo.ContainsGenericParameters)
							{
								if (!dictionary3.TryGetValue(methodInfo, out var value4))
								{
									value4 = (dictionary3[methodInfo] = new List<Rule>());
								}
								value4.Add(item3);
								num3++;
							}
						}
						if (num3 == 0)
						{
							ManualLogSource log12 = WhySoLaggyPlugin.Log;
							if (log12 != null)
							{
								log12.LogInfo((object)("[WHY_LAG] FieldProbe: type " + name + " matched in asm " + assembly.GetName().Name + " but no method '" + item3.TargetMethodName + "' found"));
							}
						}
					}
				}
			}
			foreach (KeyValuePair<MethodBase, List<Rule>> item4 in dictionary3)
			{
				MethodBase key = item4.Key;
				List<Rule> value5 = item4.Value;
				bool flag3 = false;
				bool flag4 = false;
				bool flag5 = false;
				for (int l = 0; l < value5.Count; l++)
				{
					flag3 |= value5[l].NeedsPrefix;
					flag4 |= value5[l].NeedsPostfix;
					flag5 |= value5[l].NeedsFinalizer;
				}
				HarmonyMethod val5 = null;
				if (flag3)
				{
					val5 = val;
				}
				else if (flag4 || flag5)
				{
					val5 = val2;
				}
				try
				{
					harmony.Patch(key, val5, flag4 ? val3 : null, (HarmonyMethod)null, flag5 ? val4 : null, (HarmonyMethod)null);
					_methodToRules[key] = value5;
					_hookedMethods++;
					_hookedRules += value5.Count;
					string text3 = FormatSig(key);
					for (int m = 0; m < value5.Count; m++)
					{
						Rule rule3 = value5[m];
						AbuseLogger.Write("[FIELD_PROBE] Hooked " + rule3.TargetKey + text3 + " (mode=" + (rule3.NeedsPostfix ? "Postfix" : (rule3.NeedsFinalizer ? "Finalizer" : "Prefix")) + ", " + $"fields={rule3.Compiled.Length}, rate={rule3.RateLimit}/s, stack={rule3.IncludeStack})");
					}
				}
				catch (Exception ex2)
				{
					ManualLogSource log13 = WhySoLaggyPlugin.Log;
					if (log13 != null)
					{
						log13.LogWarning((object)("[WHY_LAG] FieldProbe Patch failed on " + key.DeclaringType?.FullName + "." + key.Name + FormatSig(key) + ": " + ex2.Message));
					}
				}
			}
			AbuseLogger.Write($"[FIELD_PROBE] Initialized (rules={list2.Count}, hookedRules={_hookedRules}, hookedMethods={_hookedMethods}, file={Path.GetFileName(RulesFilePath)})");
		}

		public static void OnPrefix(MethodBase __originalMethod, object[] __args, object __instance, out object[] __state)
		{
			__state = ((__args != null) ? ((object[])__args.Clone()) : null);
			Dispatch(__originalMethod, __args, __instance, null, null, 0);
		}

		public static void OnArgsCapture(object[] __args, out object[] __state)
		{
			__state = ((__args != null) ? ((object[])__args.Clone()) : null);
		}

		public static void OnPostfix(MethodBase __originalMethod, object __instance, object __result, object[] __state)
		{
			Dispatch(__originalMethod, __state, __instance, __result, null, 1);
		}

		public static Exception OnFinalizer(MethodBase __originalMethod, object __instance, object __result, Exception __exception, object[] __state)
		{
			Dispatch(__originalMethod, __state, __instance, __result, __exception, 2);
			return __exception;
		}

		private static void Dispatch(MethodBase mb, object[] args, object instance, object result, Exception ex, int kind)
		{
			if (mb == null || !_methodToRules.TryGetValue(mb, out var value))
			{
				return;
			}
			for (int i = 0; i < value.Count; i++)
			{
				Rule rule = value[i];
				if (rule.Enabled && ((kind == 0 && rule.NeedsPrefix) || (kind == 1 && rule.NeedsPostfix) || (kind == 2 && rule.NeedsFinalizer)) && CheckRate(rule))
				{
					WriteSnapshot(rule, instance, args, result, ex, mb);
				}
			}
		}

		private static void WriteSnapshot(Rule rule, object instance, object[] args, object result, Exception ex, MethodBase mb)
		{
			StringBuilder stringBuilder = new StringBuilder(128);
			for (int i = 0; i < rule.Compiled.Length; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(";");
				}
				stringBuilder.Append(rule.RawFields[i]);
				stringBuilder.Append('=');
				string value;
				try
				{
					value = ExpressionEvaluator.Evaluate(rule.Compiled[i], instance, args, result, ex, rule.MaxValueLen);
				}
				catch (Exception ex2)
				{
					value = "err:" + ex2.GetType().Name;
				}
				stringBuilder.Append(value);
			}
			string text = null;
			string text2 = null;
			if (rule.IncludeStack)
			{
				string stack;
				try
				{
					stack = Environment.StackTrace;
				}
				catch
				{
					stack = null;
				}
				text = FilterStack(stack, rule.StackMaxDepth);
				text2 = ExtractFirstFrame(text);
			}
			try
			{
				StructuredLogger.WriteEvent(new StructuredEvent
				{
					Timestamp = StructuredLogger.NowStamp(),
					FrameNumber = Time.frameCount,
					Type = EventType.MethodTrace,
					Fields = new Dictionary<string, object>
					{
						{ "TargetMethod", rule.TargetKey },
						{
							"Snapshot",
							Truncate(stringBuilder.ToString(), 2000)
						},
						{
							"TraceStack",
							text ?? ""
						},
						{
							"TraceCaller",
							text2 ?? ""
						},
						{
							"PatchType",
							kindToStr(rule)
						}
					}
				});
			}
			catch
			{
			}
		}

		private static string kindToStr(Rule r)
		{
			if (!r.NeedsPostfix)
			{
				if (!r.NeedsFinalizer)
				{
					return "Prefix";
				}
				return "Finalizer";
			}
			return "Postfix";
		}

		private static bool CheckRate(Rule rule)
		{
			lock (_rateLock)
			{
				long ticks = DateTime.UtcNow.Ticks;
				_windowStart.TryGetValue(rule.Id, out var value);
				if (value == 0L || ticks - value >= _ticksPerSec)
				{
					_windowStart[rule.Id] = ticks;
					_counter[rule.Id] = 1;
					_overflowWarned.Remove(rule.Id);
					return true;
				}
				_counter.TryGetValue(rule.Id, out var value2);
				value2++;
				_counter[rule.Id] = value2;
				if (value2 > rule.RateLimit)
				{
					if (_overflowWarned.Add(rule.Id))
					{
						ManualLogSource log = WhySoLaggyPlugin.Log;
						if (log != null)
						{
							log.LogWarning((object)$"[WHY_LAG] FieldProbe rate limit hit for {rule.TargetKey} (>{rule.RateLimit}/s)");
						}
					}
					return false;
				}
				return true;
			}
		}

		private static string FilterStack(string stack, int maxDepth)
		{
			if (string.IsNullOrEmpty(stack) || maxDepth <= 0)
			{
				return "";
			}
			string[] array = stack.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
			StringBuilder stringBuilder = new StringBuilder(128);
			int num = 0;
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				string text = array2[i].TrimStart(Array.Empty<char>());
				if (text.StartsWith("at ", StringComparison.Ordinal) && text.IndexOf("UnityEngine.", StringComparison.Ordinal) < 0 && text.IndexOf("HarmonyLib.", StringComparison.Ordinal) < 0 && text.IndexOf("MonoMod.", StringComparison.Ordinal) < 0 && text.IndexOf("System.Environment.", StringComparison.Ordinal) < 0 && text.IndexOf("WhySoLaggy.FieldProbe", StringComparison.Ordinal) < 0 && text.IndexOf("WhySoLaggy.ExpressionEvaluator", StringComparison.Ordinal) < 0)
				{
					if (num > 0)
					{
						stringBuilder.Append(" | ");
					}
					stringBuilder.Append(text);
					num++;
					if (num >= maxDepth)
					{
						break;
					}
				}
			}
			return stringBuilder.ToString();
		}

		private static string ExtractFirstFrame(string filtered)
		{
			if (string.IsNullOrEmpty(filtered))
			{
				return null;
			}
			int num = filtered.IndexOf('|');
			if (num <= 0)
			{
				return filtered;
			}
			return filtered.Substring(0, num).Trim();
		}

		private static string FormatSig(MethodBase mb)
		{
			if (mb == null)
			{
				return "";
			}
			ParameterInfo[] parameters = mb.GetParameters();
			if (parameters == null || parameters.Length == 0)
			{
				return "()";
			}
			StringBuilder stringBuilder = new StringBuilder(32);
			stringBuilder.Append('(');
			for (int i = 0; i < parameters.Length; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append(parameters[i].ParameterType.Name);
			}
			stringBuilder.Append(')');
			return stringBuilder.ToString();
		}

		private static string Truncate(string s, int max)
		{
			if (string.IsNullOrEmpty(s) || s.Length <= max)
			{
				return s;
			}
			return s.Substring(0, max) + "...";
		}

		public static void Shutdown()
		{
			_inited = false;
			_hookedRules = 0;
			_hookedMethods = 0;
			_methodToRules.Clear();
			lock (_rateLock)
			{
				_counter.Clear();
				_windowStart.Clear();
				_overflowWarned.Clear();
			}
			ExpressionEvaluator.ClearCaches();
		}
	}
	internal static class FpsTracker
	{
		public static int SpikeThresholdMs = 50;

		public static int ReportIntervalSeconds = 10;

		public static bool EnableMemoryMonitor = true;

		private const int WindowSize = 600;

		private static readonly float[] _frameTimes = new float[600];

		private static int _writeIndex;

		private static int _sampleCount;

		private static float _periodTimer;

		private static int _periodFrames;

		private static float _periodSumMs;

		private static float _periodMinFps = float.MaxValue;

		private static float _periodMaxFps;

		private static int _periodSpikeCount;

		private static long _memLastSampleBytes;

		private static float _memSampleTimer;

		private static float _memLastRateKBps;

		private const float MemSampleInterval = 1f;

		private static readonly StringBuilder _reportSb = new StringBuilder(256);

		public static bool IsSpikeFrame { get; private set; }

		public static float CurrentFrameMs { get; private set; }

		public static void Tick()
		{
			float unscaledDeltaTime = Time.unscaledDeltaTime;
			float num = (CurrentFrameMs = unscaledDeltaTime * 1000f);
			_frameTimes[_writeIndex] = num;
			_writeIndex = (_writeIndex + 1) % 600;
			if (_sampleCount < 600)
			{
				_sampleCount++;
			}
			IsSpikeFrame = num > (float)SpikeThresholdMs;
			if (IsSpikeFrame)
			{
				_periodSpikeCount++;
			}
			_periodFrames++;
			_periodSumMs += num;
			float num3 = ((unscaledDeltaTime > 0f) ? (1f / unscaledDeltaTime) : 0f);
			if (num3 < _periodMinFps)
			{
				_periodMinFps = num3;
			}
			if (num3 > _periodMaxFps)
			{
				_periodMaxFps = num3;
			}
			if (EnableMemoryMonitor)
			{
				_memSampleTimer += unscaledDeltaTime;
				if (_memSampleTimer >= 1f)
				{
					try
					{
						long totalAllocatedMemoryLong = Profiler.GetTotalAllocatedMemoryLong();
						if (_memLastSampleBytes > 0 && totalAllocatedMemoryLong >= _memLastSampleBytes)
						{
							_memLastRateKBps = (float)((double)(totalAllocatedMemoryLong - _memLastSampleBytes) / 1024.0 / (double)_memSampleTimer);
						}
						_memLastSampleBytes = totalAllocatedMemoryLong;
					}
					catch (Exception ex)
					{
						ManualLogSource log = WhySoLaggyPlugin.Log;
						if (log != null)
						{
							log.LogWarning((object)("[WHY_LAG] MemoryMonitor sample failed: " + ex.Message));
						}
						EnableMemoryMonitor = false;
					}
					_memSampleTimer = 0f;
				}
			}
			_periodTimer += unscaledDeltaTime;
			if (_periodTimer >= (float)ReportIntervalSeconds)
			{
				WriteReport();
				ResetPeriod();
			}
		}

		public static float GetWindowAvgMs()
		{
			if (_sampleCount == 0)
			{
				return 0f;
			}
			float num = 0f;
			for (int i = 0; i < _sampleCount; i++)
			{
				num += _frameTimes[i];
			}
			return num / (float)_sampleCount;
		}

		public static float GetAllocRateKBps()
		{
			return _memLastRateKBps;
		}

		private static void WriteReport()
		{
			if (_periodFrames == 0)
			{
				return;
			}
			float num = ((_periodSumMs > 0f) ? ((float)_periodFrames / (_periodSumMs / 1000f)) : 0f);
			float num2 = _periodSumMs / (float)_periodFrames;
			_reportSb.Clear();
			_reportSb.AppendLine($"[WHY_LAG] === FPS Report ({_periodTimer:F1}s, {_periodFrames} frames) ===");
			_reportSb.AppendLine($"[WHY_LAG] FPS: avg={num:F1} | min={_periodMinFps:F1} | max={_periodMaxFps:F1} | avgFrame={num2:F1}ms");
			_reportSb.Append($"[WHY_LAG] Spikes(>{SpikeThresholdMs}ms): {_periodSpikeCount}");
			if (EnableMemoryMonitor)
			{
				_reportSb.Append($" | AllocRate: {_memLastRateKBps:F1} KB/s");
			}
			LagLogger.Write(_reportSb.ToString());
			try
			{
				RpcMonitor.RpcRuntimeMetrics rpcRuntimeMetrics = RpcMonitor.TakeRuntimeMetrics();
				StructuredRuntimeMetrics structuredRuntimeMetrics = StructuredLogger.TakeRuntimeMetrics();
				Dictionary<string, object> dictionary = new Dictionary<string, object>
				{
					{
						"AvgFps",
						Math.Round(num, 1)
					},
					{
						"MinFps",
						Math.Round(_periodMinFps, 1)
					},
					{
						"MaxFps",
						Math.Round(_periodMaxFps, 1)
					},
					{
						"AvgFrameMs",
						Math.Round(num2, 2)
					},
					{ "SpikeThresholdMs", SpikeThresholdMs },
					{ "SpikeCount", _periodSpikeCount },
					{
						"ReportDuration",
						Math.Round(_periodTimer, 2)
					},
					{ "RpcQueueDepth", rpcRuntimeMetrics.QueueDepth },
					{ "RpcQueuePeak", rpcRuntimeMetrics.QueuePeak },
					{ "RpcQueueDropped", rpcRuntimeMetrics.QueueDropped },
					{ "RpcProcessedCount", rpcRuntimeMetrics.ProcessedCount },
					{
						"RpcPumpMs",
						Math.Round(rpcRuntimeMetrics.PumpMs, 3)
					},
					{ "StructuredEventsWritten", structuredRuntimeMetrics.EventsWritten },
					{
						"StructuredFlushMs",
						Math.Round(structuredRuntimeMetrics.LastFlushMs, 3)
					}
				};
				if (EnableMemoryMonitor)
				{
					dictionary["AllocRateKBps"] = Math.Round(_memLastRateKBps, 2);
				}
				StructuredLogger.WriteEvent(new StructuredEvent
				{
					Timestamp = StructuredLogger.NowStamp(),
					FrameNumber = Time.frameCount,
					Type = EventType.FpsReport,
					Fields = dictionary
				});
			}
			catch (Exception ex)
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[WHY_LAG] FpsTracker WriteEvent failed: " + ex.Message));
				}
			}
		}

		private static void ResetPeriod()
		{
			_periodTimer = 0f;
			_periodFrames = 0;
			_periodSumMs = 0f;
			_periodMinFps = float.MaxValue;
			_periodMaxFps = 0f;
			_periodSpikeCount = 0;
		}

		public static void Reset()
		{
			Array.Clear(_frameTimes, 0, _frameTimes.Length);
			_writeIndex = 0;
			_sampleCount = 0;
			IsSpikeFrame = false;
			CurrentFrameMs = 0f;
			_memLastSampleBytes = 0L;
			_memSampleTimer = 0f;
			_memLastRateKBps = 0f;
			ResetPeriod();
		}
	}
	internal static class HarmonyScanner
	{
		public static void Scan(string bepInExDir)
		{
			try
			{
				string text = Path.Combine(bepInExDir, "harmony_patches.csv");
				using StreamWriter streamWriter = new StreamWriter(text, append: false, Encoding.UTF8);
				streamWriter.WriteLine("TargetMethod,PatchType,OwnerHarmonyId,Priority");
				int num = 0;
				int num2 = 0;
				foreach (MethodBase allPatchedMethod in Harmony.GetAllPatchedMethods())
				{
					if (allPatchedMethod == null)
					{
						continue;
					}
					Patches val = null;
					try
					{
						val = Harmony.GetPatchInfo(allPatchedMethod);
					}
					catch (Exception ex)
					{
						ManualLogSource log = WhySoLaggyPlugin.Log;
						if (log != null)
						{
							log.LogWarning((object)("[WHY_LAG] HarmonyScanner GetPatchInfo failed on " + allPatchedMethod.Name + ": " + ex.Message));
						}
						continue;
					}
					if (val != null)
					{
						string text2 = (allPatchedMethod.DeclaringType?.FullName ?? "?") + "." + allPatchedMethod.Name;
						HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
						WriteGroup(streamWriter, val.Prefixes, "Prefix", text2, hashSet);
						WriteGroup(streamWriter, val.Postfixes, "Postfix", text2, hashSet);
						WriteGroup(streamWriter, val.Transpilers, "Transpiler", text2, hashSet);
						WriteGroup(streamWriter, val.Finalizers, "Finalizer", text2, hashSet);
						num++;
						if (hashSet.Count >= 2)
						{
							num2++;
							AbuseLogger.Write(string.Format("[HARMONY_SCAN] Potential conflict: {0} patched by {1} mods: {2}", text2, hashSet.Count, string.Join(", ", hashSet)));
						}
					}
				}
				streamWriter.Flush();
				AbuseLogger.Write($"[HARMONY_SCAN] Scanned {num} patched methods, {num2} potential conflicts. CSV -> {text}");
			}
			catch (Exception ex2)
			{
				ManualLogSource log2 = WhySoLaggyPlugin.Log;
				if (log2 != null)
				{
					log2.LogError((object)("[WHY_LAG] HarmonyScanner failed: " + ex2.Message));
				}
			}
		}

		private static void WriteGroup(StreamWriter sw, IList<Patch> list, string kind, string target, HashSet<string> owners)
		{
			if (list == null)
			{
				return;
			}
			foreach (Patch item in list)
			{
				if (item != null)
				{
					string text = item.owner ?? "?";
					owners.Add(text);
					string value = CsvEscape(target);
					string value2 = CsvEscape(text);
					sw.Write(value);
					sw.Write(',');
					sw.Write(kind);
					sw.Write(',');
					sw.Write(value2);
					sw.Write(',');
					int priority = item.priority;
					sw.Write(priority.ToString(CultureInfo.InvariantCulture));
					sw.WriteLine();
					try
					{
						StructuredLogger.WriteEvent(new StructuredEvent
						{
							Timestamp = StructuredLogger.NowStamp(),
							FrameNumber = Time.frameCount,
							Type = EventType.HarmonyPatchMap,
							Fields = new Dictionary<string, object>
							{
								{ "TargetMethod", target },
								{ "PatchType", kind },
								{ "OwnerHarmonyId", text },
								{ "Priority", item.priority }
							}
						});
					}
					catch
					{
					}
				}
			}
		}

		private static string CsvEscape(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			bool flag = false;
			foreach (char c in s)
			{
				if (c == ',' || c == '"' || c == '\n' || c == '\r')
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				return s;
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length + 8);
			stringBuilder.Append('"');
			foreach (char c2 in s)
			{
				if (c2 == '"')
				{
					stringBuilder.Append("\"\"");
				}
				else
				{
					stringBuilder.Append(c2);
				}
			}
			stringBuilder.Append('"');
			return stringBuilder.ToString();
		}
	}
	internal static class LagLogger
	{
		private static StreamWriter _writer;

		private static readonly object _lock = new object();

		public static void Initialize(string bepInExDir)
		{
			try
			{
				string text = Path.Combine(bepInExDir, "WhySoLaggy.log");
				_writer = new StreamWriter(text, append: false, Encoding.UTF8)
				{
					AutoFlush = true
				};
				_writer.WriteLine("[" + Timestamp() + "] WhySoLaggy Performance Monitor started");
				_writer.WriteLine("[" + Timestamp() + "] Log file: " + text);
				_writer.WriteLine(new string('=', 60));
			}
			catch (Exception ex)
			{
				Logger.CreateLogSource("WhySoLaggy").LogError((object)("Failed to create log file: " + ex.Message));
			}
		}

		public static void Write(string message)
		{
			if (_writer == null || !LogFilter.AllowLag())
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					_writer.WriteLine("[" + Timestamp() + "] " + message);
				}
				catch
				{
				}
			}
		}

		public static void Info(string message)
		{
			if (_writer == null)
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					_writer.WriteLine("[" + Timestamp() + "] " + message);
				}
				catch
				{
				}
			}
		}

		public static void WriteRaw(string line)
		{
			if (_writer == null)
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					_writer.WriteLine(line);
				}
				catch
				{
				}
			}
		}

		public static void Flush()
		{
			if (_writer == null)
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					_writer.Flush();
				}
				catch
				{
				}
			}
		}

		public static void Shutdown()
		{
			if (_writer == null)
			{
				return;
			}
			lock (_lock)
			{
				try
				{
					_writer.WriteLine("[" + Timestamp() + "] WhySoLaggy shutting down");
					_writer.Flush();
					_writer.Close();
					_writer = null;
				}
				catch
				{
				}
			}
		}

		private static string Timestamp()
		{
			return DateTime.Now.ToString("HH:mm:ss.fff");
		}
	}
	public enum LogVerbosity
	{
		Minimal,
		Normal
	}
	public static class LogFilter
	{
		public static LogVerbosity Level;

		public static bool AllowLag()
		{
			return Level >= LogVerbosity.Normal;
		}

		public static bool AllowAbuse()
		{
			return Level >= LogVerbosity.Normal;
		}
	}
	internal static class MethodKey
	{
		public static string Canonical(MethodBase method)
		{
			if (method == null)
			{
				return "(null)";
			}
			StringBuilder stringBuilder = new StringBuilder(96);
			stringBuilder.Append(method.DeclaringType?.FullName ?? "?").Append('.').Append(method.Name)
				.Append('(');
			ParameterInfo[] parameters = method.GetParameters();
			for (int i = 0; i < parameters.Length; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append(FormatType(parameters[i].ParameterType));
			}
			return stringBuilder.Append(')').ToString();
		}

		public static string Legacy(MethodBase method)
		{
			if (method == null)
			{
				return "(null)";
			}
			return (method.DeclaringType?.Name ?? "?") + "." + method.Name;
		}

		public static string LegacyFullType(MethodBase method)
		{
			if (method == null)
			{
				return "(null)";
			}
			return (method.DeclaringType?.FullName ?? "?") + "." + method.Name;
		}

		public static bool IsIgnored(MethodBase method, HashSet<string> ignoreSet)
		{
			if (method == null || ignoreSet == null || ignoreSet.Count == 0)
			{
				return false;
			}
			if (!ignoreSet.Contains(Canonical(method)) && !ignoreSet.Contains(Legacy(method)))
			{
				return ignoreSet.Contains(LegacyFullType(method));
			}
			return true;
		}

		private static string FormatType(Type type)
		{
			if (type == null)
			{
				return "?";
			}
			if (type.IsByRef)
			{
				return FormatType(type.GetElementType()) + "&";
			}
			if (type.IsPointer)
			{
				return FormatType(type.GetElementType()) + "*";
			}
			if (type.IsArray)
			{
				return FormatType(type.GetElementType()) + "[]";
			}
			if (!type.IsGenericType)
			{
				return type.FullName ?? type.Name;
			}
			string text = type.GetGenericTypeDefinition().FullName ?? type.Name;
			int num = text.IndexOf('`');
			if (num >= 0)
			{
				text = text.Substring(0, num);
			}
			StringBuilder stringBuilder = new StringBuilder(text).Append('<');
			Type[] genericArguments = type.GetGenericArguments();
			for (int i = 0; i < genericArguments.Length; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append(FormatType(genericArguments[i]));
			}
			return stringBuilder.Append('>').ToString();
		}
	}
	internal static class MethodTracer
	{
		public static string TraceMethodNames = "";

		public static int TraceMaxDepth = 5;

		public static int TraceRateLimit = 100;

		private static readonly object _rateLock = new object();

		private static readonly Dictionary<string, int> _counter = new Dictionary<string, int>(StringComparer.Ordinal);

		private static readonly Dictionary<string, long> _windowStartTicks = new Dictionary<string, long>(StringComparer.Ordinal);

		private static readonly HashSet<string> _overflowWarnedInWindow = new HashSet<string>(StringComparer.Ordinal);

		private static readonly long _ticksPerSecond = 10000000L;

		private static bool _inited;

		private static int _hooked;

		public static void Initialize(Harmony harmony)
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Expected O, but got Unknown
			if (_inited)
			{
				return;
			}
			_inited = true;
			if (string.IsNullOrWhiteSpace(TraceMethodNames))
			{
				return;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			string[] array = TraceMethodNames.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (!string.IsNullOrEmpty(text))
				{
					hashSet.Add(text);
				}
			}
			if (hashSet.Count == 0)
			{
				return;
			}
			HarmonyMethod val = new HarmonyMethod(typeof(MethodTracer), "OnPrefix", (Type[])null)
			{
				priority = 800
			};
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				Type[] array2 = null;
				try
				{
					array2 = assembly.GetTypes();
				}
				catch (Exception ex)
				{
					ManualLogSource log = WhySoLaggyPlugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("[WHY_LAG] MethodTracer GetTypes failed in asm=" + assembly.GetName().Name + ": " + ex.Message));
					}
					continue;
				}
				Type[] array3 = array2;
				foreach (Type type in array3)
				{
					string name = type.Name;
					foreach (string item in hashSet)
					{
						int num = item.IndexOf('.');
						if (num <= 0)
						{
							continue;
						}
						string text2 = item.Substring(0, num);
						string name2 = item.Substring(num + 1);
						if (name != text2)
						{
							continue;
						}
						MethodInfo methodInfo = null;
						try
						{
							methodInfo = type.GetMethod(name2, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
						}
						catch
						{
						}
						if (methodInfo == null)
						{
							continue;
						}
						try
						{
							harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
							_hooked++;
							AbuseLogger.Write("[METHOD_TRACE] Hooked " + item);
						}
						catch (Exception ex2)
						{
							ManualLogSource log2 = WhySoLaggyPlugin.Log;
							if (log2 != null)
							{
								log2.LogWarning((object)("[WHY_LAG] MethodTracer Patch failed on " + item + ": " + ex2.Message));
							}
						}
					}
				}
			}
			AbuseLogger.Write($"[METHOD_TRACE] Initialized (hooked: {_hooked}, maxDepth: {TraceMaxDepth}, rateLimit: {TraceRateLimit}/s)");
		}

		public static void OnPrefix(MethodBase __originalMethod)
		{
			if (__originalMethod == null)
			{
				return;
			}
			string text = MethodKey.Canonical(__originalMethod);
			if (!CheckRateLimit(text))
			{
				return;
			}
			string stackTrace;
			try
			{
				stackTrace = Environment.StackTrace;
			}
			catch
			{
				return;
			}
			string text2 = FilterStack(stackTrace, TraceMaxDepth);
			string text3 = ExtractFirstFrame(text2);
			try
			{
				StructuredLogger.WriteEvent(new StructuredEvent
				{
					Timestamp = StructuredLogger.NowStamp(),
					FrameNumber = Time.frameCount,
					Type = EventType.MethodTrace,
					Fields = new Dictionary<string, object>
					{
						{ "TargetMethod", text },
						{
							"TraceStack",
							Truncate(text2, 1200)
						},
						{
							"TraceCaller",
							text3 ?? ""
						}
					}
				});
			}
			catch
			{
			}
		}

		private static bool CheckRateLimit(string key)
		{
			lock (_rateLock)
			{
				long ticks = DateTime.UtcNow.Ticks;
				_windowStartTicks.TryGetValue(key, out var value);
				if (value == 0L || ticks - value >= _ticksPerSecond)
				{
					_windowStartTicks[key] = ticks;
					_counter[key] = 1;
					_overflowWarnedInWindow.Remove(key);
					return true;
				}
				_counter.TryGetValue(key, out var value2);
				value2++;
				_counter[key] = value2;
				if (value2 > TraceRateLimit)
				{
					if (_overflowWarnedInWindow.Add(key))
					{
						ManualLogSource log = WhySoLaggyPlugin.Log;
						if (log != null)
						{
							log.LogWarning((object)$"[WHY_LAG] MethodTracer rate limit exceeded for {key} (>{TraceRateLimit}/s); suppressing additional traces this second");
						}
					}
					return false;
				}
				return true;
			}
		}

		private static string FilterStack(string stack, int maxDepth)
		{
			if (string.IsNullOrEmpty(stack))
			{
				return "";
			}
			string[] array = stack.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
			StringBuilder stringBuilder = new StringBuilder(256);
			int num = 0;
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				string text = array2[i].TrimStart(Array.Empty<char>());
				if (text.StartsWith("at ", StringComparison.Ordinal) && text.IndexOf("UnityEngine.", StringComparison.Ordinal) < 0 && text.IndexOf("HarmonyLib.", StringComparison.Ordinal) < 0 && text.IndexOf("MonoMod.", StringComparison.Ordinal) < 0 && text.IndexOf("System.Environment.", StringComparison.Ordinal) < 0 && text.IndexOf("WhySoLaggy.MethodTracer", StringComparison.Ordinal) < 0)
				{
					if (num > 0)
					{
						stringBuilder.Append(" | ");
					}
					stringBuilder.Append(text);
					num++;
					if (num >= maxDepth)
					{
						break;
					}
				}
			}
			return stringBuilder.ToString();
		}

		private static string ExtractFirstFrame(string filtered)
		{
			if (string.IsNullOrEmpty(filtered))
			{
				return null;
			}
			int num = filtered.IndexOf('|');
			if (num <= 0)
			{
				return filtered;
			}
			return filtered.Substring(0, num).Trim();
		}

		private static string Truncate(string s, int max)
		{
			if (string.IsNullOrEmpty(s) || s.Length <= max)
			{
				return s;
			}
			return s.Substring(0, max) + "...";
		}

		public static void Shutdown()
		{
			_inited = false;
			_hooked = 0;
			lock (_rateLock)
			{
				_counter.Clear();
				_windowStartTicks.Clear();
				_overflowWarnedInWindow.Clear();
			}
		}
	}
	internal static class MiniJson
	{
		public static object Parse(string json)
		{
			if (string.IsNullOrEmpty(json))
			{
				return null;
			}
			int pos = 0;
			SkipWs(json, ref pos);
			object result = ParseValue(json, ref pos);
			SkipWs(json, ref pos);
			if (pos != json.Length)
			{
				throw new FormatException($"MiniJson: unexpected trailing chars at {pos}");
			}
			return result;
		}

		public static bool TryParse(string json, out object value, out string error)
		{
			try
			{
				value = Parse(json);
				error = null;
				return true;
			}
			catch (Exception ex)
			{
				value = null;
				error = ex.Message;
				return false;
			}
		}

		public static Dictionary<string, object> AsObject(object v)
		{
			return v as Dictionary<string, object>;
		}

		public static List<object> AsArray(object v)
		{
			return v as List<object>;
		}

		public static string GetString(Dictionary<string, object> obj, string key, string defVal = null)
		{
			if (obj == null || !obj.TryGetValue(key, out var value) || value == null)
			{
				return defVal;
			}
			return (value as string) ?? Convert.ToString(value, CultureInfo.InvariantCulture);
		}

		public static bool GetBool(Dictionary<string, object> obj, string key, bool defVal)
		{
			if (obj == null || !obj.TryGetValue(key, out var value) || value == null)
			{
				return defVal;
			}
			if (value is bool)
			{
				return (bool)value;
			}
			if (value is string value2)
			{
				if (!bool.TryParse(value2, out var result))
				{
					return defVal;
				}
				return result;
			}
			return defVal;
		}

		public static int GetInt(Dictionary<string, object> obj, string key, int defVal)
		{
			if (obj == null || !obj.TryGetValue(key, out var value) || value == null)
			{
				return defVal;
			}
			if (value is long num)
			{
				return (int)num;
			}
			if (value is double num2)
			{
				return (int)num2;
			}
			if (value is string s && int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				return result;
			}
			return defVal;
		}

		private static object ParseValue(string s, ref int pos)
		{
			SkipWs(s, ref pos);
			if (pos >= s.Length)
			{
				throw new FormatException($"MiniJson: unexpected EOF at {pos}");
			}
			char c = s[pos];
			switch (c)
			{
			case '{':
				return ParseObject(s, ref pos);
			case '[':
				return ParseArray(s, ref pos);
			case '"':
				return ParseString(s, ref pos);
			case 'f':
			case 't':
				return ParseBool(s, ref pos);
			case 'n':
				return ParseNull(s, ref pos);
			case '-':
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				return ParseNumber(s, ref pos);
			default:
				throw new FormatException($"MiniJson: unexpected '{c}' at {pos}");
			}
		}

		private static Dictionary<string, object> ParseObject(string s, ref int pos)
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
			pos++;
			SkipWs(s, ref pos);
			if (pos < s.Length && s[pos] == '}')
			{
				pos++;
				return dictionary;
			}
			while (true)
			{
				SkipWs(s, ref pos);
				if (pos >= s.Length || s[pos] != '"')
				{
					throw new FormatException($"MiniJson: expected string key at {pos}");
				}
				string key = ParseString(s, ref pos);
				SkipWs(s, ref pos);
				if (pos >= s.Length || s[pos] != ':')
				{
					throw new FormatException($"MiniJson: expected ':' at {pos}");
				}
				pos++;
				object value = ParseValue(s, ref pos);
				dictionary[key] = value;
				SkipWs(s, ref pos);
				if (pos >= s.Length)
				{
					throw new FormatException("MiniJson: unexpected EOF in object");
				}
				if (s[pos] != ',')
				{
					break;
				}
				pos++;
			}
			if (s[pos] == '}')
			{
				pos++;
				return dictionary;
			}
			throw new FormatException($"MiniJson: expected ',' or '}}' at {pos}");
		}

		private static List<object> ParseArray(string s, ref int pos)
		{
			List<object> list = new List<object>();
			pos++;
			SkipWs(s, ref pos);
			if (pos < s.Length && s[pos] == ']')
			{
				pos++;
				return list;
			}
			while (true)
			{
				object item = ParseValue(s, ref pos);
				list.Add(item);
				SkipWs(s, ref pos);
				if (pos >= s.Length)
				{
					throw new FormatException("MiniJson: unexpected EOF in array");
				}
				if (s[pos] != ',')
				{
					break;
				}
				pos++;
			}
			if (s[pos] == ']')
			{
				pos++;
				return list;
			}
			throw new FormatException($"MiniJson: expected ',' or ']' at {pos}");
		}

		private static string ParseString(string s, ref int pos)
		{
			if (s[pos] != '"')
			{
				throw new FormatException($"MiniJson: expected '\"' at {pos}");
			}
			pos++;
			StringBuilder stringBuilder = new StringBuilder();
			while (pos < s.Length)
			{
				char c = s[pos++];
				switch (c)
				{
				case '"':
					return stringBuilder.ToString();
				case '\\':
				{
					if (pos >= s.Length)
					{
						throw new FormatException("MiniJson: bad escape at EOF");
					}
					char c2 = s[pos++];
					switch (c2)
					{
					case '"':
						stringBuilder.Append('"');
						break;
					case '\\':
						stringBuilder.Append('\\');
						break;
					case '/':
						stringBuilder.Append('/');
						break;
					case 'b':
						stringBuilder.Append('\b');
						break;
					case 'f':
						stringBuilder.Append('\f');
						break;
					case 'n':
						stringBuilder.Append('\n');
						break;
					case 'r':
						stringBuilder.Append('\r');
						break;
					case 't':
						stringBuilder.Append('\t');
						break;
					case 'u':
					{
						if (pos + 4 > s.Length)
						{
							throw new FormatException("MiniJson: bad \\u at EOF");
						}
						string text = s.Substring(pos, 4);
						pos += 4;
						if (!int.TryParse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
						{
							throw new FormatException("MiniJson: bad \\u hex '" + text + "'");
						}
						stringBuilder.Append((char)result);
						break;
					}
					default:
						throw new FormatException($"MiniJson: unknown escape '\\{c2}' at {pos - 1}");
					}
					break;
				}
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			throw new FormatException("MiniJson: unterminated string");
		}

		private static object ParseNumber(string s, ref int pos)
		{
			int num = pos;
			if (s[pos] == '-')
			{
				pos++;
			}
			bool flag = false;
			while (pos < s.Length)
			{
				char c = s[pos];
				if (c >= '0' && c <= '9')
				{
					pos++;
					continue;
				}
				if (c != '.' && c != 'e' && c != 'E' && c != '+' && c != '-')
				{
					break;
				}
				flag = true;
				pos++;
			}
			string text = s.Substring(num, pos - num);
			long result2;
			if (flag)
			{
				if (double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					return result;
				}
			}
			else if (long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out result2))
			{
				return result2;
			}
			throw new FormatException($"MiniJson: bad number '{text}' at {num}");
		}

		private static bool ParseBool(string s, ref int pos)
		{
			if (pos + 4 <= s.Length && s.Substring(pos, 4) == "true")
			{
				pos += 4;
				return true;
			}
			if (pos + 5 <= s.Length && s.Substring(pos, 5) == "false")
			{
				pos += 5;
				return false;
			}
			throw new FormatException($"MiniJson: bad bool at {pos}");
		}

		private static object ParseNull(string s, ref int pos)
		{
			if (pos + 4 <= s.Length && s.Substring(pos, 4) == "null")
			{
				pos += 4;
				return null;
			}
			throw new FormatException($"MiniJson: bad null at {pos}");
		}

		private static void SkipWs(string s, ref int pos)
		{
			while (pos < s.Length)
			{
				switch (s[pos])
				{
				case '\t':
				case '\n':
				case '\r':
				case ' ':
					pos++;
					break;
				case '/':
					if (pos + 1 < s.Length && s[pos + 1] == '/')
					{
						pos += 2;
						while (pos < s.Length && s[pos] != '\n')
						{
							pos++;
						}
						break;
					}
					return;
				default:
					return;
				}
			}
		}
	}
	internal static class ModConfigLocalization
	{
		internal sealed class LocalizedConfigEntry
		{
			internal string Section { get; }

			internal string Key { get; }

			internal string EnglishName { get; }

			internal string ChineseName { get; }

			internal string EnglishDescription { get; }

			internal string ChineseDescription { get; }

			internal LocalizedConfigEntry(string section, string key, string englishName, string chineseName, string englishDescription, string chineseDescription)
			{
				Section = section;
				Key = key;
				EnglishName = englishName;
				ChineseName = chineseName;
				EnglishDescription = englishDescription;
				ChineseDescription = chineseDescription;
			}
		}

		private sealed class TokenText
		{
			internal string Key { get; }

			internal string English { get; }

			internal string Chinese { get; }

			internal TokenText(string key, string english, string chinese)
			{
				Key = key;
				English = english;
				Chinese = chinese;
			}
		}

		private const string ModConfigGuid = "com.github.PEAKModding.PEAKLib.ModConfig";

		private const string ConfigFileName = "com.wuyachiyu.WhySoLaggy.cfg";

		private static readonly FieldInfo DescriptionBackingField = typeof(ConfigDescription).GetField("<Description>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly LocalizedConfigEntry[] ConfigEntries = new LocalizedConfigEntry[41]
		{
			Entry("General", "SpikeThresholdMs", "Spike Threshold", "卡顿帧阈值", "Frame time threshold for spike detection (ms). Frames exceeding this are logged.", "卡顿检测的帧时间阈值,单位毫秒。超过阈值的帧会被记录。"),
			Entry("General", "ReportIntervalSeconds", "Report Interval", "报告间隔", "Seconds between periodic performance reports.", "性能周期报告之间的间隔秒数。"),
			Entry("General", "EnablePluginProfiling", "Enable Plugin Profiling", "启用插件性能分析", "Profile each BepInEx plugin's Update/LateUpdate/FixedUpdate callbacks. Disabled by default; turn on only when diagnosing.", "分析每个 BepInEx 插件的 Update、LateUpdate 和 FixedUpdate 回调。默认关闭,仅在诊断问题时启用。"),
			Entry("General", "EnablePatchProfiling", "Enable Patch Profiling", "启用补丁性能分析", "Profile all Harmony-patched game methods. Disabled by default; turn on only when diagnosing.", "分析所有被 Harmony 补丁包裹的游戏方法。默认关闭,仅在诊断问题时启用。"),
			Entry("General", "TopMethodCount", "Top Method Count", "显示方法数量", "Number of top slow methods to show in reports.", "报告中显示的高耗时方法数量。"),
			Entry("General", "MinReportMs", "Minimum Report Time", "最低报告耗时", "Ignore patched methods whose average cost is below this threshold (ms), to reduce profiler overhead.", "忽略平均耗时低于此阈值的补丁方法,单位毫秒,以降低性能分析开销。"),
			Entry("General", "IgnorePluginGuids", "Ignored Plugin GUIDs", "忽略的插件 GUID", "Comma-separated plugin GUIDs to skip in PluginProfiler.", "PluginProfiler 要跳过的插件 GUID,使用逗号分隔。"),
			Entry("General", "IgnorePatchMethods", "Ignored Patch Methods", "忽略的补丁方法", "Comma-separated full method names (Type.Method) to skip in PatchProfiler.", "PatchProfiler 要跳过的完整方法名(Type.Method),使用逗号分隔。"),
			Entry("General", "EnableMemoryMonitor", "Enable Memory Monitor", "启用内存监控", "Sample GC allocation rate and include AllocRateKBps in FpsReport events.", "采样 GC 分配速率,并在 FpsReport 事件中记录 AllocRateKBps。"),
			Entry("AbuseDetection", "EnableAbuseDetection", "Enable Abuse Detection", "启用滥用检测", "Enable network abuse / room bombing detection.", "启用网络滥用和炸房检测。"),
			Entry("AbuseDetection", "CheckIntervalSeconds", "Check Interval", "检测间隔", "Seconds between each abuse rate check.", "每次网络滥用速率检查之间的间隔秒数。"),
			Entry("AbuseDetection", "ReportIntervalSeconds", "Report Interval", "报告间隔", "Seconds between periodic abuse summary reports.", "网络滥用汇总报告之间的间隔秒数。"),
			Entry("AbuseDetection", "InstantiateRateThreshold", "Instantiate Rate Threshold", "Instantiate 速率阈值", "Max Instantiate calls per second before triggering alert.", "触发告警前允许的每秒最大 Instantiate 调用数。"),
			Entry("AbuseDetection", "DestroyRateThreshold", "Destroy Rate Threshold", "Destroy 速率阈值", "Max Destroy calls per second before triggering alert.", "触发告警前允许的每秒最大 Destroy 调用数。"),
			Entry("AbuseDetection", "RpcRateThreshold", "RPC Rate Threshold", "RPC 速率阈值", "Max RPC calls per second before triggering alert.", "触发告警前允许的每秒最大 RPC 调用数。"),
			Entry("AbuseDetection", "ObjectSpikeThreshold", "Object Spike Threshold", "对象增长阈值", "Object count increase per check interval to trigger spike alert.", "每个检测周期内触发对象增长告警所需的数量增幅。"),
			Entry("AbuseDetection", "ActorMethodRateThreshold", "Actor Method Rate Threshold", "Actor 方法速率阈值", "Max calls for one remote Actor and RPC method per check window before alerting.", "单个远端 Actor 对同一 RPC 方法在一个检测窗口内触发告警前允许的最大调用数。"),
			Entry("AbuseDetection", "OwnershipGrabRateThreshold", "Ownership Grab Rate Threshold", "所有权抢夺阈值", "Max remote ownership transfers to one new owner per check window before alerting.", "单个新所有者在一个检测窗口内触发告警前允许的最大远端所有权转移数。"),
			Entry("AbuseDetection", "OwnershipRequestRateThreshold", "Ownership Request Rate Threshold", "所有权请求阈值", "Max ownership requests from one remote actor per check window before alerting.", "单个远端 Actor 在一个检测窗口内触发告警前允许的最大所有权请求数。"),
			Entry("AbuseDetection", "AlertCooldownSeconds", "Alert Cooldown", "告警冷却时间", "Seconds before the same abuse alert key can be emitted again.", "同一个滥用告警键再次输出前需要等待的秒数。"),
			Entry("RpcMonitor", "EnableRpcMonitor", "Enable RPC Monitor", "启用 RPC 监控", "Track all network RPC method names and their sources. Low performance overhead (<0.5ms/s for 1000 RPCs/s).", "记录所有网络 RPC 方法名及其来源。性能开销较低(1000 RPC/秒时低于 0.5 毫秒/秒)。"),
			Entry("RpcMonitor", "TopMethodCount", "Top RPC Method Count", "显示 RPC 方法数量", "Number of top RPC methods to show in periodic reports.", "周期报告中显示的高频 RPC 方法数量。"),
			Entry("RpcMonitor", "WatchedRecordPerMethodCapacity", "Watched Records Per Method", "每个方法的记录容量", "Per-method ring buffer size for watched high-risk RPCs. Each watched method has its own independent buffer so high-frequency methods cannot flood out low-frequency ones.", "高风险监控 RPC 的单方法环形缓冲区大小。每个方法拥有独立缓冲区,避免高频方法挤掉低频方法的记录。"),
			Entry("RpcMonitor", "WatchedShowPerMethod", "Watched Records To Show", "每个方法显示记录数", "How many most-recent detailed records to print per watched method in each periodic report.", "每次周期报告中每个监控方法显示的最新详细记录数量。"),
			Entry("RpcMonitor", "ExtraWatchMethods", "Extra Watch Methods", "额外监控方法", "Comma-separated extra RPC method names merged into the watch list (e.g. 'MyRPC1,MyRPC2').", "要加入监控列表的额外 RPC 方法名,使用逗号分隔(例如 'MyRPC1,MyRPC2')。"),
			Entry("RpcMonitor", "PumpBatchSize", "RPC Pump Batch Size", "RPC 每帧处理数量", "Max RPC queue items consumed per frame on main thread.", "主线程每帧最多处理的 RPC 队列项目数。"),
			Entry("RpcMonitor", "QueueCapacity", "RPC Queue Capacity", "RPC 队列容量", "Maximum queued RPC records. When full, the oldest records are discarded so recent activity is retained.", "RPC 记录队列的最大容量。队列满时丢弃最旧记录,以保留最新活动。"),
			Entry("Logging", "LogVerbosity", "Log Verbosity", "日志详细度", "Minimal (default) = abuse alerts + their detail lines + monitor init/error/milestones. Normal = Minimal + periodic reports (RPC top/watched, FPS, patch/plugin profiler, frame spike detail).", "Minimal(默认)= 滥用告警、详情、监控初始化/错误/关键节点;Normal = Minimal 加周期报告(RPC、FPS、补丁/插件分析和帧尖峰详情)。"),
			Entry("Logging", "MaxLogFileSizeMB", "Maximum Log File Size", "日志文件最大大小", "Rotate structured CSV/JSONL files when they exceed this size.", "结构化 CSV/JSONL 文件超过此大小时进行轮转,单位 MB。"),
			Entry("Logging", "MaxRotatedFiles", "Maximum Rotated Files", "最大轮转文件数", "Maximum number of rotated CSV or JSONL files retained per format.", "每种格式最多保留的 CSV 或 JSONL 轮转文件数量。"),
			Entry("Logging", "MaxRotatedStorageMB", "Maximum Rotated Storage", "最大轮转存储空间", "Maximum total size of rotated CSV or JSONL files retained per format.", "每种格式最多保留的 CSV 或 JSONL 轮转文件总大小,单位 MB。"),
			Entry("MethodTracer", "TraceMethodNames", "Trace Method Names", "追踪方法名", "Comma-separated method full names to capture Environment.StackTrace (expensive; use only while diagnosing, e.g. 'Player.Update,ColdComponent.Apply'). Empty disables tracing.", "要捕获 Environment.StackTrace 的完整方法名,使用逗号分隔(开销较高,仅在诊断时启用,例如 'Player.Update,ColdComponent.Apply')。留空则关闭追踪。"),
			Entry("MethodTracer", "TraceMaxDepth", "Trace Maximum Depth", "追踪最大深度", "Maximum stack frames recorded per trace sample.", "每条追踪样本记录的最大堆栈帧数。"),
			Entry("MethodTracer", "TraceRateLimit", "Trace Rate Limit", "追踪速率限制", "Max trace records per method per second. Excess samples are dropped.", "每个方法每秒最多记录的追踪样本数。超出的样本会被丢弃。"),
			Entry("FieldProbe", "EnableFieldProbe", "Enable FieldProbe", "启用字段探针", "Enable FieldProbe: reflectively snapshot arbitrary fields / parameters / return values at any method via a JSON rules file. Zero overhead when disabled.", "启用字段探针:通过 JSON 规则文件反射读取任意方法的字段、参数和返回值。关闭时无开销。"),
			Entry("FieldProbe", "RulesFile", "Rules File", "规则文件", "Path to FieldProbe JSON rules file. Relative path resolves under BepInEx/config/. See sample file for schema.", "FieldProbe JSON 规则文件路径。相对路径以 BepInEx/config/ 为基准。规则格式请参阅示例文件。"),
			Entry("FieldProbe", "DefaultRateLimit", "Default Rate Limit", "默认速率限制", "Default max snapshots per rule per second when a rule doesn't specify its own rateLimit.", "规则未指定 rateLimit 时,每条规则每秒允许的默认最大快照数。"),
			Entry("FieldProbe", "DefaultMaxValueLen", "Default Maximum Value Length", "默认最大值长度", "Default max string length per snapshot value. Longer values are truncated with '...'.", "每个快照值的默认最大字符串长度。更长的值会截断为 '...'。"),
			Entry("FieldProbe", "DefaultIncludeStack", "Default Include Stack", "默认包含堆栈", "Default: whether to attach filtered call stack when a rule doesn't specify includeStack.", "规则未指定 includeStack 时,是否默认附加过滤后的调用堆栈。"),
			Entry("FieldProbe", "DefaultStackMaxDepth", "Default Stack Maximum Depth", "默认堆栈最大深度", "Default max stack frames captured per snapshot when includeStack is enabled.", "启用 includeStack 时,每个快照默认捕获的最大堆栈帧数。"),
			Entry("UI", "ShowDashboard", "Show Dashboard", "显示性能面板", "Show draggable on-screen performance dashboard. Zero cost when disabled.", "显示可拖动的屏幕性能面板。关闭时无开销。")
		};

		private static readonly TokenText[] Tokens = BuildTokens();

		private static FieldInfo _currentLanguageField;

		private static readonly ModConfigUiAdapter.ActionFieldSubscription LanguageSubscription = new ModConfigUiAdapter.ActionFieldSubscription();

		internal static IEnumerable<LocalizedConfigEntry> Entries => ConfigEntries;

		private static bool IsChinese
		{
			get
			{
				if (_currentLanguageField == null)
				{
					_currentLanguageField = AccessTools.TypeByName("LocalizedText")?.GetField("CURRENT_LANGUAGE", BindingFlags.Static | BindingFlags.Public);
				}
				string text = _currentLanguageField?.GetValue(null)?.ToString();
				if (!(text == "SimplifiedChinese"))
				{
					return text == "TraditionalChinese";
				}
				return true;
			}
		}

		public static void PatchDisplayNames(Harmony harmony)
		{
			ModConfigUiAdapter.Install(harmony, "com.wuyachiyu.WhySoLaggy.cfg", "WhySoLaggy", (ConfigEntryBase entry) => GetLocalizedConfigText(entry.Definition.Section, entry.Definition.Key, IsChinese), (string text) => GetLocalizedToken(FindCanonicalToken(text), IsChinese), delegate(string message)
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)message);
				}
			});
		}

		public static void RefreshVisibleUi()
		{
			ModConfigUiAdapter.RefreshVisibleUi();
		}

		public static void SubscribeLanguageChanged(Action handler)
		{
			try
			{
				Type type = AccessTools.TypeByName("LocalizedText");
				_currentLanguageField = type?.GetField("CURRENT_LANGUAGE", BindingFlags.Static | BindingFlags.Public);
				LanguageSubscription.Subscribe(type?.GetField("OnLangugageChanged", BindingFlags.Static | BindingFlags.Public), handler);
			}
			catch (Exception ex)
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Language subscription skipped: " + ex.Message));
				}
			}
		}

		public static void Shutdown()
		{
			LanguageSubscription.Dispose();
			ModConfigUiAdapter.Shutdown();
			_currentLanguageField = null;
		}

		private static bool IsOwnEntry(ConfigEntryBase entry)
		{
			return ModConfigUiAdapter.Owns(entry, "com.wuyachiyu.WhySoLaggy.cfg");
		}

		internal static bool IsOwnCategory(string category)
		{
			if (!ModConfigUiAdapter.SameMod(category, "WhySoLaggy"))
			{
				return category == "com.wuyachiyu.WhySoLaggy";
			}
			return true;
		}

		public static void ApplyLocalizedDescriptions(IEnumerable<ConfigEntryBase> entries)
		{
			if (entries == null)
			{
				return;
			}
			bool isChinese = IsChinese;
			foreach (ConfigEntryBase item in entries.Where((ConfigEntryBase value) => value != null))
			{
				if (IsOwnEntry(item))
				{
					string localizedDescription = GetLocalizedDescription(item.Definition.Section, item.Definition.Key, isChinese);
					SetDescription(item, localizedDescription);
				}
			}
		}

		internal static string GetLocalizedConfigText(string section, string key, bool chinese)
		{
			LocalizedConfigEntry localizedConfigEntry = FindEntry(section, key);
			if (localizedConfigEntry != null)
			{
				if (!chinese)
				{
					return localizedConfigEntry.EnglishName;
				}
				return localizedConfigEntry.ChineseName;
			}
			return null;
		}

		internal static string GetLocalizedDescription(string section, string key, bool chinese)
		{
			LocalizedConfigEntry localizedConfigEntry = FindEntry(section, key);
			if (localizedConfigEntry != null)
			{
				if (!chinese)
				{
					return localizedConfigEntry.EnglishDescription;
				}
				return localizedConfigEntry.ChineseDescription;
			}
			return null;
		}

		internal static string GetLocalizedCategoryText(string category, bool chinese)
		{
			string text = FindCanonicalToken(category);
			if (!string.IsNullOrEmpty(text))
			{
				return GetLocalizedToken(text, chinese);
			}
			return null;
		}

		private static string FindCanonicalToken(string text)
		{
			string text2 = Normalize(text);
			if (string.IsNullOrEmpty(text2))
			{
				return null;
			}
			TokenText[] tokens = Tokens;
			foreach (TokenText tokenText in tokens)
			{
				if (string.Equals(text2, Normalize(tokenText.English), StringComparison.OrdinalIgnoreCase) || string.Equals(text2, Normalize(tokenText.Chinese), StringComparison.OrdinalIgnoreCase) || string.Equals(text2, Normalize(tokenText.Key), StringComparison.OrdinalIgnoreCase))
				{
					return tokenText.Key;
				}
			}
			return null;
		}

		private static string GetLocalizedToken(string key, bool chinese)
		{
			TokenText[] tokens = Tokens;
			foreach (TokenText tokenText in tokens)
			{
				if (string.Equals(tokenText.Key, key, StringComparison.Ordinal))
				{
					if (!chinese)
					{
						return tokenText.English;
					}
					return tokenText.Chinese;
				}
			}
			return null;
		}

		private static TokenText[] BuildTokens()
		{
			List<TokenText> list = new List<TokenText>
			{
				Token("WhySoLaggy", "WhySoLaggy", "为什么这么卡"),
				Token("General", "General", "常规"),
				Token("AbuseDetection", "Abuse Detection", "滥用检测"),
				Token("RpcMonitor", "RPC Monitor", "RPC 监控"),
				Token("Logging", "Logging", "日志"),
				Token("MethodTracer", "Method Tracer", "方法追踪"),
				Token("FieldProbe", "FieldProbe", "字段探针"),
				Token("UI", "UI", "界面"),
				Token("Minimal", "Minimal", "最简"),
				Token("Normal", "Normal", "普通")
			};
			LocalizedConfigEntry[] configEntries = ConfigEntries;
			foreach (LocalizedConfigEntry entry in configEntries)
			{
				if (!list.Any((TokenText token) => token.Key == entry.Key))
				{
					list.Add(Token(entry.Key, entry.EnglishName, entry.ChineseName));
				}
			}
			return list.ToArray();
		}

		private static string Normalize(string value)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value.Replace(" ", string.Empty).Trim();
			}
			return string.Empty;
		}

		private static LocalizedConfigEntry FindEntry(string section, string key)
		{
			return ConfigEntries.FirstOrDefault((LocalizedConfigEntry entry) => string.Equals(entry.Section, section, StringComparison.Ordinal) && string.Equals(entry.Key, key, StringComparison.Ordinal));
		}

		private static LocalizedConfigEntry Entry(string section, string key, string englishName, string chineseName, string englishDescription, string chineseDescription)
		{
			return new LocalizedConfigEntry(section, key, englishName, chineseName, englishDescription, chineseDescription);
		}

		private static TokenText Token(string key, string english, string chinese)
		{
			return new TokenText(key, english, chinese);
		}

		private static void SetDescription(ConfigEntryBase entry, string text)
		{
			if (entry == null || entry.Description == null || DescriptionBackingField == null || string.IsNullOrEmpty(text))
			{
				return;
			}
			try
			{
				DescriptionBackingField.SetValue(entry.Description, text);
			}
			catch (Exception ex)
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[WHY_LAG] ModConfig description localization skipped: " + ex.Message));
				}
			}
		}
	}
	internal static class ModConfigUiAdapter
	{
		internal sealed class ActionFieldSubscription : IDisposable
		{
			private FieldInfo _field;

			private Action _handler;

			internal void Subscribe(FieldInfo field, Action handler)
			{
				if (!(field == null) && !(field.FieldType != typeof(Action)) && field.IsStatic && handler != null && (!(_field == field) || _handler != handler))
				{
					Dispose();
					field.SetValue(null, (Action)Delegate.Combine((Action)field.GetValue(null), handler));
					_field = field;
					_handler = handler;
				}
			}

			public void Dispose()
			{
				if (_field != null)
				{
					_field.SetValue(null, (Action)Delegate.Remove((Action)_field.GetValue(null), _handler));
				}
				_field = null;
				_handler = null;
			}
		}

		private const string Guid = "com.github.PEAKModding.PEAKLib.ModConfig";

		private const BindingFlags Members = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly HashSet<MethodBase> Patched = new HashSet<MethodBase>();

		private static readonly HashSet<string> Warnings = new HashSet<string>();

		private static readonly Dictionary<Tuple<Type, string>, MemberInfo> MemberCache = new Dictionary<Tuple<Type, string>, MemberInfo>();

		private static string _fileName;

		private static string _modName;

		private static Func<ConfigEntryBase, string> _name;

		private static Func<string, string> _token;

		private static Action<string> _warn;

		private static Type _inputCell;

		private static Type _row;

		private static Type _tab;

		private static MonoBehaviour _menu;

		private static Coroutine _refresh;

		internal static void Install(Harmony harmony, string fileName, string modName, Func<ConfigEntryBase, string> name, Func<string, string> token, Action<string> warn)
		{
			_fileName = fileName;
			_modName = modName;
			_name = name;
			_token = token;
			_warn = warn;
			if (harmony == null || !Chainloader.PluginInfos.TryGetValue("com.github.PEAKModding.PEAKLib.ModConfig", out var value) || (Object)(object)((value != null) ? value.Instance : null) == (Object)null)
			{
				return;
			}
			Assembly assembly = ((object)value.Instance).GetType().Assembly;
			foreach (Type item in from t in assembly.GetTypes()
				where t.FullName != null && t.FullName.StartsWith("PEAKLib.ModConfig.SettingOptions.BepInEx", StringComparison.Ordinal) && !t.ContainsGenericParameters && !t.IsAbstract
				select t)
			{
				Patch(harmony, DeclaredImplementation(item, "GetDisplayName", Type.EmptyTypes), "DisplayNamePostfix");
			}
			Type type = assembly.GetType("PEAKLib.ModConfig.Components.ModSettingsMenu") ?? assembly.GetType("PEAKLib.ModConfig.Components.ModdedSettingsMenu");
			if (type == null)
			{
				Warn("menu", "Unsupported ModConfig menu type.");
				return;
			}
			string[] array = new string[4] { "OnEnable", "ShowSettings", "SetSection", "UpdateSectionTabs" };
			foreach (string text in array)
			{
				Patch(harmony, DeclaredImplementation(type, text, (!(text == "SetSection") && !(text == "UpdateSectionTabs")) ? Type.EmptyTypes : new Type[1] { typeof(string) }), "MenuPostfix");
			}
			array = new string[2] { "OnDisable", "OnDestroy" };
			foreach (string name2 in array)
			{
				MethodInfo methodInfo = DeclaredImplementation(type, name2, Type.EmptyTypes);
				if (methodInfo != null)
				{
					Patch(harmony, methodInfo, "MenuClosedPostfix");
				}
			}
			_inputCell = AccessTools.TypeByName("Zorro.Settings.SettingInputUICell");
			_row = AccessTools.TypeByName("SettingsUICell");
			_tab = assembly.GetType("PEAKLib.ModConfig.Components.ModdedTABSButton");
			Type type2 = AccessTools.TypeByName("Zorro.Settings.UI.EnumSettingUI");
			Patch(harmony, DeclaredImplementation(type2, "RefreshText", Type.EmptyTypes), "ControlPostfix");
			foreach (Type item2 in from t in assembly.GetTypes()
				where _inputCell != null && _inputCell.IsAssignableFrom(t) && !t.IsAbstract
				select t)
			{
				foreach (MethodInfo item3 in from m in item2.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
					where m.Name == "Setup" && m.GetParameters().Length == 2
					select m)
				{
					Patch(harmony, item3, "ControlPostfix");
				}
			}
		}

		internal static MethodInfo DeclaredImplementation(Type type, string name, Type[] parameters)
		{
			while (type != null)
			{
				MethodInfo method = type.GetMethod(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, parameters, null);
				if (method != null)
				{
					if (!method.ContainsGenericParameters && !method.IsAbstract)
					{
						return method;
					}
					return null;
				}
				type = type.BaseType;
			}
			return null;
		}

		private static void Patch(Harmony harmony, MethodInfo method, string callback)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			if (method == null)
			{
				Warn(callback, "A ModConfig hook is unavailable: " + callback);
			}
			else if (!Patched.Contains(method))
			{
				try
				{
					harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(typeof(ModConfigUiAdapter), callback, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Patched.Add(method);
				}
				catch (Exception ex)
				{
					Warn(method.ToString(), "ModConfig hook skipped: " + method?.ToString() + ": " + ex.Message);
				}
			}
		}

		internal static bool Owns(ConfigEntryBase entry, string fileName)
		{
			if (((entry != null) ? entry.ConfigFile : null) != null && !string.IsNullOrEmpty(fileName))
			{
				return string.Equals(Path.GetFileName(entry.ConfigFile.ConfigFilePath), fileName, StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		internal static ConfigEntryBase Entry(object setting)
		{
			if (setting == null)
			{
				return null;
			}
			Type[] interfaces = setting.GetType().GetInterfaces();
			for (int i = 0; i < interfaces.Length; i++)
			{
				PropertyInfo property = interfaces[i].GetProperty("ConfigBase", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null && typeof(ConfigEntryBase).IsAssignableFrom(property.PropertyType))
				{
					object? value = property.GetValue(setting, null);
					return (ConfigEntryBase)((value is ConfigEntryBase) ? value : null);
				}
			}
			object obj = Read(setting, "ConfigBase");
			return (ConfigEntryBase)((obj is ConfigEntryBase) ? obj : null);
		}

		private static void DisplayNamePostfix(object __instance, ref string __result)
		{
			try
			{
				ConfigEntryBase val = Entry(__instance);
				if (Owns(val, _fileName))
				{
					string text = _name(val);
					if (!string.IsNullOrEmpty(text))
					{
						__result = text;
					}
				}
			}
			catch (Exception ex)
			{
				Warn("name", ex.Message);
			}
		}

		private static void MenuPostfix(MonoBehaviour __instance, MethodBase __originalMethod)
		{
			if ((Object)(object)_menu != (Object)(object)__instance || __originalMethod.Name == "OnEnable")
			{
				StopRefresh();
			}
			_menu = __instance;
			RefreshVisibleUi();
		}

		private static void MenuClosedPostfix(MonoBehaviour __instance)
		{
			if (!((Object)(object)_menu != (Object)(object)__instance))
			{
				StopRefresh();
				_menu = null;
			}
		}

		internal static void RefreshVisibleUi()
		{
			if ((Object)(object)_menu == (Object)null || !((Behaviour)_menu).isActiveAndEnabled)
			{
				StopRefresh();
				_menu = null;
			}
			else if (_refresh == null)
			{
				_refresh = _menu.StartCoroutine(DeferredRefresh(_menu));
			}
		}

		private static IEnumerator DeferredRefresh(MonoBehaviour owner)
		{
			yield return null;
			try
			{
				if (!((Object)(object)owner == (Object)null) && !((Object)(object)owner != (Object)(object)_menu) && ((Behaviour)owner).isActiveAndEnabled)
				{
					RefreshMenu(owner);
				}
			}
			finally
			{
				if ((Object)(object)owner == (Object)(object)_menu)
				{
					_refresh = null;
				}
			}
		}

		private static void RefreshMenu(MonoBehaviour owner)
		{
			try
			{
				RefreshTabs(owner, "ModTabController", sections: false);
				if (SameMod(Read(owner, "selectedMod") as string, _modName))
				{
					RefreshTabs(owner, "SectionTabController", sections: true);
				}
				object obj = Read(owner, "Content");
				Transform val = (Transform)((obj is Transform) ? obj : null);
				if (!((Object)(object)val == (Object)null) && !(_inputCell == null))
				{
					Component[] componentsInChildren = ((Component)val).GetComponentsInChildren(_inputCell, false);
					for (int i = 0; i < componentsInChildren.Length; i++)
					{
						RefreshControl(componentsInChildren[i]);
					}
				}
			}
			catch (Exception ex)
			{
				Warn("menu-refresh", ex.Message);
			}
		}

		internal static bool SameMod(string category, string modName)
		{
			if (!string.IsNullOrEmpty(category) && !string.IsNullOrEmpty(modName))
			{
				return string.Equals(category.Replace(" ", ""), modName.Replace(" ", ""), StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static void RefreshTabs(MonoBehaviour menu, string controllerName, bool sections)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			try
			{
				if (_tab == null || !(Read(Read(menu, controllerName), "Tabs") is IEnumerable enumerable))
				{
					return;
				}
				foreach (GameObject item in enumerable)
				{
					GameObject val = item;
					if (!((Object)(object)val == (Object)null))
					{
						Component component = val.GetComponent(_tab);
						string text = Read(component, "category") as string;
						if (sections || SameMod(text, _modName))
						{
							SetText(Read(component, "text"), _token(sections ? text : _modName));
						}
					}
				}
			}
			catch (Exception ex)
			{
				Warn("tabs", ex.Message);
			}
		}

		private static void ControlPostfix(Component __instance)
		{
			RefreshControl(__instance);
		}

		private static void RefreshControl(Component control)
		{
			try
			{
				if ((Object)(object)control == (Object)null || !control.gameObject.activeInHierarchy)
				{
					return;
				}
				object obj = Read(control, "KeySetting") ?? Read(control, "_listeningSetting");
				ConfigEntryBase val = Entry(obj);
				if (!Owns(val, _fileName))
				{
					return;
				}
				if (_row != null)
				{
					Component componentInParent = control.GetComponentInParent(_row);
					if ((Object)(object)componentInParent != (Object)null)
					{
						string text = _name(val);
						if (!string.IsNullOrEmpty(text))
						{
							object obj2 = Read(componentInParent, "localizedText");
							if (obj2 != null)
							{
								Write(obj2, "autoSet", false);
							}
							SetText(Read(componentInParent, "m_text"), text);
						}
					}
				}
				if (!val.SettingType.IsEnum && !(val.Description.AcceptableValues is AcceptableValueList<string>))
				{
					return;
				}
				object obj3 = Read(control, "dropdown");
				if (obj3 == null || !(Read(obj3, "options") is IList list) || !(DeclaredImplementation(obj.GetType(), "GetUnlocalizedChoices", Type.EmptyTypes)?.Invoke(obj, null) is IEnumerable<string> source))
				{
					return;
				}
				string[] array = source.ToArray();
				if (array.Length != list.Count)
				{
					return;
				}
				ApplyOptionText(list, array, _token);
				DeclaredImplementation(obj3.GetType(), "RefreshShownValue", Type.EmptyTypes)?.Invoke(obj3, null);
				if (Read(obj3, "m_Items") is IList list2 && list2.Count == array.Length)
				{
					for (int i = 0; i < list2.Count; i++)
					{
						SetText(Read(list2[i], "text"), Read(list[i], "text") as string);
					}
				}
			}
			catch (Exception ex)
			{
				Warn("control", ex.Message);
			}
		}

		internal static void ApplyOptionText(IList options, string[] raw, Func<string, string> translate)
		{
			if (options != null && raw != null && options.Count == raw.Length)
			{
				for (int i = 0; i < raw.Length; i++)
				{
					SetText(options[i], translate(raw[i]) ?? raw[i]);
				}
			}
		}

		private static void SetText(object target, string text)
		{
			if (target != null && !string.IsNullOrEmpty(text) && !object.Equals(Read(target, "text"), text))
			{
				Write(target, "text", text);
			}
		}

		private static MemberInfo Member(Type type, string name)
		{
			Tuple<Type, string> key = Tuple.Create(type, name);
			if (MemberCache.TryGetValue(key, out var value))
			{
				return value;
			}
			Type type2 = type;
			while (type2 != null)
			{
				value = (MemberInfo)(((object)type2.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) ?? ((object)type2.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)));
				if (value != null)
				{
					break;
				}
				type2 = type2.BaseType;
			}
			MemberCache[key] = value;
			return value;
		}

		private static object Read(object target, string name)
		{
			if (target == null)
			{
				return null;
			}
			MemberInfo memberInfo = Member(target.GetType(), name);
			if (!(memberInfo is PropertyInfo propertyInfo))
			{
				return (memberInfo as FieldInfo)?.