using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
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;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[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("0.1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.1.0.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 Write(string message)
{
if (_writer == null)
{
return;
}
lock (_lock)
{
try
{
_writer.WriteLine("[" + Timestamp() + "] " + message);
}
catch
{
}
}
}
public static void Alert(string message)
{
string text = "⚠ ABUSE ALERT: " + message;
Write(text);
ManualLogSource log = WhySoLaggyPlugin.Log;
if (log != null)
{
log.LogWarning((object)("[WHY_LAG] " + text));
}
}
public static void WriteRaw(string line)
{
if (_writer == null)
{
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_0076: Unknown result type (might be due to invalid IL or missing references)
//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
//IL_0116: 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;
Rect val = default(Rect);
((Rect)(ref val))..ctor(10f, 10f, 520f, num);
GUI.Box(val, GUIContent.none, _boxStyle);
float num2 = 18f;
Rect val2 = default(Rect);
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;
((Rect)(ref val2))..ctor(18f, num2, 504f, 22f);
GUI.Label(val2, notification.Message, _textStyle);
num2 += 22f;
}
}
private static void EnsureStyles()
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Expected O, but got Unknown
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Expected O, but got Unknown
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_007b: Expected O, but got Unknown
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_0090: Expected O, but got Unknown
//IL_00bb: 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 FpsTracker
{
public static int SpikeThresholdMs = 50;
public static int ReportIntervalSeconds = 10;
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;
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;
}
_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;
}
private static void WriteReport()
{
if (_periodFrames != 0)
{
float num = ((_periodSumMs > 0f) ? ((float)_periodFrames / (_periodSumMs / 1000f)) : 0f);
float num2 = _periodSumMs / (float)_periodFrames;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"[WHY_LAG] === FPS Report ({_periodTimer:F1}s, {_periodFrames} frames) ===");
stringBuilder.AppendLine($"[WHY_LAG] FPS: avg={num:F1} | min={_periodMinFps:F1} | max={_periodMaxFps:F1} | avgFrame={num2:F1}ms");
stringBuilder.Append($"[WHY_LAG] Spikes(>{SpikeThresholdMs}ms): {_periodSpikeCount}");
LagLogger.Write(stringBuilder.ToString());
}
}
private static void ResetPeriod()
{
_periodTimer = 0f;
_periodFrames = 0;
_periodSumMs = 0f;
_periodMinFps = float.MaxValue;
_periodMaxFps = 0f;
_periodSpikeCount = 0;
}
}
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)
{
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");
}
}
internal static class NetworkAbuseDetector
{
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 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<string, int> _prefabCount = new Dictionary<string, int>();
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 => (int)Application.systemLanguage == 6 || (int)Application.systemLanguage == 40 || (int)Application.systemLanguage == 41;
public static void Initialize(Harmony harmony)
{
if (_initialized)
{
return;
}
try
{
PatchPhotonMethods(harmony);
FindGameTypes();
_lastPhotonViewCount = CountPhotonViews();
_lastZombieCount = CountZombies();
_initialized = true;
AbuseLogger.Write("[ABUSE] NetworkAbuseDetector initialized (observation-only mode)");
AbuseLogger.Write($"[ABUSE] Thresholds: Instantiate={InstantiateRateThreshold}/s, Destroy={DestroyRateThreshold}/s, RPC={RpcRateThreshold}/s, ObjectSpike={ObjectSpikeThreshold}");
AbuseLogger.Write($"[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 Tick()
{
if (_initialized)
{
float unscaledDeltaTime = Time.unscaledDeltaTime;
_checkTimer += unscaledDeltaTime;
_reportTimer += unscaledDeltaTime;
if (_checkTimer >= CheckIntervalSeconds)
{
CheckRates();
CheckObjectSpike();
ResetCounters();
_checkTimer = 0f;
}
if (_reportTimer >= ReportIntervalSeconds)
{
WritePeriodicReport();
ResetReportStats();
_reportTimer = 0f;
}
}
}
private static void PatchPhotonMethods(Harmony harmony)
{
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_0075: Expected O, but got Unknown
//IL_0103: Unknown result type (might be due to invalid IL or missing references)
//IL_0111: Expected O, but got Unknown
//IL_0191: Unknown result type (might be due to invalid IL or missing references)
//IL_019f: Expected O, but got Unknown
MethodInfo[] methods = typeof(PhotonNetwork).GetMethods(BindingFlags.Static | BindingFlags.Public);
MethodInfo[] array = methods;
foreach (MethodInfo methodInfo in array)
{
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.Write("[ABUSE] Failed to patch " + methodInfo.Name + ": " + ex.Message);
}
}
}
MethodInfo[] methods2 = typeof(PhotonNetwork).GetMethods(BindingFlags.Static | BindingFlags.Public);
MethodInfo[] array2 = methods2;
foreach (MethodInfo methodInfo2 in array2)
{
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.Write("[ABUSE] Failed to patch Destroy: " + ex2.Message);
}
}
}
MethodInfo[] methods3 = typeof(PhotonView).GetMethods(BindingFlags.Instance | BindingFlags.Public);
MethodInfo[] array3 = methods3;
foreach (MethodInfo methodInfo3 in array3)
{
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.Write("[ABUSE] Failed to patch RPC: " + ex3.Message);
}
}
}
AbuseLogger.Write("[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.Write("[ABUSE] Found zombie type: " + type.FullName);
break;
}
}
}
catch
{
}
}
if (_zombieType == null)
{
AbuseLogger.Write("[ABUSE] MushroomZombie type not found (zombie counting disabled)");
}
}
private static void OnInstantiatePrefix(string prefabName)
{
lock (_lock)
{
_localInstantiateCount++;
_totalInstantiates++;
if (!string.IsNullOrEmpty(prefabName))
{
if (!_prefabCount.ContainsKey(prefabName))
{
_prefabCount[prefabName] = 0;
}
_prefabCount[prefabName]++;
}
}
}
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> dict;
Dictionary<int, int> dict2;
Dictionary<int, int> dict3;
lock (_lock)
{
num = _localInstantiateCount + _remoteInstantiateCount;
num2 = _localDestroyCount + _remoteDestroyCount;
num3 = _localRpcCount + _remoteRpcCount;
dict = new Dictionary<int, int>(_instantiateByActor);
dict2 = new Dictionary<int, int>(_rpcByActor);
dict3 = 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++;
string message = $"Instantiate flood! Rate: {num4:F1}/s (threshold: {InstantiateRateThreshold}/s)";
AbuseLogger.Alert(message);
string message2 = (IsChinese ? $"⚠ 刷物体洪水!速率: {num4:F1}/秒(阈值: {InstantiateRateThreshold}/秒)" : $"⚠ Instantiate flood! Rate: {num4:F1}/s (threshold: {InstantiateRateThreshold}/s)");
AbuseNotificationUI.Show(message2);
LogTopActors(dict, "Instantiate");
LogTopPrefabs();
}
if (num5 >= (float)DestroyRateThreshold)
{
_alertCount++;
string message3 = $"Destroy flood! Rate: {num5:F1}/s (threshold: {DestroyRateThreshold}/s)";
AbuseLogger.Alert(message3);
string message4 = (IsChinese ? $"⚠ 大量销毁!速率: {num5:F1}/秒(阈值: {DestroyRateThreshold}/秒)" : $"⚠ Destroy flood! Rate: {num5:F1}/s (threshold: {DestroyRateThreshold}/s)");
AbuseNotificationUI.Show(message4);
LogTopActors(dict3, "Destroy");
}
if (num6 >= (float)RpcRateThreshold)
{
_alertCount++;
string message5 = $"RPC flood! Rate: {num6:F1}/s (threshold: {RpcRateThreshold}/s)";
AbuseLogger.Alert(message5);
string message6 = (IsChinese ? $"⚠ RPC洪水!速率: {num6:F1}/秒(阈值: {RpcRateThreshold}/秒)" : $"⚠ RPC flood! Rate: {num6:F1}/s (threshold: {RpcRateThreshold}/s)");
AbuseNotificationUI.Show(message6);
LogTopActors(dict2, "RPC");
}
}
private static void CheckObjectSpike()
{
int num = CountPhotonViews();
int num2 = CountZombies();
int num3 = num - _lastPhotonViewCount;
int num4 = num2 - _lastZombieCount;
if (num3 >= ObjectSpikeThreshold)
{
_alertCount++;
string message = $"PhotonView spike! +{num3} in {CheckIntervalSeconds}s (now: {num})";
AbuseLogger.Alert(message);
string message2 = (IsChinese ? $"⚠ 对象突增!+{num3} 个/{CheckIntervalSeconds}秒(当前: {num})" : $"⚠ PhotonView spike! +{num3} in {CheckIntervalSeconds}s (now: {num})");
AbuseNotificationUI.Show(message2);
InvestigatePhotonViewOwners();
}
if (num4 >= ObjectSpikeThreshold)
{
_alertCount++;
string message3 = $"Zombie spike! +{num4} in {CheckIntervalSeconds}s (now: {num2})";
AbuseLogger.Alert(message3);
string message4 = (IsChinese ? $"⚠ 僵尸突增!+{num4} 个/{CheckIntervalSeconds}秒(当前: {num2})" : $"⚠ Zombie spike! +{num4} in {CheckIntervalSeconds}s (now: {num2})");
AbuseNotificationUI.Show(message4);
}
_lastPhotonViewCount = num;
_lastZombieCount = num2;
}
private static int CountPhotonViews()
{
//IL_0005: 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_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: 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())
{
PhotonView current = 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 void InvestigatePhotonViewOwners()
{
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: 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)
{
string message = (IsChinese ? $" → 最大持有: {list[0].Key}({list[0].Value} 个对象)" : $" → Top owner: {list[0].Key} ({list[0].Value} objs)");
AbuseNotificationUI.Show(message);
}
}
catch (Exception ex)
{
AbuseLogger.Write("[ABUSE] Failed to investigate owners: " + ex.Message);
}
}
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}");
float reportIntervalSeconds = ReportIntervalSeconds;
AbuseLogger.Write($"[ABUSE] Period totals ({reportIntervalSeconds:F0}s): Instantiates={_totalInstantiates}, Destroys={_totalDestroys}, RPCs={_totalRpcs}");
AbuseLogger.Write($"[ABUSE] Alerts triggered: {_alertCount}");
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 num = 0;
foreach (KeyValuePair<string, int> item in list)
{
AbuseLogger.WriteRaw($" {item.Key}: {item.Value}x");
if (++num >= 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}");
}
}
AbuseLogger.Write(new string('─', 50));
}
private static void ResetCounters()
{
lock (_lock)
{
_localInstantiateCount = 0;
_localDestroyCount = 0;
_localRpcCount = 0;
_remoteInstantiateCount = 0;
_remoteDestroyCount = 0;
_remoteRpcCount = 0;
_instantiateByActor.Clear();
_rpcByActor.Clear();
_destroyByActor.Clear();
}
}
private static void ResetReportStats()
{
lock (_lock)
{
_totalInstantiates = 0;
_totalDestroys = 0;
_totalRpcs = 0;
_alertCount = 0;
_prefabCount.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.Write("[ABUSE] Top " + action + " sources (by ActorNumber):");
int num = 0;
foreach (KeyValuePair<int, int> item in list)
{
string arg = ResolvePlayerName(item.Key);
AbuseLogger.WriteRaw($" {arg}#{item.Key}: {item.Value}x");
if (++num >= 5)
{
break;
}
}
if (list.Count > 0)
{
string text = ResolvePlayerName(list[0].Key);
string message = (IsChinese ? $" → 嫌疑人: {text}#{list[0].Key}({list[0].Value}次 {action})" : $" → Suspect: {text}#{list[0].Key} ({list[0].Value}x {action})");
AbuseNotificationUI.Show(message);
}
}
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.Write("[ABUSE] Top prefabs this interval:");
int num = 0;
foreach (KeyValuePair<string, int> item in list)
{
AbuseLogger.WriteRaw($" {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 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 bool _initialized;
private static int _patchedCount;
public static void Initialize(Harmony harmony)
{
//IL_0156: Unknown result type (might be due to invalid IL or missing references)
//IL_015b: Unknown result type (might be due to invalid IL or missing references)
//IL_0176: Unknown result type (might be due to invalid IL or missing references)
//IL_017b: Unknown result type (might be due to invalid IL or missing references)
//IL_018a: Expected O, but got Unknown
//IL_018a: 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;
foreach (MethodBase item in list)
{
if (item == null)
{
continue;
}
try
{
Patches patchInfo = Harmony.GetPatchInfo(item);
if (patchInfo == null)
{
continue;
}
List<string> list2 = patchInfo.Owners?.ToList() ?? new List<string>();
if (list2.Count != 0)
{
List<string> list3 = list2.Where((string o) => o != OwnHarmonyId).ToList();
if (list3.Count == 0)
{
num++;
continue;
}
string methodKey = GetMethodKey(item);
_ownerMap[methodKey] = string.Join(", ", list3);
harmony.Patch(item, new HarmonyMethod(typeof(PatchProfiler), "TimingPrefix", (Type[])null)
{
priority = 800
}, new HarmonyMethod(typeof(PatchProfiler), "TimingPostfix", (Type[])null)
{
priority = 0
}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
_patchedCount++;
}
}
catch (Exception ex)
{
num2++;
string text = item.DeclaringType?.Name + "." + item.Name;
stringBuilder.AppendLine("[WHY_LAG] FAILED to hook " + text + ": " + ex.Message);
}
}
stringBuilder.AppendLine($"[WHY_LAG] Hooked: {_patchedCount} | Skipped(self-only): {num} | Failed: {num2}");
stringBuilder.Append("[WHY_LAG] PatchProfiler initialized");
LagLogger.Write(stringBuilder.ToString());
}
public static void TimingPrefix(MethodBase __originalMethod, out long __state)
{
__state = Stopwatch.GetTimestamp();
}
public static void TimingPostfix(MethodBase __originalMethod, long __state)
{
long elapsedTicks = Stopwatch.GetTimestamp() - __state;
string methodKey = GetMethodKey(__originalMethod);
Record(methodKey, elapsedTicks);
}
private static void Record(string methodKey, long elapsedTicks)
{
if (!_timings.TryGetValue(methodKey, out var value))
{
value = new MethodTimingData();
_timings[methodKey] = value;
}
value.TotalTicks += elapsedTicks;
value.CallCount++;
if (!_frameTimers.TryGetValue(methodKey, out var value2))
{
value2 = new FrameMethodData();
_frameTimers[methodKey] = value2;
}
value2.Ticks += elapsedTicks;
value2.Calls++;
}
public static void WriteReport(int totalFrames)
{
if (!_initialized || _timings.Count == 0)
{
return;
}
double num = (double)Stopwatch.Frequency / 1000.0;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("[WHY_LAG] -- Top Slow Patched Methods (per-frame avg) --");
List<KeyValuePair<string, MethodTimingData>> list = (from kv in _timings
where kv.Value.CallCount > 0
orderby kv.Value.TotalTicks descending
select kv).Take(TopMethodCount).ToList();
foreach (KeyValuePair<string, MethodTimingData> item in list)
{
double num2 = (double)item.Value.TotalTicks / num;
double num3 = ((totalFrames > 0) ? (num2 / (double)totalFrames) : num2);
int num4 = ((totalFrames > 0) ? (item.Value.CallCount / totalFrames) : item.Value.CallCount);
string value;
string text = (_ownerMap.TryGetValue(item.Key, out value) ? value : "?");
string text2 = ((num3 > 2.0) ? " !!!" : ((num3 > 0.5) ? " !" : ""));
stringBuilder.AppendLine($"[WHY_LAG] {item.Key}: {num3:F2}ms x{num4} [{text}]{text2}");
}
LagLogger.Write(stringBuilder.ToString().TrimEnd(Array.Empty<char>()));
foreach (MethodTimingData value2 in _timings.Values)
{
value2.TotalTicks = 0L;
value2.CallCount = 0;
}
}
public static void WriteSpikeDetail()
{
if (!_initialized || _frameTimers.Count == 0)
{
return;
}
double tickFreq = (double)Stopwatch.Frequency / 1000.0;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("[WHY_LAG] Methods: ");
List<KeyValuePair<string, FrameMethodData>> list = (from kv in _frameTimers
where kv.Value.Ticks > 0
orderby kv.Value.Ticks descending
select kv).Take(5).ToList();
foreach (KeyValuePair<string, FrameMethodData> item in list)
{
double num = (double)item.Value.Ticks / tickFreq;
if (!(num < 0.5))
{
string value;
string text = (_ownerMap.TryGetValue(item.Key, out value) ? value : "?");
stringBuilder.Append($"{item.Key}={num:F1}ms x{item.Value.Calls} [{text}] | ");
}
}
if (list.Any((KeyValuePair<string, FrameMethodData> kv) => (double)kv.Value.Ticks / tickFreq >= 0.5))
{
LagLogger.Write(stringBuilder.ToString());
}
}
public static void ResetFrameTimers()
{
foreach (FrameMethodData value in _frameTimers.Values)
{
value.Ticks = 0L;
value.Calls = 0;
}
}
private static string GetMethodKey(MethodBase method)
{
if (method == null)
{
return "(null)";
}
string text = method.DeclaringType?.Name ?? "?";
return text + "." + method.Name;
}
}
internal static class PluginProfiler
{
private class PluginTimingData
{
public long TotalTicks;
public int CallCount;
}
public static bool Enabled = true;
private static readonly Dictionary<string, PluginTimingData> _timings = new Dictionary<string, PluginTimingData>();
private static readonly Dictionary<string, string> _displayNames = new Dictionary<string, string>();
private static readonly Dictionary<string, long> _frameTimers = new Dictionary<string, long>();
private static bool _initialized;
private static int _patchedCount;
public static void Initialize(Harmony harmony)
{
//IL_0146: Unknown result type (might be due to invalid IL or missing references)
//IL_014b: Unknown result type (might be due to invalid IL or missing references)
//IL_0166: Unknown result type (might be due to invalid IL or missing references)
//IL_016b: Unknown result type (might be due to invalid IL or missing references)
//IL_017a: Expected O, but got Unknown
//IL_017a: Expected O, but got Unknown
if (_initialized || !Enabled)
{
return;
}
_initialized = true;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("[WHY_LAG] -- PluginProfiler: scanning plugins --");
foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
{
PluginInfo value = pluginInfo.Value;
if ((Object)(object)value.Instance == (Object)null)
{
continue;
}
Type type = ((object)value.Instance).GetType();
string key = type.FullName ?? type.Name;
BepInPlugin metadata = value.Metadata;
string text = ((metadata != null) ? metadata.Name : null) ?? pluginInfo.Key;
if (type == typeof(WhySoLaggyPlugin))
{
continue;
}
_displayNames[key] = text;
string[] array = new string[3] { "Update", "LateUpdate", "FixedUpdate" };
string[] array2 = array;
foreach (string text2 in array2)
{
MethodInfo method = type.GetMethod(text2, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (!(method == null))
{
try
{
harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(PluginProfiler), "TimingPrefix", (Type[])null)
{
priority = 800
}, new HarmonyMethod(typeof(PluginProfiler), "TimingPostfix", (Type[])null)
{
priority = 0
}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
_patchedCount++;
stringBuilder.AppendLine("[WHY_LAG] Hooked " + text + "." + text2);
}
catch (Exception ex)
{
stringBuilder.AppendLine("[WHY_LAG] FAILED " + text + "." + text2 + ": " + ex.Message);
}
}
}
}
stringBuilder.Append($"[WHY_LAG] PluginProfiler: {_patchedCount} methods hooked across {_displayNames.Count} plugins");
LagLogger.Write(stringBuilder.ToString());
}
public static void TimingPrefix(MonoBehaviour __instance, out long __state)
{
__state = Stopwatch.GetTimestamp();
}
public static void TimingPostfix(MonoBehaviour __instance, long __state)
{
long elapsedTicks = Stopwatch.GetTimestamp() - __state;
string typeName = ((object)__instance).GetType().FullName ?? ((object)__instance).GetType().Name;
Record(typeName, elapsedTicks);
}
private static void Record(string typeName, long elapsedTicks)
{
if (!_timings.TryGetValue(typeName, out var value))
{
value = new PluginTimingData();
_timings[typeName] = value;
}
value.TotalTicks += elapsedTicks;
value.CallCount++;
if (!_frameTimers.ContainsKey(typeName))
{
_frameTimers[typeName] = 0L;
}
_frameTimers[typeName] += elapsedTicks;
}
public static void WriteReport(int totalFrames)
{
if (!_initialized || _timings.Count == 0)
{
return;
}
double num = (double)Stopwatch.Frequency / 1000.0;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("[WHY_LAG] -- Plugin Update Time (per-frame avg) --");
List<KeyValuePair<string, PluginTimingData>> list = _timings.OrderByDescending((KeyValuePair<string, PluginTimingData> kv) => kv.Value.TotalTicks).ToList();
double num2 = 0.0;
foreach (KeyValuePair<string, PluginTimingData> item in list)
{
string value;
string text = (_displayNames.TryGetValue(item.Key, out value) ? value : item.Key);
double num3 = (double)item.Value.TotalTicks / num;
double num4 = ((totalFrames > 0) ? (num3 / (double)totalFrames) : num3);
num2 += num3;
string text2 = ((num4 > 1.0) ? " !!!" : ((num4 > 0.5) ? " !" : ""));
stringBuilder.AppendLine($"[WHY_LAG] {text}: {num4:F2}ms/frame (total={num3:F1}ms, calls={item.Value.CallCount}){text2}");
}
double num5 = ((totalFrames > 0) ? (num2 / (double)totalFrames) : num2);
stringBuilder.Append($"[WHY_LAG] Total MOD overhead: {num5:F2}ms/frame");
LagLogger.Write(stringBuilder.ToString());
foreach (PluginTimingData value2 in _timings.Values)
{
value2.TotalTicks = 0L;
value2.CallCount = 0;
}
}
public static void WriteSpikeDetail(float frameMs)
{
if (!_initialized || _frameTimers.Count == 0)
{
return;
}
double num = (double)Stopwatch.Frequency / 1000.0;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append($"[WHY_LAG] !!! SPIKE {frameMs:F0}ms ({((frameMs > 0f) ? (1000f / frameMs) : 0f):F1} FPS) !!! Plugins: ");
List<KeyValuePair<string, long>> list = (from kv in _frameTimers
where kv.Value > 0
orderby kv.Value descending
select kv).ToList();
foreach (KeyValuePair<string, long> item in list)
{
string value;
string arg = (_displayNames.TryGetValue(item.Key, out value) ? value : item.Key);
double num2 = (double)item.Value / num;
if (!(num2 < 0.1))
{
stringBuilder.Append($"{arg}={num2:F1}ms | ");
}
}
LagLogger.Write(stringBuilder.ToString());
}
public static void ResetFrameTimers()
{
_frameTimers.Clear();
}
}
[BepInPlugin("com.wuyachiyu.WhySoLaggy", "WhySoLaggy", "1.0.1")]
public class WhySoLaggyPlugin : BaseUnityPlugin, IOnEventCallback
{
[CompilerGenerated]
private sealed class <Start>d__21 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public WhySoLaggyPlugin <>4__this;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <Start>d__21(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
Log.LogInfo((object)"[WHY_LAG] Waiting 5s for all plugins to finish loading...");
LagLogger.Write("[WHY_LAG] Waiting 5s for all plugins to finish loading...");
<>2__current = (object)new WaitForSeconds(5f);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
if (EnablePluginProfiling.Value)
{
PluginProfiler.Initialize(<>4__this._harmony);
}
if (EnablePatchProfiling.Value)
{
PatchProfiler.Initialize(<>4__this._harmony);
}
if (EnableAbuseDetection.Value)
{
NetworkAbuseDetector.InstantiateRateThreshold = InstantiateRateThreshold.Value;
NetworkAbuseDetector.DestroyRateThreshold = DestroyRateThreshold.Value;
NetworkAbuseDetector.RpcRateThreshold = RpcRateThreshold.Value;
NetworkAbuseDetector.ObjectSpikeThreshold = ObjectSpikeThreshold.Value;
NetworkAbuseDetector.CheckIntervalSeconds = AbuseCheckInterval.Value;
NetworkAbuseDetector.ReportIntervalSeconds = AbuseReportInterval.Value;
NetworkAbuseDetector.Initialize(<>4__this._harmony);
}
<>4__this._profilingActive = true;
Log.LogInfo((object)"[WHY_LAG] Profiling active!");
LagLogger.Write("[WHY_LAG] === Profiling active ===");
return false;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
public const string PluginGuid = "com.wuyachiyu.WhySoLaggy";
public const string PluginName = "WhySoLaggy";
public const string PluginVersion = "1.0.1";
public static ConfigEntry<int> SpikeThresholdMs;
public static ConfigEntry<int> ReportIntervalSeconds;
public static ConfigEntry<bool> EnablePluginProfiling;
public static ConfigEntry<bool> EnablePatchProfiling;
public static ConfigEntry<int> TopMethodCount;
public static ConfigEntry<bool> EnableAbuseDetection;
public static ConfigEntry<float> AbuseCheckInterval;
public static ConfigEntry<float> AbuseReportInterval;
public static ConfigEntry<int> InstantiateRateThreshold;
public static ConfigEntry<int> DestroyRateThreshold;
public static ConfigEntry<int> RpcRateThreshold;
public static ConfigEntry<int> ObjectSpikeThreshold;
internal static ManualLogSource Log;
private Harmony _harmony;
private int _reportFrameCount;
private float _reportTimer;
private bool _profilingActive;
private void Awake()
{
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_004e: Expected O, but got Unknown
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
//IL_0081: Expected O, but got Unknown
//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
//IL_00f4: Expected O, but got Unknown
//IL_0147: Unknown result type (might be due to invalid IL or missing references)
//IL_0151: Expected O, but got Unknown
//IL_0184: Unknown result type (might be due to invalid IL or missing references)
//IL_018e: Expected O, but got Unknown
//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
//IL_01c1: Expected O, but got Unknown
//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
//IL_01f4: Expected O, but got Unknown
//IL_0221: Unknown result type (might be due to invalid IL or missing references)
//IL_022b: Expected O, but got Unknown
//IL_0254: Unknown result type (might be due to invalid IL or missing references)
//IL_025e: Expected O, but got Unknown
//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
//IL_02f4: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
Log.LogInfo((object)"[WHY_LAG] WhySoLaggy Awake");
SpikeThresholdMs = ((BaseUnityPlugin)this).Config.Bind<int>("General", "SpikeThresholdMs", 50, new ConfigDescription("Frame time threshold for spike detection (ms). Frames exceeding this are logged.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(16, 200), Array.Empty<object>()));
ReportIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("General", "ReportIntervalSeconds", 10, new ConfigDescription("Seconds between periodic performance reports.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 60), Array.Empty<object>()));
EnablePluginProfiling = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnablePluginProfiling", true, "Profile each BepInEx plugin's Update/LateUpdate/FixedUpdate callbacks.");
EnablePatchProfiling = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnablePatchProfiling", true, "Profile all Harmony-patched game methods.");
TopMethodCount = ((BaseUnityPlugin)this).Config.Bind<int>("General", "TopMethodCount", 10, new ConfigDescription("Number of top slow methods to show in reports.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(3, 30), Array.Empty<object>()));
EnableAbuseDetection = ((BaseUnityPlugin)this).Config.Bind<bool>("AbuseDetection", "EnableAbuseDetection", true, "Enable network abuse / room bombing detection.");
AbuseCheckInterval = ((BaseUnityPlugin)this).Config.Bind<float>("AbuseDetection", "CheckIntervalSeconds", 1f, new ConfigDescription("Seconds between each abuse rate check.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 5f), Array.Empty<object>()));
AbuseReportInterval = ((BaseUnityPlugin)this).Config.Bind<float>("AbuseDetection", "ReportIntervalSeconds", 30f, new ConfigDescription("Seconds between periodic abuse summary reports.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 120f), Array.Empty<object>()));
InstantiateRateThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("AbuseDetection", "InstantiateRateThreshold", 15, new ConfigDescription("Max Instantiate calls per second before triggering alert.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 100), Array.Empty<object>()));
DestroyRateThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("AbuseDetection", "DestroyRateThreshold", 20, new ConfigDescription("Max Destroy calls per second before triggering alert.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 100), Array.Empty<object>()));
RpcRateThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("AbuseDetection", "RpcRateThreshold", 50, new ConfigDescription("Max RPC calls per second before triggering alert.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 200), Array.Empty<object>()));
ObjectSpikeThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("AbuseDetection", "ObjectSpikeThreshold", 30, new ConfigDescription("Object count increase per check interval to trigger spike alert.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 100), Array.Empty<object>()));
string bepInExDir = Path.GetDirectoryName(Path.GetDirectoryName(typeof(BaseUnityPlugin).Assembly.Location)) ?? Paths.BepInExRootPath;
LagLogger.Initialize(bepInExDir);
AbuseLogger.Initialize(bepInExDir);
FpsTracker.SpikeThresholdMs = SpikeThresholdMs.Value;
FpsTracker.ReportIntervalSeconds = ReportIntervalSeconds.Value;
PluginProfiler.Enabled = EnablePluginProfiling.Value;
PatchProfiler.Enabled = EnablePatchProfiling.Value;
PatchProfiler.TopMethodCount = TopMethodCount.Value;
_harmony = new Harmony("com.wuyachiyu.WhySoLaggy");
PatchProfiler.OwnHarmonyId = "com.wuyachiyu.WhySoLaggy";
Log.LogInfo((object)"[WHY_LAG] Config bound, loggers initialized");
}
[IteratorStateMachine(typeof(<Start>d__21))]
private IEnumerator Start()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <Start>d__21(0)
{
<>4__this = this
};
}
private void Update()
{
FpsTracker.Tick();
if (EnableAbuseDetection.Value)
{
NetworkAbuseDetector.Tick();
}
if (_profilingActive)
{
_reportFrameCount++;
_reportTimer += Time.unscaledDeltaTime;
if (FpsTracker.IsSpikeFrame)
{
PluginProfiler.WriteSpikeDetail(FpsTracker.CurrentFrameMs);
PatchProfiler.WriteSpikeDetail();
}
if (_reportTimer >= (float)ReportIntervalSeconds.Value)
{
LagLogger.Write(new string('-', 60));
PluginProfiler.WriteReport(_reportFrameCount);
PatchProfiler.WriteReport(_reportFrameCount);
_reportFrameCount = 0;
_reportTimer = 0f;
}
PluginProfiler.ResetFrameTimers();
PatchProfiler.ResetFrameTimers();
}
}
private void OnDestroy()
{
PhotonNetwork.RemoveCallbackTarget((object)this);
LagLogger.Shutdown();
AbuseLogger.Shutdown();
}
private void OnEnable()
{
PhotonNetwork.AddCallbackTarget((object)this);
}
private void OnDisable()
{
PhotonNetwork.RemoveCallbackTarget((object)this);
}
public void OnEvent(EventData photonEvent)
{
if (EnableAbuseDetection.Value)
{
NetworkAbuseDetector.OnNetworkEvent(photonEvent.Code, photonEvent.Sender);
}
}
private void OnGUI()
{
if (EnableAbuseDetection.Value)
{
AbuseNotificationUI.DrawGUI();
}
}
}
}