Decompiled source of WhySoLaggy v1.0.3

WhySoLaggy.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Concurrent;
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 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: ComVisible(false)]
[assembly: Guid("7a0d965a-ed09-40ae-9680-c8917710a150")]
[assembly: AssemblyFileVersion("1.0.3.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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;
			}
		}
	}
	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];
				if (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;
				}
				bool flag = i == 0 && ce.Root == RootKind.StaticType;
				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.Public | BindingFlags.NonPublic | (isStatic ? BindingFlags.Static : BindingFlags.Instance);
				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 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 @int = MiniJson.GetInt(dictionary, "rateLimitPerRule", DefaultRateLimit);
			int int2 = MiniJson.GetInt(dictionary, "maxValueLen", DefaultMaxValueLen);
			bool @bool = MiniJson.GetBool(dictionary, "includeStack", DefaultIncludeStack);
			int int3 = 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 @string = MiniJson.GetString(dictionary2, "target");
				if (string.IsNullOrEmpty(@string) || @string.IndexOf('.') <= 0)
				{
					ManualLogSource log8 = WhySoLaggyPlugin.Log;
					if (log8 != null)
					{
						log8.LogWarning((object)("[WHY_LAG] FieldProbe rule skipped: bad target '" + @string + "'"));
					}
					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): " + @string));
					}
					continue;
				}
				int num2 = @string.IndexOf('.');
				Rule rule = new Rule();
				num = (rule.Id = num + 1);
				rule.TargetTypeName = @string.Substring(0, num2);
				rule.TargetMethodName = @string.Substring(num2 + 1);
				rule.TargetKey = @string;
				rule.RateLimit = MiniJson.GetInt(dictionary2, "rateLimit", @int);
				rule.MaxValueLen = MiniJson.GetInt(dictionary2, "maxValueLen", int2);
				rule.IncludeStack = MiniJson.GetBool(dictionary2, "includeStack", @bool);
				rule.StackMaxDepth = MiniJson.GetInt(dictionary2, "stackMaxDepth", int3);
				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 text = item2 as string;
					if (string.IsNullOrEmpty(text))
					{
						continue;
					}
					text = text.Trim();
					ExpressionEvaluator.CompiledExpr compiledExpr = ExpressionEvaluator.Compile(text);
					if (!string.IsNullOrEmpty(compiledExpr.CompileError))
					{
						ManualLogSource log10 = WhySoLaggyPlugin.Log;
						if (log10 != null)
						{
							log10.LogWarning((object)("[WHY_LAG] FieldProbe compile err (" + @string + "): '" + text + "' → " + compiledExpr.CompileError));
						}
					}
					else
					{
						if (compiledExpr.Root == ExpressionEvaluator.RootKind.Result)
						{
							flag = true;
						}
						if (compiledExpr.Root == ExpressionEvaluator.RootKind.Exception)
						{
							flag2 = true;
						}
						list4.Add(text);
						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
			};
			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)
							{
								continue;
							}
							try
							{
								HarmonyMethod val5 = null;
								if (item3.NeedsPrefix)
								{
									val5 = val;
								}
								else if (item3.NeedsPostfix || item3.NeedsFinalizer)
								{
									val5 = val2;
								}
								harmony.Patch((MethodBase)methodInfo, val5, item3.NeedsPostfix ? val3 : null, (HarmonyMethod)null, item3.NeedsFinalizer ? val4 : null, (HarmonyMethod)null);
								if (!_methodToRules.TryGetValue(methodInfo, out var value4))
								{
									value4 = new List<Rule>();
									_methodToRules[methodInfo] = value4;
								}
								value4.Add(item3);
								_hookedRules++;
								num3++;
								string text2 = FormatSig(methodInfo);
								AbuseLogger.Write("[FIELD_PROBE] Hooked " + item3.TargetKey + text2 + " (mode=" + (item3.NeedsPostfix ? "Postfix" : (item3.NeedsFinalizer ? "Finalizer" : "Prefix")) + ", " + $"fields={item3.Compiled.Length}, rate={item3.RateLimit}/s, stack={item3.IncludeStack})");
							}
							catch (Exception ex2)
							{
								ManualLogSource log12 = WhySoLaggyPlugin.Log;
								if (log12 != null)
								{
									log12.LogWarning((object)("[WHY_LAG] FieldProbe Patch failed on " + item3.TargetKey + FormatSig(methodInfo) + ": " + ex2.Message));
								}
							}
						}
						if (num3 == 0)
						{
							ManualLogSource log13 = WhySoLaggyPlugin.Log;
							if (log13 != null)
							{
								log13.LogInfo((object)("[WHY_LAG] FieldProbe: type " + name + " matched in asm " + assembly.GetName().Name + " but no method '" + item3.TargetMethodName + "' found"));
							}
						}
					}
				}
			}
			AbuseLogger.Write($"[FIELD_PROBE] Initialized (rules={list2.Count}, hooks={_hookedRules}, 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
			{
				StructuredEvent evt = default(StructuredEvent);
				evt.Timestamp = StructuredLogger.NowStamp();
				evt.FrameNumber = Time.frameCount;
				evt.Type = EventType.MethodTrace;
				evt.Fields = new Dictionary<string, object>
				{
					{ "TargetMethod", rule.TargetKey },
					{
						"Snapshot",
						Truncate(stringBuilder.ToString(), 2000)
					},
					{
						"TraceStack",
						text ?? ""
					},
					{
						"TraceCaller",
						text2 ?? ""
					},
					{
						"PatchType",
						kindToStr(rule)
					}
				};
				StructuredLogger.WriteEvent(evt);
			}
			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) + "...";
		}
	}
	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 num2 = (CurrentFrameMs = unscaledDeltaTime * 1000f);
			_frameTimes[_writeIndex] = num2;
			_writeIndex = (_writeIndex + 1) % 600;
			if (_sampleCount < 600)
			{
				_sampleCount++;
			}
			IsSpikeFrame = num2 > (float)SpikeThresholdMs;
			if (IsSpikeFrame)
			{
				_periodSpikeCount++;
			}
			_periodFrames++;
			_periodSumMs += num2;
			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
			{
				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)
					}
				};
				if (EnableMemoryMonitor)
				{
					dictionary["AllocRateKBps"] = Math.Round(_memLastRateKBps, 2);
				}
				StructuredEvent evt = default(StructuredEvent);
				evt.Timestamp = StructuredLogger.NowStamp();
				evt.FrameNumber = Time.frameCount;
				evt.Type = EventType.FpsReport;
				evt.Fields = dictionary;
				StructuredLogger.WriteEvent(evt);
			}
			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;
		}
	}
	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
					{
						StructuredEvent evt = default(StructuredEvent);
						evt.Timestamp = StructuredLogger.NowStamp();
						evt.FrameNumber = Time.frameCount;
						evt.Type = EventType.HarmonyPatchMap;
						evt.Fields = new Dictionary<string, object>
						{
							{ "TargetMethod", target },
							{ "PatchType", kind },
							{ "OwnerHarmonyId", text },
							{ "Priority", item.priority }
						};
						StructuredLogger.WriteEvent(evt);
					}
					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 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 = (__originalMethod.DeclaringType?.Name ?? "?") + "." + __originalMethod.Name;
			if (!CheckRateLimit(text))
			{
				return;
			}
			string stackTrace;
			try
			{
				stackTrace = Environment.StackTrace;
			}
			catch
			{
				return;
			}
			string text2 = FilterStack(stackTrace, TraceMaxDepth);
			string text3 = ExtractFirstFrame(text2);
			try
			{
				StructuredEvent evt = default(StructuredEvent);
				evt.Timestamp = StructuredLogger.NowStamp();
				evt.FrameNumber = Time.frameCount;
				evt.Type = EventType.MethodTrace;
				evt.Fields = new Dictionary<string, object>
				{
					{ "TargetMethod", text },
					{
						"TraceStack",
						Truncate(text2, 1200)
					},
					{
						"TraceCaller",
						text3 ?? ""
					}
				};
				StructuredLogger.WriteEvent(evt);
			}
			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) + "...";
		}
	}
	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 NetworkAbuseDetector
	{
		private struct ClientRpcRecord
		{
			public float Time;

			public int Actor;

			public string Method;
		}

		public static int InstantiateRateThreshold = 15;

		public static int DestroyRateThreshold = 20;

		public static int RpcRateThreshold = 50;

		public static int ObjectSpikeThreshold = 30;

		public static float CheckIntervalSeconds = 1f;

		public static float ReportIntervalSeconds = 30f;

		private const byte EventInstantiate = 202;

		private const byte EventRpc = 200;

		private const byte EventDestroy = 204;

		private const byte EventDestroyPlayer = 207;

		private const byte EventOwnershipRequest = 210;

		private const byte EventOwnershipTransfer = 211;

		private const byte EventOwnershipUpdate = 215;

		private static readonly object _lock = new object();

		private static int _localInstantiateCount;

		private static int _localDestroyCount;

		private static int _localRpcCount;

		private static int _remoteInstantiateCount;

		private static int _remoteDestroyCount;

		private static int _remoteRpcCount;

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

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

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

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

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

		public static int ActorMethodRateThreshold = 20;

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

		public static int OwnershipGrabRateThreshold = 10;

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

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

		private static float _lastTraceSampleTime;

		private const int TraceFirstNPerPrefab = 3;

		private const float TraceSampleWindowSeconds = 5f;

		private static bool _forceTraceNext;

		private static readonly LinkedList<ClientRpcRecord> _recentClientRpcs = new LinkedList<ClientRpcRecord>();

		private const float SuspectWindowSeconds = 2.5f;

		private const int SuspectWindowMaxEntries = 256;

		private static int _lastPhotonViewCount;

		private static int _lastZombieCount;

		private static Type _zombieType;

		private static float _checkTimer;

		private static float _reportTimer;

		private static bool _initialized;

		private static int _totalInstantiates;

		private static int _totalDestroys;

		private static int _totalRpcs;

		private static int _alertCount;

		private static bool IsChinese
		{
			get
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: Invalid comparison between Unknown and I4
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: Invalid comparison between Unknown and I4
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0018: Invalid comparison between Unknown and I4
				if ((int)Application.systemLanguage != 6 && (int)Application.systemLanguage != 40)
				{
					return (int)Application.systemLanguage == 41;
				}
				return true;
			}
		}

		public static void Initialize(Harmony harmony)
		{
			if (_initialized)
			{
				return;
			}
			try
			{
				PatchPhotonMethods(harmony);
				FindGameTypes();
				_lastPhotonViewCount = CountPhotonViews();
				_lastZombieCount = CountZombies();
				_initialized = true;
				AbuseLogger.Info("[ABUSE] NetworkAbuseDetector initialized (observation-only mode)");
				AbuseLogger.Info($"[ABUSE] Thresholds: Instantiate={InstantiateRateThreshold}/s, Destroy={DestroyRateThreshold}/s, RPC={RpcRateThreshold}/s, ObjectSpike={ObjectSpikeThreshold}");
				AbuseLogger.Info($"[ABUSE] Initial counts: PhotonViews={_lastPhotonViewCount}, Zombies={_lastZombieCount}");
			}
			catch (Exception arg)
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogError((object)$"[WHY_LAG] NetworkAbuseDetector init failed: {arg}");
				}
			}
		}

		public static void OnNetworkEvent(byte eventCode, int senderActorNumber)
		{
			if (!_initialized || (PhotonNetwork.LocalPlayer != null && senderActorNumber == PhotonNetwork.LocalPlayer.ActorNumber))
			{
				return;
			}
			lock (_lock)
			{
				switch (eventCode)
				{
				case 202:
					_remoteInstantiateCount++;
					_totalInstantiates++;
					IncrementActor(_instantiateByActor, senderActorNumber);
					break;
				case 200:
					_remoteRpcCount++;
					_totalRpcs++;
					IncrementActor(_rpcByActor, senderActorNumber);
					break;
				case 204:
				case 207:
					_remoteDestroyCount++;
					_totalDestroys++;
					IncrementActor(_destroyByActor, senderActorNumber);
					break;
				}
			}
		}

		public static void OnRemoteRpcEvent(EventData photonEvent)
		{
			if (!_initialized || photonEvent.Code != 200)
			{
				return;
			}
			int sender = photonEvent.Sender;
			try
			{
				if (PhotonNetwork.LocalPlayer != null && sender == PhotonNetwork.LocalPlayer.ActorNumber)
				{
					return;
				}
			}
			catch
			{
			}
			string text = null;
			int num = 0;
			int num2 = 0;
			try
			{
				object customData = photonEvent.CustomData;
				Hashtable val = (Hashtable)((customData is Hashtable) ? customData : null);
				if (val != null)
				{
					if (val.ContainsKey((byte)0) && val[(byte)0] is int num3)
					{
						num = num3;
					}
					if (val.ContainsKey((byte)3))
					{
						text = val[(byte)3] as string;
					}
					if (string.IsNullOrEmpty(text) && val.ContainsKey((byte)5))
					{
						object obj2 = val[(byte)5];
						try
						{
							List<string> list = PhotonNetwork.PhotonServerSettings?.RpcList;
							if (list != null && obj2 is byte)
							{
								byte b = (byte)obj2;
								if (b < list.Count)
								{
									text = list[b];
								}
							}
						}
						catch
						{
						}
					}
					if (val.ContainsKey((byte)4) && val[(byte)4] is object[] array)
					{
						num2 = array.Length;
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[WHY_LAG] OnRemoteRpcEvent unpack failed: " + ex.Message));
				}
			}
			if (!string.IsNullOrEmpty(text))
			{
				lock (_lock)
				{
					_recentClientRpcs.AddLast(new ClientRpcRecord
					{
						Time = Time.realtimeSinceStartup,
						Actor = sender,
						Method = text
					});
					if (_recentClientRpcs.Count > 256)
					{
						_recentClientRpcs.RemoveFirst();
					}
					if (!_rpcByActorMethod.TryGetValue(sender, out var value))
					{
						value = new Dictionary<string, int>(StringComparer.Ordinal);
						_rpcByActorMethod[sender] = value;
					}
					value.TryGetValue(text, out var value2);
					value[text] = value2 + 1;
					if (!_rpcByActorMethodTotal.TryGetValue(sender, out var value3))
					{
						value3 = new Dictionary<string, int>(StringComparer.Ordinal);
						_rpcByActorMethodTotal[sender] = value3;
					}
					value3.TryGetValue(text, out var value4);
					value3[text] = value4 + 1;
				}
			}
			if (string.IsNullOrEmpty(text) || !RpcMonitor.WatchedMethods.Contains(text))
			{
				return;
			}
			try
			{
				string text2 = null;
				try
				{
					if (PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.Players != null && PhotonNetwork.CurrentRoom.Players.TryGetValue(sender, out var value5))
					{
						text2 = ((value5 != null) ? value5.NickName : null);
					}
				}
				catch
				{
				}
				Dictionary<string, object> fields = new Dictionary<string, object>
				{
					{ "RpcMethod", text },
					{ "SenderActor", sender },
					{
						"SenderName",
						text2 ?? "<unknown>"
					},
					{ "TargetViewID", num },
					{
						"IsMasterClient",
						PhotonNetwork.IsMasterClient ? 1 : 0
					},
					{
						"ArgsSummary",
						$"params={num2}"
					}
				};
				StructuredEvent evt = default(StructuredEvent);
				evt.Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
				evt.FrameNumber = Time.frameCount;
				evt.Type = EventType.RemoteRpcTrace;
				evt.Fields = fields;
				StructuredLogger.WriteEvent(evt);
			}
			catch (Exception ex2)
			{
				ManualLogSource log2 = WhySoLaggyPlugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[WHY_LAG] RemoteRpcTrace emit failed: " + ex2.Message));
				}
			}
		}

		private static bool TryFindRecentClientRpc(float now, out int actor, out string method, out int ageMs)
		{
			actor = -1;
			method = null;
			ageMs = -1;
			lock (_lock)
			{
				while (_recentClientRpcs.Count > 0 && now - _recentClientRpcs.First.Value.Time > 2.5f)
				{
					_recentClientRpcs.RemoveFirst();
				}
				if (_recentClientRpcs.Count == 0)
				{
					return false;
				}
				ClientRpcRecord value = _recentClientRpcs.Last.Value;
				actor = value.Actor;
				method = value.Method;
				ageMs = (int)((now - value.Time) * 1000f);
				return true;
			}
		}

		public static void OnOwnershipEvent(EventData photonEvent)
		{
			if (!_initialized)
			{
				return;
			}
			byte code = photonEvent.Code;
			if (code != 210 && code != 211 && code != 215)
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			try
			{
				if (photonEvent.CustomData is int[] array && array.Length >= 2)
				{
					num = array[0];
					num2 = array[1];
				}
			}
			catch
			{
			}
			int sender = photonEvent.Sender;
			string text = code switch
			{
				211 => "Transfer", 
				210 => "Request", 
				_ => "Update", 
			};
			int num3 = ((code == 211 || code == 215) ? num2 : sender);
			if (num3 > 0)
			{
				lock (_lock)
				{
					_ownershipGrabbedByActor.TryGetValue(num3, out var value);
					_ownershipGrabbedByActor[num3] = value + 1;
				}
			}
			try
			{
				Dictionary<string, object> dictionary = new Dictionary<string, object>
				{
					{
						"RpcMethod",
						"Ownership" + text
					},
					{ "SenderActor", sender },
					{ "TargetViewID", num },
					{
						"ArgsSummary",
						$"otherActor={num2}"
					}
				};
				try
				{
					if (PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.Players != null && PhotonNetwork.CurrentRoom.Players.TryGetValue(sender, out var value2))
					{
						dictionary["SenderName"] = ((value2 != null) ? value2.NickName : null);
					}
				}
				catch
				{
				}
				StructuredEvent evt = default(StructuredEvent);
				evt.Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
				evt.FrameNumber = Time.frameCount;
				evt.Type = EventType.OwnershipChange;
				evt.Fields = dictionary;
				StructuredLogger.WriteEvent(evt);
			}
			catch (Exception ex)
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[WHY_LAG] OwnershipChange emit failed: " + ex.Message));
				}
			}
		}

		public static void Tick()
		{
			if (_initialized)
			{
				float unscaledDeltaTime = Time.unscaledDeltaTime;
				_checkTimer += unscaledDeltaTime;
				_reportTimer += unscaledDeltaTime;
				if (_checkTimer >= CheckIntervalSeconds)
				{
					CheckRates();
					CheckObjectSpike();
					CheckActorMethodHotspots();
					CheckOwnershipGrab();
					ResetCounters();
					RpcMonitor.OnWindowEnd();
					_checkTimer = 0f;
				}
				if (_reportTimer >= ReportIntervalSeconds)
				{
					WritePeriodicReport();
					ResetReportStats();
					_reportTimer = 0f;
				}
			}
		}

		private static void PatchPhotonMethods(Harmony harmony)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Expected O, but got Unknown
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Expected O, but got Unknown
			MethodInfo[] methods = typeof(PhotonNetwork).GetMethods(BindingFlags.Static | BindingFlags.Public);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name == "Instantiate" || methodInfo.Name == "InstantiateRoomObject")
				{
					try
					{
						harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(NetworkAbuseDetector), "OnInstantiatePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					}
					catch (Exception ex)
					{
						AbuseLogger.Info("[ABUSE] Failed to patch " + methodInfo.Name + ": " + ex.Message);
					}
				}
			}
			methods = typeof(PhotonNetwork).GetMethods(BindingFlags.Static | BindingFlags.Public);
			foreach (MethodInfo methodInfo2 in methods)
			{
				if (methodInfo2.Name == "Destroy")
				{
					try
					{
						harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(NetworkAbuseDetector), "OnDestroyPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					}
					catch (Exception ex2)
					{
						AbuseLogger.Info("[ABUSE] Failed to patch Destroy: " + ex2.Message);
					}
				}
			}
			methods = typeof(PhotonView).GetMethods(BindingFlags.Instance | BindingFlags.Public);
			foreach (MethodInfo methodInfo3 in methods)
			{
				if (methodInfo3.Name == "RPC")
				{
					try
					{
						harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(NetworkAbuseDetector), "OnRpcPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					}
					catch (Exception ex3)
					{
						AbuseLogger.Info("[ABUSE] Failed to patch RPC: " + ex3.Message);
					}
				}
			}
			AbuseLogger.Info("[ABUSE] Photon method hooks registered (local API tracking)");
		}

		private static void FindGameTypes()
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				if (_zombieType != null)
				{
					break;
				}
				try
				{
					Type[] types = assembly.GetTypes();
					foreach (Type type in types)
					{
						if (type.Name == "MushroomZombie")
						{
							_zombieType = type;
							AbuseLogger.Info("[ABUSE] Found zombie type: " + type.FullName);
							break;
						}
					}
				}
				catch (Exception ex)
				{
					ManualLogSource log = WhySoLaggyPlugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("[WHY_LAG] FindGameTypes scan failed in asm=" + assembly.GetName().Name + ": " + ex.Message));
					}
				}
			}
			if (_zombieType == null)
			{
				AbuseLogger.Info("[ABUSE] MushroomZombie type not found (zombie counting disabled)");
			}
		}

		private static void OnInstantiatePrefix(string prefabName, Vector3 position)
		{
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			lock (_lock)
			{
				_localInstantiateCount++;
				_totalInstantiates++;
				_ = _totalInstantiates;
				if (!string.IsNullOrEmpty(prefabName))
				{
					if (!_prefabCount.ContainsKey(prefabName))
					{
						_prefabCount[prefabName] = 0;
					}
					_prefabCount[prefabName]++;
					_prefabTraceTaken.TryGetValue(prefabName, out var value);
					if (value < 3)
					{
						flag = true;
						_prefabTraceTaken[prefabName] = value + 1;
					}
					else if (_forceTraceNext)
					{
						flag = true;
						_forceTraceNext = false;
					}
					else if (Time.realtimeSinceStartup - _lastTraceSampleTime >= 5f)
					{
						flag = true;
						_lastTraceSampleTime = Time.realtimeSinceStartup;
					}
				}
			}
			int num;
			bool flag2;
			try
			{
				num = ((PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : (-1));
				flag2 = PhotonNetwork.IsMasterClient;
			}
			catch
			{
				num = -1;
				flag2 = false;
			}
			string traceOut = null;
			string callerOut = null;
			if (flag)
			{
				ExtractCallerStack(out traceOut, out callerOut);
			}
			try
			{
				Dictionary<string, object> dictionary = new Dictionary<string, object>
				{
					{
						"PrefabName",
						prefabName ?? "<null>"
					},
					{
						"IsMasterClient",
						flag2 ? 1 : 0
					},
					{
						"Position",
						$"{position.x:F1},{position.y:F1},{position.z:F1}"
					},
					{ "LocalActor", num }
				};
				if (!string.IsNullOrEmpty(traceOut))
				{
					dictionary["TraceStack"] = traceOut;
				}
				if (!string.IsNullOrEmpty(callerOut))
				{
					dictionary["TraceCaller"] = callerOut;
				}
				if (flag2 && TryFindRecentClientRpc(Time.realtimeSinceStartup, out var actor, out var method, out var ageMs))
				{
					dictionary["SuspectedRequesterActor"] = actor;
					dictionary["SuspectedRequesterRpc"] = method ?? "<?>";
					dictionary["SuspectedAgeMs"] = ageMs;
					try
					{
						if (PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.Players != null && PhotonNetwork.CurrentRoom.Players.TryGetValue(actor, out var value2))
						{
							dictionary["SuspectedRequesterName"] = ((value2 != null) ? value2.NickName : null) ?? "<unknown>";
						}
					}
					catch
					{
					}
				}
				StructuredEvent evt = default(StructuredEvent);
				evt.Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
				evt.FrameNumber = Time.frameCount;
				evt.Type = EventType.InstantiateTrace;
				evt.Fields = dictionary;
				StructuredLogger.WriteEvent(evt);
			}
			catch (Exception ex)
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[WHY_LAG] InstantiateTrace emit failed: " + ex.Message));
				}
			}
		}

		private static void ExtractCallerStack(out string traceOut, out string callerOut)
		{
			traceOut = null;
			callerOut = null;
			try
			{
				StackTrace stackTrace = new StackTrace(2, fNeedFileInfo: false);
				int frameCount = stackTrace.FrameCount;
				StringBuilder stringBuilder = new StringBuilder();
				int num = 0;
				for (int i = 0; i < frameCount; i++)
				{
					if (num >= 12)
					{
						break;
					}
					MethodBase method = stackTrace.GetFrame(i).GetMethod();
					if (method == null)
					{
						continue;
					}
					Type declaringType = method.DeclaringType;
					string text = ((declaringType != null) ? declaringType.FullName : "<?>");
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append(" | ");
					}
					stringBuilder.Append(text).Append('.').Append(method.Name);
					num++;
					if (callerOut == null && declaringType != null)
					{
						string text2 = declaringType.Namespace ?? string.Empty;
						if (!text2.StartsWith("Photon") && !text2.StartsWith("HarmonyLib") && !text2.StartsWith("UnityEngine") && !text2.StartsWith("System") && text2 != "WhySoLaggy")
						{
							callerOut = text + "." + method.Name;
						}
					}
				}
				traceOut = stringBuilder.ToString();
			}
			catch
			{
			}
		}

		public static void ForceTraceNextInstantiate()
		{
			_forceTraceNext = true;
		}

		private static void OnDestroyPrefix()
		{
			lock (_lock)
			{
				_localDestroyCount++;
				_totalDestroys++;
			}
		}

		private static void OnRpcPrefix(PhotonView __instance, string methodName)
		{
			lock (_lock)
			{
				_localRpcCount++;
				_totalRpcs++;
			}
		}

		private static void CheckRates()
		{
			float checkIntervalSeconds = CheckIntervalSeconds;
			int num;
			int num2;
			int num3;
			Dictionary<int, int> dictionary;
			Dictionary<int, int> dictionary2;
			Dictionary<int, int> dictionary3;
			lock (_lock)
			{
				num = _localInstantiateCount + _remoteInstantiateCount;
				num2 = _localDestroyCount + _remoteDestroyCount;
				num3 = _localRpcCount + _remoteRpcCount;
				dictionary = new Dictionary<int, int>(_instantiateByActor);
				dictionary2 = new Dictionary<int, int>(_rpcByActor);
				dictionary3 = new Dictionary<int, int>(_destroyByActor);
			}
			float num4 = (float)num / checkIntervalSeconds;
			float num5 = (float)num2 / checkIntervalSeconds;
			float num6 = (float)num3 / checkIntervalSeconds;
			if (num4 >= (float)InstantiateRateThreshold)
			{
				_alertCount++;
				AbuseLogger.Alert($"Instantiate flood! Rate: {num4:F1}/s (threshold: {InstantiateRateThreshold}/s)");
				AbuseNotificationUI.Show(IsChinese ? $"⚠ 刷物体洪水!速率: {num4:F1}/秒(阈值: {InstantiateRateThreshold}/秒)" : $"⚠ Instantiate flood! Rate: {num4:F1}/s (threshold: {InstantiateRateThreshold}/s)");
				LogTopActors(dictionary, "Instantiate");
				LogTopPrefabs();
				EmitAbuseAlertEvent("InstantiateFlood", num4, InstantiateRateThreshold, dictionary);
				ForceTraceNextInstantiate();
			}
			if (num5 >= (float)DestroyRateThreshold)
			{
				_alertCount++;
				AbuseLogger.Alert($"Destroy flood! Rate: {num5:F1}/s (threshold: {DestroyRateThreshold}/s)");
				AbuseNotificationUI.Show(IsChinese ? $"⚠ 大量销毁!速率: {num5:F1}/秒(阈值: {DestroyRateThreshold}/秒)" : $"⚠ Destroy flood! Rate: {num5:F1}/s (threshold: {DestroyRateThreshold}/s)");
				LogTopActors(dictionary3, "Destroy");
				EmitAbuseAlertEvent("DestroyFlood", num5, DestroyRateThreshold, dictionary3);
			}
			if (num6 >= (float)RpcRateThreshold)
			{
				_alertCount++;
				AbuseLogger.Alert($"RPC flood! Rate: {num6:F1}/s (threshold: {RpcRateThreshold}/s)");
				AbuseNotificationUI.Show(IsChinese ? $"⚠ RPC洪水!速率: {num6:F1}/秒(阈值: {RpcRateThreshold}/秒)" : $"⚠ RPC flood! Rate: {num6:F1}/s (threshold: {RpcRateThreshold}/s)");
				LogTopActors(dictionary2, "RPC");
				RpcMonitor.LogCurrentWindowTopMethods(5);
				EmitAbuseAlertEvent("RpcFlood", num6, RpcRateThreshold, dictionary2);
			}
		}

		private static void EmitAbuseAlertEvent(string alertType, float rate, int threshold, Dictionary<int, int> actorMap)
		{
			try
			{
				int num = -1;
				int num2 = 0;
				if (actorMap != null)
				{
					foreach (KeyValuePair<int, int> item in actorMap)
					{
						if (item.Value > num2)
						{
							num = item.Key;
							num2 = item.Value;
						}
					}
				}
				Dictionary<string, object> dictionary = new Dictionary<string, object>
				{
					{ "AlertType", alertType },
					{
						"Rate",
						Math.Round(rate, 2)
					},
					{ "Threshold", threshold }
				};
				if (num >= 0)
				{
					dictionary["TopActor"] = num;
					dictionary["TopActorName"] = ResolvePlayerName(num);
				}
				try
				{
					dictionary["Ping"] = PhotonNetwork.GetPing();
				}
				catch
				{
				}
				StructuredEvent evt = default(StructuredEvent);
				evt.Timestamp = StructuredLogger.NowStamp();
				evt.FrameNumber = Time.frameCount;
				evt.Type = EventType.AbuseAlert;
				evt.Fields = dictionary;
				StructuredLogger.WriteEvent(evt);
			}
			catch (Exception ex)
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[WHY_LAG] NetworkAbuseDetector EmitAbuseAlert failed: " + ex.Message));
				}
			}
		}

		private static void CheckActorMethodHotspots()
		{
			List<(int, string, int)> list = null;
			lock (_lock)
			{
				foreach (KeyValuePair<int, Dictionary<string, int>> item in _rpcByActorMethod)
				{
					foreach (KeyValuePair<string, int> item2 in item.Value)
					{
						if (item2.Value >= ActorMethodRateThreshold)
						{
							if (list == null)
							{
								list = new List<(int, string, int)>(4);
							}
							list.Add((item.Key, item2.Key, item2.Value));
						}
					}
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (var (num, text, num2) in list)
			{
				_alertCount++;
				string text2 = TryGetNickName(num);
				AbuseLogger.Alert($"Actor×Method hotspot! Actor #{num} ({text2}) sent '{text}' x{num2} in {CheckIntervalSeconds:F1}s (threshold: {ActorMethodRateThreshold})");
				AbuseNotificationUI.Show(IsChinese ? $"⚠ 客户端 RPC 热点!#{num} {text2} {text} ×{num2}" : $"⚠ Actor×Method hotspot! #{num} {text2} {text} ×{num2}");
				try
				{
					StructuredEvent evt = default(StructuredEvent);
					evt.Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
					evt.FrameNumber = Time.frameCount;
					evt.Type = EventType.AbuseAlert;
					evt.Fields = new Dictionary<string, object>
					{
						{ "AlertType", "ActorMethodHotspot" },
						{ "TopActor", num },
						{ "TopActorName", text2 },
						{ "RpcMethod", text },
						{ "CurrentCount", num2 },
						{ "Threshold", ActorMethodRateThreshold }
					};
					StructuredLogger.WriteEvent(evt);
				}
				catch
				{
				}
			}
		}

		private static void CheckOwnershipGrab()
		{
			List<(int, int)> list = null;
			lock (_lock)
			{
				foreach (KeyValuePair<int, int> item in _ownershipGrabbedByActor)
				{
					if (item.Value >= OwnershipGrabRateThreshold)
					{
						if (list == null)
						{
							list = new List<(int, int)>(2);
						}
						list.Add((item.Key, item.Value));
					}
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (var (num, num2) in list)
			{
				_alertCount++;
				string text = TryGetNickName(num);
				AbuseLogger.Alert($"Ownership grab! Actor #{num} ({text}) took {num2} PhotonView ownerships in {CheckIntervalSeconds:F1}s (threshold: {OwnershipGrabRateThreshold})");
				AbuseNotificationUI.Show(IsChinese ? $"⚠ 所有权抢夺!#{num} {text} ■{num2}" : $"⚠ Ownership grab! #{num} {text} ×{num2}");
				try
				{
					StructuredEvent evt = default(StructuredEvent);
					evt.Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
					evt.FrameNumber = Time.frameCount;
					evt.Type = EventType.AbuseAlert;
					evt.Fields = new Dictionary<string, object>
					{
						{ "AlertType", "OwnershipGrab" },
						{ "TopActor", num },
						{ "TopActorName", text },
						{ "CurrentCount", num2 },
						{ "Threshold", OwnershipGrabRateThreshold }
					};
					StructuredLogger.WriteEvent(evt);
				}
				catch
				{
				}
			}
		}

		private static string TryGetNickName(int actor)
		{
			try
			{
				if (PhotonNetwork.CurrentRoom != null && PhotonNetwork.CurrentRoom.Players != null && PhotonNetwork.CurrentRoom.Players.TryGetValue(actor, out var value))
				{
					return ((value != null) ? value.NickName : null) ?? "?";
				}
			}
			catch
			{
			}
			return "?";
		}

		private static void CheckObjectSpike()
		{
			int num = CountPhotonViews();
			int num2 = CountZombies();
			int num3 = num - _lastPhotonViewCount;
			int num4 = num2 - _lastZombieCount;
			if (num3 >= ObjectSpikeThreshold)
			{
				_alertCount++;
				AbuseLogger.Alert($"PhotonView spike! +{num3} in {CheckIntervalSeconds}s (now: {num})");
				AbuseNotificationUI.Show(IsChinese ? $"⚠ 对象突增!+{num3} 个/{CheckIntervalSeconds}秒(当前: {num})" : $"⚠ PhotonView spike! +{num3} in {CheckIntervalSeconds}s (now: {num})");
				string topOwners = InvestigatePhotonViewOwners();
				EmitSpikeEvent("PhotonViewSpike", num3, num, topOwners);
			}
			if (num4 >= ObjectSpikeThreshold)
			{
				_alertCount++;
				AbuseLogger.Alert($"Zombie spike! +{num4} in {CheckIntervalSeconds}s (now: {num2})");
				AbuseNotificationUI.Show(IsChinese ? $"⚠ 僵尸突增!+{num4} 个/{CheckIntervalSeconds}秒(当前: {num2})" : $"⚠ Zombie spike! +{num4} in {CheckIntervalSeconds}s (now: {num2})");
				EmitSpikeEvent("ZombieSpike", num4, num2);
			}
			_lastPhotonViewCount = num;
			_lastZombieCount = num2;
		}

		private static int CountPhotonViews()
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				int num = 0;
				ValueIterator<int, PhotonView> enumerator = PhotonNetwork.PhotonViewCollection.GetEnumerator();
				try
				{
					while (enumerator.MoveNext())
					{
						_ = enumerator.Current;
						num++;
					}
				}
				finally
				{
					((IDisposable)enumerator).Dispose();
				}
				return num;
			}
			catch
			{
				return 0;
			}
		}

		private static int CountZombies()
		{
			if (_zombieType == null)
			{
				return 0;
			}
			try
			{
				Object[] array = Object.FindObjectsByType(_zombieType, (FindObjectsSortMode)0);
				return (array != null) ? array.Length : 0;
			}
			catch
			{
				return 0;
			}
		}

		private static string InvestigatePhotonViewOwners()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Dictionary<string, int> dictionary = new Dictionary<string, int>();
				ValueIterator<int, PhotonView> enumerator = PhotonNetwork.PhotonViewCollection.GetEnumerator();
				try
				{
					while (enumerator.MoveNext())
					{
						PhotonView current = enumerator.Current;
						string key = "Scene";
						if ((Object)(object)current != (Object)null && current.Owner != null)
						{
							key = string.Format("{0}#{1}", current.Owner.NickName ?? "?", current.Owner.ActorNumber);
						}
						if (!dictionary.ContainsKey(key))
						{
							dictionary[key] = 0;
						}
						dictionary[key]++;
					}
				}
				finally
				{
					((IDisposable)enumerator).Dispose();
				}
				AbuseLogger.Write("[ABUSE]   PhotonView owner distribution:");
				List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(dictionary);
				list.Sort((KeyValuePair<string, int> a, KeyValuePair<string, int> b) => b.Value.CompareTo(a.Value));
				foreach (KeyValuePair<string, int> item in list)
				{
					AbuseLogger.WriteRaw($"          {item.Key}: {item.Value} objects");
				}
				if (list.Count > 0 && list[0].Value > ObjectSpikeThreshold)
				{
					AbuseNotificationUI.Show(IsChinese ? $"  → 最大持有: {list[0].Key}({list[0].Value} 个对象)" : $"  → Top owner: {list[0].Key} ({list[0].Value} objs)");
				}
				if (list.Count == 0)
				{
					return null;
				}
				StringBuilder stringBuilder = new StringBuilder();
				int num = ((list.Count < 5) ? list.Count : 5);
				for (int i = 0; i < num; i++)
				{
					if (i > 0)
					{
						stringBuilder.Append(';');
					}
					stringBuilder.Append(list[i].Key).Append(':').Append(list[i].Value);
				}
				return stringBuilder.ToString();
			}
			catch (Exception ex)
			{
				AbuseLogger.Write("[ABUSE]   Failed to investigate owners: " + ex.Message);
				return null;
			}
		}

		private static void WritePeriodicReport()
		{
			AbuseLogger.Write(new string('─', 50));
			AbuseLogger.Write("[ABUSE] ═══ Periodic Abuse Report ═══");
			if (PhotonNetwork.InRoom)
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				AbuseLogger.Write(string.Format("[ABUSE]   Room: {0}, Players: {1}/{2}", ((currentRoom != null) ? currentRoom.Name : null) ?? "?", (currentRoom != null) ? currentRoom.PlayerCount : 0, (currentRoom != null) ? currentRoom.MaxPlayers : 0));
			}
			else
			{
				AbuseLogger.Write("[ABUSE]   Not in room");
			}
			AbuseLogger.Write($"[ABUSE]   Current objects: PhotonViews={_lastPhotonViewCount}, Zombies={_lastZombieCount}");
			int num = -1;
			try
			{
				num = PhotonNetwork.GetPing();
			}
			catch
			{
			}
			if (num >= 0)
			{
				AbuseLogger.Write($"[ABUSE]   Photon Ping: {num} ms");
			}
			float reportIntervalSeconds = ReportIntervalSeconds;
			AbuseLogger.Write($"[ABUSE]   Period totals ({reportIntervalSeconds:F0}s): Instantiates={_totalInstantiates}, Destroys={_totalDestroys}, RPCs={_totalRpcs}");
			AbuseLogger.Write($"[ABUSE]   Alerts triggered: {_alertCount}");
			try
			{
				string text = null;
				int num2 = 0;
				int num3 = 0;
				if (PhotonNetwork.InRoom)
				{
					Room currentRoom2 = PhotonNetwork.CurrentRoom;
					text = ((currentRoom2 != null) ? currentRoom2.Name : null);
					num2 = ((currentRoom2 != null) ? currentRoom2.PlayerCount : 0);
					num3 = ((currentRoom2 != null) ? currentRoom2.MaxPlayers : 0);
				}
				StructuredEvent evt = default(StructuredEvent);
				evt.Timestamp = StructuredLogger.NowStamp();
				evt.FrameNumber = Time.frameCount;
				evt.Type = EventType.PeriodicReport;
				evt.Fields = new Dictionary<string, object>
				{
					{ "AlertType", "PeriodicReport" },
					{ "CurrentCount", _lastPhotonViewCount },
					{
						"Delta",
						_totalInstantiates - _totalDestroys
					},
					{ "TotalInstantiates", _totalInstantiates },
					{ "TotalDestroys", _totalDestroys },
					{ "TotalRpcs", _totalRpcs },
					{ "AlertCount", _alertCount },
					{ "ZombieCount", _lastZombieCount },
					{
						"RoomName",
						text ?? ""
					},
					{ "PlayerCount", num2 },
					{ "MaxPlayers", num3 },
					{ "Ping", num }
				};
				StructuredLogger.WriteEvent(evt);
			}
			catch (Exception ex)
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[WHY_LAG] NetworkAbuseDetector PeriodicReport event failed: " + ex.Message));
				}
			}
			lock (_lock)
			{
				if (_prefabCount.Count > 0)
				{
					AbuseLogger.Write("[ABUSE]   Top spawned prefabs:");
					List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(_prefabCount);
					list.Sort((KeyValuePair<string, int> a, KeyValuePair<string, int> b) => b.Value.CompareTo(a.Value));
					int num4 = 0;
					foreach (KeyValuePair<string, int> item4 in list)
					{
						AbuseLogger.WriteRaw($"          {item4.Key}: {item4.Value}x");
						if (++num4 >= 5)
						{
							break;
						}
					}
				}
			}
			if (PhotonNetwork.InRoom)
			{
				AbuseLogger.Write("[ABUSE]   Players in room:");
				Player[] playerList = PhotonNetwork.PlayerList;
				foreach (Player val in playerList)
				{
					string arg = (val.IsMasterClient ? " [Master]" : "");
					AbuseLogger.WriteRaw($"          #{val.ActorNumber} {val.NickName}{arg}");
				}
			}
			lock (_lock)
			{
				if (_rpcByActorMethodTotal.Count > 0)
				{
					AbuseLogger.Write("[ABUSE]   Top client RPCs (Actor×Method):");
					List<(int, string, int)> list2 = new List<(int, string, int)>();
					foreach (KeyValuePair<int, Dictionary<string, int>> item5 in _rpcByActorMethodTotal)
					{
						foreach (KeyValuePair<string, int> item6 in item5.Value)
						{
							list2.Add((item5.Key, item6.Key, item6.Value));
						}
					}
					list2.Sort(((int actor, string method, int count) a, (int actor, string method, int count) b) => b.count.CompareTo(a.count));
					int num5 = 0;
					foreach (var item7 in list2)
					{
						int item = item7.Item1;
						string item2 = item7.Item2;
						int item3 = item7.Item3;
						string text2 = TryGetNickName(item);
						AbuseLogger.WriteRaw($"          #{item} {text2}: {item2} ×{item3}");
						if (++num5 >= 10)
						{
							break;
						}
					}
				}
			}
			AbuseLogger.Write(new string('─', 50));
			RpcMonitor.WritePeriodicReport();
		}

		private static void EmitSpikeEvent(string alertType, int delta, int currentCount, string topOwners = null)
		{
			try
			{
				Dictionary<string, object> dictionary = new Dictionary<string, object>
				{
					{ "AlertType", alertType },
					{ "Delta", delta },
					{ "CurrentCount", currentCount },
					{ "Threshold", ObjectSpikeThreshold }
				};
				if (!string.IsNullOrEmpty(topOwners))
				{
					dictionary["TopOwners"] = topOwners;
				}
				try
				{
					dictionary["Ping"] = PhotonNetwork.GetPing();
				}
				catch
				{
				}
				StructuredEvent evt = default(StructuredEvent);
				evt.Timestamp = StructuredLogger.NowStamp();
				evt.FrameNumber = Time.frameCount;
				evt.Type = EventType.AbuseAlert;
				evt.Fields = dictionary;
				StructuredLogger.WriteEvent(evt);
			}
			catch (Exception ex)
			{
				ManualLogSource log = WhySoLaggyPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[WHY_LAG] NetworkAbuseDetector EmitSpike failed: " + ex.Message));
				}
			}
		}

		private static void ResetCounters()
		{
			lock (_lock)
			{
				_localInstantiateCount = 0;
				_localDestroyCount = 0;
				_localRpcCount = 0;
				_remoteInstantiateCount = 0;
				_remoteDestroyCount = 0;
				_remoteRpcCount = 0;
				_instantiateByActor.Clear();
				_rpcByActor.Clear();
				_destroyByActor.Clear();
				_rpcByActorMethod.Clear();
				_ownershipGrabbedByActor.Clear();
			}
		}

		private static void ResetReportStats()
		{
			lock (_lock)
			{
				_totalInstantiates = 0;
				_totalDestroys = 0;
				_totalRpcs = 0;
				_alertCount = 0;
				_prefabCount.Clear();
				_rpcByActorMethodTotal.Clear();
			}
		}

		private static void IncrementActor(Dictionary<int, int> dict, int actorNumber)
		{
			if (!dict.ContainsKey(actorNumber))
			{
				dict[actorNumber] = 0;
			}
			dict[actorNumber]++;
		}

		private static void LogTopActors(Dictionary<int, int> dict, string action)
		{
			if (dict.Count == 0)
			{
				return;
			}
			List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>(dict);
			list.Sort((KeyValuePair<int, int> a, KeyValuePair<int, int> b) => b.Value.CompareTo(a.Value));
			AbuseLogger.AlertDetail("[ABUSE]   Top " + action + " sources (by ActorNumber):");
			int num = 0;
			foreach (KeyValuePair<int, int> item in list)
			{
				string arg = ResolvePlayerName(item.Key);
				AbuseLogger.AlertDetailRaw($"          {arg}#{item.Key}: {item.Value}x");
				if (++num >= 5)
				{
					break;
				}
			}
			if (list.Count > 0)
			{
				string text = ResolvePlayerName(list[0].Key);
				AbuseNotificationUI.Show(IsChinese ? $"  → 嫌疑人: {text}#{list[0].Key}({list[0].Value}次 {action})" : $"  → Suspect: {text}#{list[0].Key} ({list[0].Value}x {action})");
			}
		}

		private static void LogTopPrefabs()
		{
			lock (_lock)
			{
				if (_prefabCount.Count == 0)
				{
					return;
				}
				List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(_prefabCount);
				list.Sort((KeyValuePair<string, int> a, KeyValuePair<string, int> b) => b.Value.CompareTo(a.Value));
				AbuseLogger.AlertDetail("[ABUSE]   Top prefabs this interval:");
				int num = 0;
				foreach (KeyValuePair<string, int> item in list)
				{
					AbuseLogger.AlertDetailRaw($"          {item.Key}: {item.Value}x");
					if (++num >= 5)
					{
						break;
					}
				}
			}
		}

		private static string ResolvePlayerName(int actorNumber)
		{
			try
			{
				if (!PhotonNetwork.InRoom)
				{
					return "?";
				}
				Player[] playerList = PhotonNetwork.PlayerList;
				foreach (Player val in playerList)
				{
					if (val.ActorNumber == actorNumber)
					{
						return val.NickName ?? "?";
					}
				}
			}
			catch
			{
			}
			return "?";
		}
	}
	internal static class PatchProfiler
	{
		private class MethodTimingData
		{
			public long TotalTicks;

			public int CallCount;
		}

		private class FrameMethodData
		{
			public long Ticks;

			public int Calls;
		}

		public static bool Enabled = true;

		public static int TopMethodCount = 10;

		public static float MinReportMs = 0.1f;

		public static readonly HashSet<string> IgnoreMethods = new HashSet<string>(StringComparer.Ordinal);

		public static string OwnHarmonyId;

		private static readonly Dictionary<string, MethodTimingData> _timings = new Dictionary<string, MethodTimingData>();

		private static readonly Dictionary<string, string> _ownerMap = new Dictionary<string, string>();

		private static readonly Dictionary<string, FrameMethodData> _frameTimers = new Dictionary<string, FrameMethodData>();

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

		private static bool _initialized;

		private static int _patchedCount;

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

		private static readonly List<KeyValuePair<string, MethodTimingData>> _reportSorted = new List<KeyValuePair<string, MethodTimingData>>(64);

		private static readonly List<KeyValuePair<string, FrameMethodData>> _spikeSorted = new List<KeyValuePair<string, FrameMethodData>>(32);

		public static void Initialize(Harmony harmony)
		{
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Expected O, but got Unknown
			//IL_0184: Expected O, but got Unknown
			if (_initialized || !Enabled)
			{
				return;
			}
			_initialized = true;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("[WHY_LAG] -- PatchProfiler: scanning patched methods --");
			List<MethodBase> list = Harmony.GetAllPatchedMethods().ToList();
			stringBuilder.AppendLine($"[WHY_LAG]   Found {list.Count} patched methods in total");
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			foreach (MethodBase item in list)
			{
				if (item == null)
				{
					continue;
				}
				st