Decompiled source of DiagnoseServerLag v0.10.21

BepInEx/plugins/DiagnoseServerLag.dll

Decompiled 2 weeks ago
using System;
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 HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("DiagnoseServerLag")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.10.21.0")]
[assembly: AssemblyInformationalVersion("0.10.21+be923118940ce2bee0b4699d9eed53b4b66604b3")]
[assembly: AssemblyProduct("DiagnoseServerLag")]
[assembly: AssemblyTitle("DiagnoseServerLag")]
[assembly: AssemblyVersion("0.10.21.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DiagnoseServerLag
{
	internal static class CaptureCsv
	{
		internal const string Header = "utc,machine,second,frame_avg_ms,frame_max_ms,stalls,frames,ping_ms,ping_measured,ping_round_trip,quality_local,quality_remote,in_bytes_sec,out_bytes_sec,send_queue_bytes,send_rate_bytes_sec,zdos,instances,zdos_sent_sec,zdos_recv_sec,change_queue,peers,cpu_ms_per_sec,cpu_measured,gc0,gc1,gc2,collections,heap_bytes,working_set_bytes,owned_ai,nearby_ai,feed_ms,owned_objects,nearby_objects,unowned_objects,unowned_ai,system_cpu_pct,nearby_pieces,loading,free_memory_mb,terrain_zones,terrain_bytes,gpu_ms,cpu_main_ms,cpu_render_ms,main_cpu_ms_sec,settings_rev";

		internal static readonly string[] Columns = "utc,machine,second,frame_avg_ms,frame_max_ms,stalls,frames,ping_ms,ping_measured,ping_round_trip,quality_local,quality_remote,in_bytes_sec,out_bytes_sec,send_queue_bytes,send_rate_bytes_sec,zdos,instances,zdos_sent_sec,zdos_recv_sec,change_queue,peers,cpu_ms_per_sec,cpu_measured,gc0,gc1,gc2,collections,heap_bytes,working_set_bytes,owned_ai,nearby_ai,feed_ms,owned_objects,nearby_objects,unowned_objects,unowned_ai,system_cpu_pct,nearby_pieces,loading,free_memory_mb,terrain_zones,terrain_bytes,gpu_ms,cpu_main_ms,cpu_render_ms,main_cpu_ms_sec,settings_rev".Split(new char[1] { ',' });

		internal static string Sparse(params string[] pairs)
		{
			string[] array = new string[Columns.Length];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = "";
			}
			for (int j = 0; j + 1 < pairs.Length; j += 2)
			{
				int num = Array.IndexOf(Columns, pairs[j]);
				if (num >= 0)
				{
					array[num] = pairs[j + 1];
				}
				else
				{
					DiagnoseServerLagMod.Log.LogWarning((object)("[DiagnoseServerLag] capture column '" + pairs[j] + "' does not exist; dropped."));
				}
			}
			return string.Join(",", array);
		}

		internal static string Row(string machine, Sample s, CultureInfo c)
		{
			return string.Join(",", Iso(s.UtcTicks), Csv(machine), s.At.ToString("0.0", c), s.FrameMsAvg.ToString("0.00", c), s.FrameMsMax.ToString("0.00", c), s.Stalls.ToString(c), s.Frames.ToString(c), s.Ping.ToString(c), s.HasPing ? "1" : "0", s.PingFromRoundTrip ? "1" : "0", s.LocalQuality.ToString("0.0000", c), s.RemoteQuality.ToString("0.0000", c), s.InByteSec.ToString("0", c), s.OutByteSec.ToString("0", c), s.SendQueue.ToString(c), s.SendRate.ToString(c), s.Zdos.ToString(c), s.Instances.ToString(c), s.ZdosSent.ToString(c), s.ZdosRecv.ToString(c), s.ChangeQueue.ToString(c), s.Peers.ToString(c), s.CpuMsPerSec.ToString("0.0", c), s.HasCpu ? "1" : "0", s.Gc0.ToString(c), s.Gc1.ToString(c), s.Gc2.ToString(c), Machine.Collections(s).ToString(c), s.HeapBytes.ToString(c), s.WorkingSetBytes.ToString(c), s.OwnedAI.ToString(c), s.NearbyAI.ToString(c), s.FeedMs.ToString("0", c), s.OwnedObjects.ToString(c), s.NearbyObjects.ToString(c), s.UnownedObjects.ToString(c), s.UnownedAI.ToString(c), (s.SystemCpu >= 0f) ? (s.SystemCpu * 100f).ToString("0.0", c) : "", s.NearbyPieces.ToString(c), s.Loading ? "1" : "0", (s.FreeMemoryMB >= 0) ? s.FreeMemoryMB.ToString(c) : "", s.TerrainZones.ToString(c), s.TerrainBytes.ToString(c), (s.GpuMs >= 0f) ? s.GpuMs.ToString("0.00", c) : "", (s.CpuMainMs >= 0f) ? s.CpuMainMs.ToString("0.00", c) : "", (s.CpuRenderMs >= 0f) ? s.CpuRenderMs.ToString("0.00", c) : "", (s.MainCpuMsPerSec >= 0f) ? s.MainCpuMsPerSec.ToString("0.0", c) : "", s.SettingsRev.ToString(c));
		}

		internal static string Iso(long ticks)
		{
			if (ticks > 0)
			{
				return new DateTime(ticks, DateTimeKind.Utc).ToString("yyyy-MM-ddTHH:mm:ssZ");
			}
			return "";
		}

		internal static string Csv(string s)
		{
			if (!string.IsNullOrEmpty(s))
			{
				if (s.IndexOf(',') < 0)
				{
					return s;
				}
				return "\"" + s.Replace("\"", "\"\"") + "\"";
			}
			return "";
		}
	}
	internal sealed class ClientSeries
	{
		internal struct Second
		{
			internal long UtcTicks;

			internal float FrameMaxMs;

			internal int Stalls;

			internal float CpuMsPerSec;

			internal int Collections;
		}

		private const byte Layout = 11;

		internal long Uid;

		internal string Name = "";

		internal string CpuName = "";

		internal int Cores;

		internal bool HasCpu;

		internal string Gpu = "";

		internal int VramMB;

		internal int RamMB;

		internal int ScreenWidth;

		internal int ScreenHeight;

		internal int RefreshHz;

		internal string Graphics = "";

		internal string GpuDetail = "";

		internal string ModVersion = "";

		internal readonly List<Second> Seconds = new List<Second>();

		internal readonly List<Sample> Samples = new List<Sample>();

		internal bool Full;

		internal long LeftAtUtcTicks;

		internal float FrameMedianMs;

		internal int TotalStalls;

		internal float CpuMedianMsPerSec;

		internal int RoundTripMs;

		internal int OwnedAI;

		internal int NearbyAI;

		internal ZPackage Pack()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write((byte)11);
			val.Write(Uid);
			val.Write(Name ?? "");
			val.Write(CpuName ?? "");
			val.Write(Cores);
			val.Write(HasCpu);
			val.Write(FrameMedianMs);
			val.Write(TotalStalls);
			val.Write(CpuMedianMsPerSec);
			val.Write(RoundTripMs);
			val.Write(OwnedAI);
			val.Write(NearbyAI);
			val.Write(Gpu ?? "");
			val.Write(VramMB);
			val.Write(RamMB);
			val.Write(ScreenWidth);
			val.Write(ScreenHeight);
			val.Write(Graphics ?? "");
			val.Write(GpuDetail ?? "");
			ZPackage val2 = new ZPackage();
			val2.Write(Samples.Count);
			foreach (Sample sample in Samples)
			{
				SampleWire.Write(val2, sample);
			}
			val.WriteCompressed(val2);
			val.Write(ModVersion ?? "");
			val.Write(RefreshHz);
			return val;
		}

		internal static ClientSeries Unpack(ZPackage pkg)
		{
			if (pkg == null)
			{
				return null;
			}
			try
			{
				ClientSeries clientSeries = new ClientSeries();
				byte b = pkg.ReadByte();
				if (b < 1)
				{
					return null;
				}
				clientSeries.Uid = pkg.ReadLong();
				clientSeries.Name = pkg.ReadString();
				clientSeries.CpuName = pkg.ReadString();
				clientSeries.Cores = pkg.ReadInt();
				clientSeries.HasCpu = pkg.ReadBool();
				clientSeries.FrameMedianMs = pkg.ReadSingle();
				clientSeries.TotalStalls = pkg.ReadInt();
				clientSeries.CpuMedianMsPerSec = pkg.ReadSingle();
				clientSeries.RoundTripMs = pkg.ReadInt();
				if (b >= 3)
				{
					clientSeries.OwnedAI = pkg.ReadInt();
					clientSeries.NearbyAI = pkg.ReadInt();
				}
				if (b >= 11)
				{
					clientSeries.Gpu = pkg.ReadString();
					clientSeries.VramMB = pkg.ReadInt();
					clientSeries.RamMB = pkg.ReadInt();
					clientSeries.ScreenWidth = pkg.ReadInt();
					clientSeries.ScreenHeight = pkg.ReadInt();
					clientSeries.Graphics = pkg.ReadString();
					clientSeries.GpuDetail = pkg.ReadString();
				}
				if (b >= 2)
				{
					ZPackage val = pkg.ReadCompressedPackage();
					int num = val.ReadInt();
					if (num < 0 || num > 20000)
					{
						return null;
					}
					for (int i = 0; i < num; i++)
					{
						clientSeries.Samples.Add(SampleWire.Read(val, b));
					}
					clientSeries.Full = true;
					clientSeries.FillSecondsFromSamples();
				}
				else
				{
					int num2 = pkg.ReadInt();
					if (num2 < 0 || num2 > 20000)
					{
						return null;
					}
					for (int j = 0; j < num2; j++)
					{
						clientSeries.Seconds.Add(new Second
						{
							UtcTicks = pkg.ReadLong(),
							FrameMaxMs = pkg.ReadSingle(),
							Stalls = pkg.ReadInt(),
							CpuMsPerSec = pkg.ReadSingle(),
							Collections = pkg.ReadInt()
						});
					}
				}
				try
				{
					clientSeries.ModVersion = pkg.ReadString();
				}
				catch
				{
					clientSeries.ModVersion = "";
				}
				try
				{
					clientSeries.RefreshHz = pkg.ReadInt();
				}
				catch
				{
					clientSeries.RefreshHz = 0;
				}
				return clientSeries;
			}
			catch (Exception ex)
			{
				DiagnoseServerLagMod.Log.LogWarning((object)("[DiagnoseServerLag] Could not read a client series: " + ex.Message));
				return null;
			}
		}

		private void FillSecondsFromSamples()
		{
			Seconds.Clear();
			foreach (Sample sample in Samples)
			{
				Seconds.Add(new Second
				{
					UtcTicks = sample.UtcTicks,
					FrameMaxMs = sample.FrameMsMax,
					Stalls = sample.Stalls,
					CpuMsPerSec = sample.CpuMsPerSec,
					Collections = Machine.Collections(sample)
				});
			}
		}

		internal void Recompute()
		{
			TotalStalls = 0;
			HasCpu = false;
			foreach (Sample sample in Samples)
			{
				if (sample.HasCpu)
				{
					HasCpu = true;
				}
				TotalStalls += sample.Stalls;
			}
			if (Samples.Count > 0)
			{
				OwnedAI = Samples[Samples.Count - 1].OwnedAI;
				NearbyAI = Samples[Samples.Count - 1].NearbyAI;
			}
			FrameMedianMs = Stats.Median(Samples, (Sample x) => x.FrameMsAvg);
			CpuMedianMsPerSec = Stats.Median(Samples, (Sample x) => x.CpuMsPerSec);
			FillSecondsFromSamples();
		}

		private static string LocalName()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				string playerName = localPlayer.GetPlayerName();
				if (!string.IsNullOrEmpty(playerName))
				{
					return playerName;
				}
			}
			if (!string.IsNullOrEmpty(Sampler.LastPlayerName))
			{
				return Sampler.LastPlayerName;
			}
			return "(no character)";
		}

		internal static ClientSeries FromLocal(int seconds)
		{
			List<Sample> list = Sampler.History.Recent(seconds);
			ClientSeries clientSeries = new ClientSeries
			{
				Uid = ZDOMan.GetSessionID(),
				Name = LocalName(),
				CpuName = SystemInfo.processorType,
				Cores = Machine.ProcessorCount,
				RoundTripMs = Mathf.RoundToInt(LagNetwork.RoundTripMs),
				ModVersion = "0.10.21"
			};
			foreach (Sample item in list)
			{
				if (item.HasCpu)
				{
					clientSeries.HasCpu = true;
				}
				clientSeries.TotalStalls += item.Stalls;
				clientSeries.Samples.Add(item);
			}
			clientSeries.Full = true;
			clientSeries.FillSecondsFromSamples();
			if (list.Count > 0)
			{
				clientSeries.OwnedAI = list[list.Count - 1].OwnedAI;
				clientSeries.NearbyAI = list[list.Count - 1].NearbyAI;
			}
			clientSeries.FrameMedianMs = Stats.Median(list, (Sample x) => x.FrameMsAvg);
			clientSeries.CpuMedianMsPerSec = Stats.Median(list, (Sample x) => x.CpuMsPerSec);
			Hardware.Refresh();
			clientSeries.Gpu = Hardware.Gpu;
			clientSeries.VramMB = Hardware.VramMB;
			clientSeries.RamMB = Hardware.RamMB;
			clientSeries.ScreenWidth = Hardware.ScreenWidth;
			clientSeries.ScreenHeight = Hardware.ScreenHeight;
			clientSeries.RefreshHz = Hardware.RefreshHz;
			clientSeries.Graphics = GraphicsConfig.Describe();
			clientSeries.GpuDetail = string.Join(", ", new string[4]
			{
				string.IsNullOrEmpty(Hardware.GpuVendor) ? null : Hardware.GpuVendor,
				string.IsNullOrEmpty(Hardware.GpuDriver) ? null : Hardware.GpuDriver,
				(Hardware.ShaderLevel > 0) ? $"shader {(float)Hardware.ShaderLevel / 10f:0.0}" : null,
				string.IsNullOrEmpty(Hardware.GraphicsApi) ? null : Hardware.GraphicsApi
			}.Where((string x) => x != null).ToArray());
			return clientSeries;
		}
	}
	internal static class Commands
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__0_0;

			public static ConsoleEvent <>9__0_1;

			public static ConsoleEvent <>9__0_2;

			public static ConsoleEvent <>9__0_3;

			public static ConsoleEvent <>9__0_4;

			public static ConsoleEvent <>9__0_5;

			public static ConsoleEvent <>9__0_6;

			public static ConsoleEvent <>9__0_7;

			public static ConsoleEvent <>9__0_8;

			public static ConsoleEvent <>9__0_9;

			public static Func<Sample, float> <>9__3_0;

			public static Func<Sample, float> <>9__3_1;

			public static Func<Sample, float> <>9__3_5;

			public static Func<Sample, float> <>9__3_6;

			public static Func<Sample, float> <>9__3_7;

			public static Func<Sample, float> <>9__3_8;

			public static Func<Sample, float> <>9__3_9;

			public static Func<Sample, float> <>9__3_10;

			public static Func<Sample, float> <>9__3_11;

			public static Func<Sample, float> <>9__3_12;

			public static Func<Sample, float> <>9__3_13;

			public static Func<Sample, float> <>9__3_14;

			public static Func<Sample, float> <>9__3_15;

			public static Func<Sample, float> <>9__3_16;

			public static Func<Sample, float> <>9__3_17;

			public static Func<Sample, float> <>9__3_18;

			public static Func<Sample, float> <>9__3_2;

			public static Func<Sample, float> <>9__3_3;

			public static Func<Sample, float> <>9__3_4;

			internal void <Register>b__0_0(ConsoleEventArgs args)
			{
				if ((Object)(object)Player.m_localPlayer == (Object)null)
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString("No character loaded.");
					}
				}
				else
				{
					LagPanel.Toggle();
				}
			}

			internal void <Register>b__0_1(ConsoleEventArgs args)
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine(Verdict.CoverageNote());
				foreach (Finding item in Verdict.Diagnose())
				{
					stringBuilder.AppendLine($"[{item.Confidence}%] {item.Headline}");
					foreach (string item2 in item.Evidence)
					{
						stringBuilder.AppendLine("    - " + item2);
					}
					if (!string.IsNullOrEmpty(item.Advice))
					{
						stringBuilder.AppendLine("    => " + item.Advice);
					}
				}
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString(stringBuilder.ToString().TrimEnd(Array.Empty<char>()));
				}
			}

			internal void <Register>b__0_2(ConsoleEventArgs args)
			{
				if (!Sampler.TryNewest(out var s))
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString("Nothing measured yet.");
					}
					return;
				}
				Terminal context2 = args.Context;
				if (context2 != null)
				{
					context2.AddString($"frames {s.FrameMsAvg:0.0} ms avg / {s.FrameMsMax:0} ms worst over {s.Frames} frames, {s.Stalls} stalls\n" + string.Format("ping {0}, quality {1:0.0}%/{2:0.0}%\n", s.HasPing ? (s.Ping + " ms") : "not measurable", s.LocalQuality * 100f, s.RemoteQuality * 100f) + "queue " + Stats.Bytes(s.SendQueue) + ", rate " + Stats.Bytes(s.SendRate) + "/s, in " + Stats.Bytes(s.InByteSec) + "/s, out " + Stats.Bytes(s.OutByteSec) + "/s\n" + $"objects {s.Zdos} known / {s.Instances} built, {s.ZdosSent}/s sent, {s.ZdosRecv}/s received, {s.ChangeQueue} unacknowledged\n" + $"peers {s.Peers}, history {Sampler.History.Count}s");
				}
			}

			internal void <Register>b__0_3(ConsoleEventArgs args)
			{
				string text = SimDistanceText();
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString(text);
				}
				DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] " + text.Replace("\n", " | ")));
			}

			internal void <Register>b__0_4(ConsoleEventArgs args)
			{
				ServerReport latest = LagNetwork.Latest;
				if (latest == null)
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString(Verdict.CoverageNote());
					}
					return;
				}
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine(Verdict.CoverageNote());
				stringBuilder.AppendLine($"tick {latest.TickMsAvg:0.0} ms now / {latest.BaselineTickMs:0.0} ms median over {latest.WindowSeconds}s, worst {latest.WorstTickMs:0} ms, {latest.StallsInWindow} stalls");
				stringBuilder.AppendLine(string.Format("world {0} objects, {1}/s sent, {2}/s received, {3} players, {4}", latest.Zdos, latest.ZdosSent, latest.ZdosRecv, latest.PeerCount, latest.Dedicated ? "dedicated" : "player-hosted"));
				stringBuilder.AppendLine("worst player queue " + Stats.Bytes(latest.WorstSendQueue) + ", total send rate " + Stats.Bytes(latest.TotalSendRate) + "/s");
				if (latest.PeerDetailWithheld)
				{
					stringBuilder.AppendLine("the per-player table was not shared with you");
				}
				foreach (PeerSample peer in latest.Peers)
				{
					string name = peer.Name;
					object arg;
					if (!peer.HasPing)
					{
						arg = "?";
					}
					else
					{
						int ping = peer.Ping;
						arg = ping + " ms";
					}
					stringBuilder.AppendLine($"  {name}: ping {arg}, quality {peer.Quality * 100f:0.0}%, " + $"queued {Stats.Bytes(peer.SendQueue)}, rate {Stats.Bytes(peer.SendRate)}/s, {peer.DistanceFromCenter:0} m from center");
				}
				Terminal context2 = args.Context;
				if (context2 != null)
				{
					context2.AddString(stringBuilder.ToString().TrimEnd(Array.Empty<char>()));
				}
			}

			internal void <Register>b__0_5(ConsoleEventArgs args)
			{
				string text = Dump();
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString((text == null) ? "Could not write the dump; see the log." : ("Wrote " + text));
				}
			}

			internal void <Register>b__0_6(ConsoleEventArgs args)
			{
				if (!ParseWindow(args, out var seconds, out var localOnly))
				{
					return;
				}
				string csvPath;
				string text = Bench(seconds, out csvPath);
				DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] bench\n" + text));
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString(text + ((csvPath == null) ? "" : ("\nwrote " + csvPath)));
				}
				if (localOnly || Sampler.IsServerHere)
				{
					return;
				}
				if (LagNetwork.Module == ServerModule.Absent)
				{
					Terminal context2 = args.Context;
					if (context2 != null)
					{
						context2.AddString("The server is not running this mod, so only this machine was captured.");
					}
					return;
				}
				LagNetwork.AskCapture(seconds);
				Terminal context3 = args.Context;
				if (context3 != null)
				{
					context3.AddString("Also asked the server for a group capture; its reply prints here when it arrives.");
				}
			}

			internal void <Register>b__0_7(ConsoleEventArgs args)
			{
				if (!Machine.Readable)
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString("Process counters unavailable: " + Machine.UnreadableReason);
					}
					return;
				}
				if (!Sampler.TryNewest(out var s) || !s.HasCpu)
				{
					Terminal context2 = args.Context;
					if (context2 != null)
					{
						context2.AddString("Nothing measured yet.");
					}
					return;
				}
				Terminal context3 = args.Context;
				if (context3 != null)
				{
					context3.AddString($"CPU {s.CpuMsPerSec:0} ms/s = {Machine.CoreShare(s.CpuMsPerSec) * 100f:0.0}% of one core, " + $"{Machine.MachineShare(s.CpuMsPerSec) * 100f:0.0}% of {Machine.ProcessorCount} cores\n" + $"headroom about {Machine.Headroom(s.CpuMsPerSec):0.0}x the current load before one core is full\n" + $"collections {Machine.Collections(s)} this second" + (Machine.GenerationsDistinct ? $" ({s.Gc0}/{s.Gc1}/{s.Gc2} by generation)" : " (generations not separated)") + ", heap " + Stats.Bytes(s.HeapBytes) + (Machine.HasWorkingSet ? (", working set " + Stats.Bytes(s.WorkingSetBytes)) : ", working set not measurable"));
				}
			}

			internal void <Register>b__0_8(ConsoleEventArgs args)
			{
				string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "");
				switch (text)
				{
				case "start":
				case "on":
				{
					Terminal context11 = args.Context;
					if (context11 != null)
					{
						context11.AddString(Hotspots.Start());
					}
					break;
				}
				case "off":
				case "stop":
				{
					Terminal context2 = args.Context;
					if (context2 != null)
					{
						context2.AddString(Hotspots.Stop());
					}
					break;
				}
				case "who":
				case "patches":
				{
					string text6 = Hotspots.Patches().TrimEnd(Array.Empty<char>());
					Terminal context10 = args.Context;
					if (context10 != null)
					{
						context10.AddString(text6);
					}
					DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] hotspot patches\n" + text6));
					break;
				}
				case "mods":
				{
					string text5 = Hotspots.Mods((args.Length > 2) ? args[2] : "");
					Terminal context7 = args.Context;
					if (context7 != null)
					{
						context7.AddString(text5);
					}
					break;
				}
				case "drill":
				{
					if (args.Length < 3)
					{
						Terminal context4 = args.Context;
						if (context4 != null)
						{
							context4.AddString("dsl_hotspots drill needs a method, as Type.Method - for example 'dsl_hotspots drill Hud.Update'. Run 'dsl_hotspots patches' to see which ones have anything worth drilling into.");
						}
						break;
					}
					string text3 = args[2];
					int num = text3.LastIndexOf('.');
					if (num <= 0 || num == text3.Length - 1)
					{
						Terminal context5 = args.Context;
						if (context5 != null)
						{
							context5.AddString("'" + text3 + "' is not Type.Method - try 'dsl_hotspots drill Hud.Update'.");
						}
						break;
					}
					string text4 = Hotspots.Drill(text3.Substring(0, num), text3.Substring(num + 1));
					Terminal context6 = args.Context;
					if (context6 != null)
					{
						context6.AddString(text4);
					}
					break;
				}
				case "reset":
				{
					if (!Hotspots.Running)
					{
						Terminal context8 = args.Context;
						if (context8 != null)
						{
							context8.AddString("Hotspot timing is off. Run: dsl_hotspots on");
						}
						break;
					}
					Hotspots.Reset();
					Terminal context9 = args.Context;
					if (context9 != null)
					{
						context9.AddString("counters reset - play for a few minutes, then dsl_hotspots");
					}
					break;
				}
				case "":
				{
					string text2 = Hotspots.Report().TrimEnd(Array.Empty<char>());
					Terminal context3 = args.Context;
					if (context3 != null)
					{
						context3.AddString(text2);
					}
					DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] hotspots\n" + text2));
					break;
				}
				default:
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString("dsl_hotspots takes 'on', 'off', 'reset', 'patches', 'drill <Type>.<Method>', 'mods [name]', or nothing at all to read the current window. '" + text + "' is none of those, so nothing was changed.");
					}
					break;
				}
				}
			}

			internal void <Register>b__0_9(ConsoleEventArgs args)
			{
				Sampler.Reset();
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString("History cleared. The baseline rebuilds over the next minute.");
				}
			}

			internal float <Bench>b__3_0(Sample x)
			{
				return x.FrameMsAvg;
			}

			internal float <Bench>b__3_1(Sample x)
			{
				return x.FrameMsMax;
			}

			internal float <Bench>b__3_5(Sample x)
			{
				return x.GpuMs;
			}

			internal float <Bench>b__3_6(Sample x)
			{
				return x.CpuMainMs;
			}

			internal float <Bench>b__3_7(Sample x)
			{
				return x.CpuRenderMs;
			}

			internal float <Bench>b__3_8(Sample x)
			{
				return x.MainCpuMsPerSec;
			}

			internal float <Bench>b__3_9(Sample x)
			{
				return x.Frames;
			}

			internal float <Bench>b__3_10(Sample x)
			{
				return x.CpuMsPerSec;
			}

			internal float <Bench>b__3_11(Sample x)
			{
				return x.CpuMsPerSec;
			}

			internal float <Bench>b__3_12(Sample x)
			{
				return x.Gc0;
			}

			internal float <Bench>b__3_13(Sample x)
			{
				return x.Gc1;
			}

			internal float <Bench>b__3_14(Sample x)
			{
				return x.Gc2;
			}

			internal float <Bench>b__3_15(Sample x)
			{
				return Machine.Collections(x);
			}

			internal float <Bench>b__3_16(Sample x)
			{
				return x.HeapBytes;
			}

			internal float <Bench>b__3_17(Sample x)
			{
				return x.WorkingSetBytes;
			}

			internal float <Bench>b__3_18(Sample x)
			{
				return x.HeapBytes;
			}

			internal float <Bench>b__3_2(Sample x)
			{
				return x.ZdosSent;
			}

			internal float <Bench>b__3_3(Sample x)
			{
				return x.ZdosRecv;
			}

			internal float <Bench>b__3_4(Sample x)
			{
				return x.SendQueue;
			}
		}

		internal static void Register()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			//IL_00de: 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_00d4: Expected O, but got Unknown
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Expected O, but got Unknown
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Expected O, but got Unknown
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Expected O, but got Unknown
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Expected O, but got Unknown
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Expected O, but got Unknown
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Expected O, but got Unknown
			object obj = <>c.<>9__0_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					if ((Object)(object)Player.m_localPlayer == (Object)null)
					{
						Terminal context = args.Context;
						if (context != null)
						{
							context.AddString("No character loaded.");
						}
					}
					else
					{
						LagPanel.Toggle();
					}
				};
				<>c.<>9__0_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("dsl", "Diagnose Server Lag: open or close the lag report", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj2 = <>c.<>9__0_1;
			if (obj2 == null)
			{
				ConsoleEvent val2 = delegate(ConsoleEventArgs args)
				{
					StringBuilder stringBuilder = new StringBuilder();
					stringBuilder.AppendLine(Verdict.CoverageNote());
					foreach (Finding item in Verdict.Diagnose())
					{
						stringBuilder.AppendLine($"[{item.Confidence}%] {item.Headline}");
						foreach (string item2 in item.Evidence)
						{
							stringBuilder.AppendLine("    - " + item2);
						}
						if (!string.IsNullOrEmpty(item.Advice))
						{
							stringBuilder.AppendLine("    => " + item.Advice);
						}
					}
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString(stringBuilder.ToString().TrimEnd(Array.Empty<char>()));
					}
				};
				<>c.<>9__0_1 = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("dsl_why", "Diagnose Server Lag: print the verdict and its evidence", (ConsoleEvent)obj2, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj3 = <>c.<>9__0_2;
			if (obj3 == null)
			{
				ConsoleEvent val3 = delegate(ConsoleEventArgs args)
				{
					if (!Sampler.TryNewest(out var s))
					{
						Terminal context = args.Context;
						if (context != null)
						{
							context.AddString("Nothing measured yet.");
						}
					}
					else
					{
						Terminal context2 = args.Context;
						if (context2 != null)
						{
							context2.AddString($"frames {s.FrameMsAvg:0.0} ms avg / {s.FrameMsMax:0} ms worst over {s.Frames} frames, {s.Stalls} stalls\n" + string.Format("ping {0}, quality {1:0.0}%/{2:0.0}%\n", s.HasPing ? (s.Ping + " ms") : "not measurable", s.LocalQuality * 100f, s.RemoteQuality * 100f) + "queue " + Stats.Bytes(s.SendQueue) + ", rate " + Stats.Bytes(s.SendRate) + "/s, in " + Stats.Bytes(s.InByteSec) + "/s, out " + Stats.Bytes(s.OutByteSec) + "/s\n" + $"objects {s.Zdos} known / {s.Instances} built, {s.ZdosSent}/s sent, {s.ZdosRecv}/s received, {s.ChangeQueue} unacknowledged\n" + $"peers {s.Peers}, history {Sampler.History.Count}s");
						}
					}
				};
				<>c.<>9__0_2 = val3;
				obj3 = (object)val3;
			}
			new ConsoleCommand("dsl_now", "Diagnose Server Lag: the last second measured on this machine", (ConsoleEvent)obj3, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj4 = <>c.<>9__0_3;
			if (obj4 == null)
			{
				ConsoleEvent val4 = delegate(ConsoleEventArgs args)
				{
					string text = SimDistanceText();
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString(text);
					}
					DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] " + text.Replace("\n", " | ")));
				};
				<>c.<>9__0_3 = val4;
				obj4 = (object)val4;
			}
			new ConsoleCommand("dsl_sim", "Diagnose Server Lag: the draw distance actually in effect, not the one requested", (ConsoleEvent)obj4, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj5 = <>c.<>9__0_4;
			if (obj5 == null)
			{
				ConsoleEvent val5 = delegate(ConsoleEventArgs args)
				{
					ServerReport latest = LagNetwork.Latest;
					if (latest == null)
					{
						Terminal context = args.Context;
						if (context != null)
						{
							context.AddString(Verdict.CoverageNote());
						}
					}
					else
					{
						StringBuilder stringBuilder = new StringBuilder();
						stringBuilder.AppendLine(Verdict.CoverageNote());
						stringBuilder.AppendLine($"tick {latest.TickMsAvg:0.0} ms now / {latest.BaselineTickMs:0.0} ms median over {latest.WindowSeconds}s, worst {latest.WorstTickMs:0} ms, {latest.StallsInWindow} stalls");
						stringBuilder.AppendLine(string.Format("world {0} objects, {1}/s sent, {2}/s received, {3} players, {4}", latest.Zdos, latest.ZdosSent, latest.ZdosRecv, latest.PeerCount, latest.Dedicated ? "dedicated" : "player-hosted"));
						stringBuilder.AppendLine("worst player queue " + Stats.Bytes(latest.WorstSendQueue) + ", total send rate " + Stats.Bytes(latest.TotalSendRate) + "/s");
						if (latest.PeerDetailWithheld)
						{
							stringBuilder.AppendLine("the per-player table was not shared with you");
						}
						foreach (PeerSample peer in latest.Peers)
						{
							string name = peer.Name;
							object arg;
							if (!peer.HasPing)
							{
								arg = "?";
							}
							else
							{
								int ping = peer.Ping;
								arg = ping + " ms";
							}
							stringBuilder.AppendLine($"  {name}: ping {arg}, quality {peer.Quality * 100f:0.0}%, " + $"queued {Stats.Bytes(peer.SendQueue)}, rate {Stats.Bytes(peer.SendRate)}/s, {peer.DistanceFromCenter:0} m from center");
						}
						Terminal context2 = args.Context;
						if (context2 != null)
						{
							context2.AddString(stringBuilder.ToString().TrimEnd(Array.Empty<char>()));
						}
					}
				};
				<>c.<>9__0_4 = val5;
				obj5 = (object)val5;
			}
			new ConsoleCommand("dsl_server", "Diagnose Server Lag: what the server last reported about itself", (ConsoleEvent)obj5, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj6 = <>c.<>9__0_5;
			if (obj6 == null)
			{
				ConsoleEvent val6 = delegate(ConsoleEventArgs args)
				{
					string text = Dump();
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString((text == null) ? "Could not write the dump; see the log." : ("Wrote " + text));
					}
				};
				<>c.<>9__0_5 = val6;
				obj6 = (object)val6;
			}
			new ConsoleCommand("dsl_dump", "Diagnose Server Lag: write the measured seconds to a CSV", (ConsoleEvent)obj6, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj7 = <>c.<>9__0_6;
			if (obj7 == null)
			{
				ConsoleEvent val7 = delegate(ConsoleEventArgs args)
				{
					if (ParseWindow(args, out var seconds, out var localOnly))
					{
						string csvPath;
						string text = Bench(seconds, out csvPath);
						DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] bench\n" + text));
						Terminal context = args.Context;
						if (context != null)
						{
							context.AddString(text + ((csvPath == null) ? "" : ("\nwrote " + csvPath)));
						}
						if (!localOnly && !Sampler.IsServerHere)
						{
							if (LagNetwork.Module == ServerModule.Absent)
							{
								Terminal context2 = args.Context;
								if (context2 != null)
								{
									context2.AddString("The server is not running this mod, so only this machine was captured.");
								}
							}
							else
							{
								LagNetwork.AskCapture(seconds);
								Terminal context3 = args.Context;
								if (context3 != null)
								{
									context3.AddString("Also asked the server for a group capture; its reply prints here when it arrives.");
								}
							}
						}
					}
				};
				<>c.<>9__0_6 = val7;
				obj7 = (object)val7;
			}
			ConsoleEvent val8 = (ConsoleEvent)obj7;
			new ConsoleCommand("dsl_bench", "Diagnose Server Lag: capture the last N minutes (default 2). 30 is half an hour, 90s a tight window, 'local' skips asking the server", val8, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj8 = <>c.<>9__0_7;
			if (obj8 == null)
			{
				ConsoleEvent val9 = delegate(ConsoleEventArgs args)
				{
					Sample s;
					if (!Machine.Readable)
					{
						Terminal context = args.Context;
						if (context != null)
						{
							context.AddString("Process counters unavailable: " + Machine.UnreadableReason);
						}
					}
					else if (!Sampler.TryNewest(out s) || !s.HasCpu)
					{
						Terminal context2 = args.Context;
						if (context2 != null)
						{
							context2.AddString("Nothing measured yet.");
						}
					}
					else
					{
						Terminal context3 = args.Context;
						if (context3 != null)
						{
							context3.AddString($"CPU {s.CpuMsPerSec:0} ms/s = {Machine.CoreShare(s.CpuMsPerSec) * 100f:0.0}% of one core, " + $"{Machine.MachineShare(s.CpuMsPerSec) * 100f:0.0}% of {Machine.ProcessorCount} cores\n" + $"headroom about {Machine.Headroom(s.CpuMsPerSec):0.0}x the current load before one core is full\n" + $"collections {Machine.Collections(s)} this second" + (Machine.GenerationsDistinct ? $" ({s.Gc0}/{s.Gc1}/{s.Gc2} by generation)" : " (generations not separated)") + ", heap " + Stats.Bytes(s.HeapBytes) + (Machine.HasWorkingSet ? (", working set " + Stats.Bytes(s.WorkingSetBytes)) : ", working set not measurable"));
						}
					}
				};
				<>c.<>9__0_7 = val9;
				obj8 = (object)val9;
			}
			new ConsoleCommand("dsl_cpu", "Diagnose Server Lag: what this process is costing the machine", (ConsoleEvent)obj8, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj9 = <>c.<>9__0_8;
			if (obj9 == null)
			{
				ConsoleEvent val10 = delegate(ConsoleEventArgs args)
				{
					string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "");
					switch (text)
					{
					case "start":
					case "on":
					{
						Terminal context11 = args.Context;
						if (context11 != null)
						{
							context11.AddString(Hotspots.Start());
						}
						break;
					}
					case "off":
					case "stop":
					{
						Terminal context2 = args.Context;
						if (context2 != null)
						{
							context2.AddString(Hotspots.Stop());
						}
						break;
					}
					case "who":
					case "patches":
					{
						string text6 = Hotspots.Patches().TrimEnd(Array.Empty<char>());
						Terminal context10 = args.Context;
						if (context10 != null)
						{
							context10.AddString(text6);
						}
						DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] hotspot patches\n" + text6));
						break;
					}
					case "mods":
					{
						string text5 = Hotspots.Mods((args.Length > 2) ? args[2] : "");
						Terminal context7 = args.Context;
						if (context7 != null)
						{
							context7.AddString(text5);
						}
						break;
					}
					case "drill":
						if (args.Length < 3)
						{
							Terminal context4 = args.Context;
							if (context4 != null)
							{
								context4.AddString("dsl_hotspots drill needs a method, as Type.Method - for example 'dsl_hotspots drill Hud.Update'. Run 'dsl_hotspots patches' to see which ones have anything worth drilling into.");
							}
						}
						else
						{
							string text3 = args[2];
							int num = text3.LastIndexOf('.');
							if (num <= 0 || num == text3.Length - 1)
							{
								Terminal context5 = args.Context;
								if (context5 != null)
								{
									context5.AddString("'" + text3 + "' is not Type.Method - try 'dsl_hotspots drill Hud.Update'.");
								}
							}
							else
							{
								string text4 = Hotspots.Drill(text3.Substring(0, num), text3.Substring(num + 1));
								Terminal context6 = args.Context;
								if (context6 != null)
								{
									context6.AddString(text4);
								}
							}
						}
						break;
					case "reset":
						if (!Hotspots.Running)
						{
							Terminal context8 = args.Context;
							if (context8 != null)
							{
								context8.AddString("Hotspot timing is off. Run: dsl_hotspots on");
							}
						}
						else
						{
							Hotspots.Reset();
							Terminal context9 = args.Context;
							if (context9 != null)
							{
								context9.AddString("counters reset - play for a few minutes, then dsl_hotspots");
							}
						}
						break;
					case "":
					{
						string text2 = Hotspots.Report().TrimEnd(Array.Empty<char>());
						Terminal context3 = args.Context;
						if (context3 != null)
						{
							context3.AddString(text2);
						}
						DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] hotspots\n" + text2));
						break;
					}
					default:
					{
						Terminal context = args.Context;
						if (context != null)
						{
							context.AddString("dsl_hotspots takes 'on', 'off', 'reset', 'patches', 'drill <Type>.<Method>', 'mods [name]', or nothing at all to read the current window. '" + text + "' is none of those, so nothing was changed.");
						}
						break;
					}
					}
				};
				<>c.<>9__0_8 = val10;
				obj9 = (object)val10;
			}
			new ConsoleCommand("dsl_hotspots", "Diagnose Server Lag: time the main thread per method ('on', 'off', 'reset', 'patches', 'drill Type.Method', or nothing to read it)", (ConsoleEvent)obj9, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj10 = <>c.<>9__0_9;
			if (obj10 == null)
			{
				ConsoleEvent val11 = delegate(ConsoleEventArgs args)
				{
					Sampler.Reset();
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString("History cleared. The baseline rebuilds over the next minute.");
					}
				};
				<>c.<>9__0_9 = val11;
				obj10 = (object)val11;
			}
			new ConsoleCommand("dsl_reset", "Diagnose Server Lag: forget the measurements and start again", (ConsoleEvent)obj10, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private static bool ParseWindow(ConsoleEventArgs args, out int seconds, out bool localOnly)
		{
			int capacity = Sampler.History.Capacity;
			int num = capacity / 60;
			seconds = 120;
			localOnly = false;
			for (int i = 1; i < args.Length; i++)
			{
				string text = args[i];
				if (string.IsNullOrEmpty(text))
				{
					continue;
				}
				if (string.Equals(text, "local", StringComparison.OrdinalIgnoreCase))
				{
					localOnly = true;
					continue;
				}
				int num2 = text.Length;
				while (num2 > 0 && !char.IsDigit(text[num2 - 1]))
				{
					num2--;
				}
				string s = text.Substring(0, num2);
				string text2 = text.Substring(num2).ToLowerInvariant();
				if (!int.TryParse(s, out var result) || result <= 0)
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString("dsl_bench: " + text + " is not a duration. Give minutes (dsl_bench 30), or seconds with an s (dsl_bench 90s).");
					}
					return false;
				}
				int num3;
				if (text2.Length == 0)
				{
					num3 = 60;
				}
				else
				{
					switch (text2)
					{
					case "m":
					case "min":
					case "mins":
					case "minute":
					case "minutes":
						num3 = 60;
						break;
					case "s":
					case "sec":
					case "secs":
					case "second":
					case "seconds":
						num3 = 1;
						break;
					default:
					{
						Terminal context2 = args.Context;
						if (context2 != null)
						{
							context2.AddString("dsl_bench: " + text2 + " is not a unit. Use m for minutes or s for seconds.");
						}
						return false;
					}
					}
				}
				if (text2.Length == 0 && result > num)
				{
					Terminal context3 = args.Context;
					if (context3 != null)
					{
						context3.AddString("dsl_bench takes minutes now, and " + result + " minutes is more than the " + num + " this client keeps. If you meant seconds - this command used to take them - " + result + "s is " + result / 60 + " minutes, or ask for " + num + " to get everything there is. Nothing captured.");
					}
					return false;
				}
				long num4 = (long)result * (long)num3;
				if (num4 < 5)
				{
					num4 = 5L;
				}
				if (num4 > capacity)
				{
					num4 = capacity;
				}
				seconds = (int)num4;
			}
			return true;
		}

		internal static string SimDistanceText()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return "Not in a world.";
			}
			try
			{
				SimulationDistance syncedSimulationDistance = instance.GetSyncedSimulationDistance();
				int nearSimulationDistance = ((SimulationDistance)(ref syncedSimulationDistance)).NearSimulationDistance;
				float num = ((float)nearSimulationDistance + 2.5f) * 64f;
				int num2 = -1;
				GraphicsSettingsManager instance2 = GraphicsSettingsManager.Instance;
				if ((Object)(object)instance2 != (Object)null)
				{
					SimulationDistance simulationDistance = instance2.GetSimulationDistance();
					num2 = ((SimulationDistance)(ref simulationDistance)).NearSimulationDistance;
				}
				string text = $"draw distance in effect: near {nearSimulationDistance}, far {((SimulationDistance)(ref syncedSimulationDistance)).FarSimulationDistance}" + (((SimulationDistance)(ref syncedSimulationDistance)).IsClassic ? " (classic)" : "") + $" - about {num:0} m";
				if (num2 >= 0 && num2 != nearSimulationDistance)
				{
					return text + "\n" + $"the menu is asking for {num2} (about {((float)num2 + 2.5f) * 64f:0} m) and not getting it: " + "the server's ceiling is lower. Raise it there, then change any graphics setting or reconnect - the request is only re-sent when something changes.";
				}
				if (num2 >= 0)
				{
					return text + "\nthe menu asked for this and got it.";
				}
				return text;
			}
			catch (Exception ex)
			{
				return "Could not read the draw distance: " + ex.Message;
			}
		}

		internal static string Bench(int seconds, out string csvPath)
		{
			csvPath = null;
			List<Sample> list = Sampler.History.Recent(seconds);
			if (list.Count == 0)
			{
				return "Nothing measured yet.";
			}
			StringBuilder stringBuilder = new StringBuilder();
			bool isDedicatedHere = Sampler.IsDedicatedHere;
			bool isServerHere = Sampler.IsServerHere;
			string arg = (isDedicatedHere ? "dedicated server" : (isServerHere ? "host (also a client)" : "client"));
			float num = Stats.Median(list, (Sample x) => x.FrameMsAvg);
			float num2 = Stats.Max(list, (Sample x) => x.FrameMsMax);
			int num3 = 0;
			foreach (Sample item in list)
			{
				num3 += item.Stalls;
			}
			bool flag = num3 == 0 && num2 <= num * DslConfig.SteadyTickRatio.Value + 2f;
			Sampler.TryNewest(out var s);
			stringBuilder.AppendLine(string.Format("DiagnoseServerLag {0} bench - last {1}s", "0.10.21", list.Count));
			stringBuilder.AppendLine($"  role            {arg}, {Machine.ProcessorCount} cores");
			stringBuilder.AppendLine($"  world           {s.Zdos} objects, {s.Peers} players connected");
			string[] array = SimDistanceText().Split(new char[1] { '\n' });
			foreach (string text in array)
			{
				stringBuilder.AppendLine("  draw distance   " + text);
			}
			stringBuilder.AppendLine($"  tick            {num:0.0} ms median, {num2:0} ms worst, {num3} stalls" + (flag ? "   (capped - not a load measurement)" : ""));
			if (list.Count > 0)
			{
				List<GraphicsWatch.Change> list2 = GraphicsWatch.Since(list[0].UtcTicks);
				if (list2.Count > 0)
				{
					stringBuilder.AppendLine($"  SETTINGS MOVED  {list2.Count} change(s) inside this window - it is not one configuration");
					foreach (GraphicsWatch.Change item2 in list2)
					{
						stringBuilder.AppendLine("                  " + CaptureCsv.Iso(item2.UtcTicks) + "  " + item2.What);
					}
				}
			}
			if (FrameTimings.Available == true)
			{
				float num5 = Stats.Median(list, (Sample x) => x.GpuMs);
				float num6 = Stats.Median(list, (Sample x) => x.CpuMainMs);
				float num7 = Stats.Median(list, (Sample x) => x.CpuRenderMs);
				string text2 = ((num5 > num6 * 1.15f) ? "graphics card" : ((num6 > num5 * 1.15f) ? "main thread" : "neither clearly"));
				stringBuilder.AppendLine($"  frame split     GPU {num5:0.0} ms, main thread {num6:0.0} ms, render thread {num7:0.0} ms   (limited by the {text2})");
			}
			else
			{
				float num8 = Stats.Median(list, (Sample x) => x.MainCpuMsPerSec);
				float num9 = Stats.Median(list, (Sample x) => x.Frames);
				if (num8 >= 0f && num9 > 0f)
				{
					float num10 = num8 / num9;
					float num11 = num - num10;
					if (num11 < 0f)
					{
						num11 = 0f;
					}
					string text3 = ((num10 >= num * 0.8f) ? "computing - turning graphics settings down will not help much" : ((num11 >= num * 0.5f) ? "waiting, not computing - a graphics or display limit, so settings and resolution are the lever" : "part computing, part waiting"));
					stringBuilder.AppendLine($"  frame split     main thread computed {num10:0.0} ms of the {num:0.0} ms frame, waited {num11:0.0} ms");
					stringBuilder.AppendLine("                  mostly " + text3);
				}
			}
			bool flag2 = false;
			foreach (Sample item3 in list)
			{
				if (item3.HasCpu)
				{
					flag2 = true;
					break;
				}
			}
			if (Machine.Readable && flag2)
			{
				float num12 = Stats.Median(list, (Sample x) => x.CpuMsPerSec);
				float num13 = Stats.Max(list, (Sample x) => x.CpuMsPerSec);
				stringBuilder.AppendLine($"  CPU             {num12:0} ms/s = {Machine.CoreShare(num12) * 100f:0.0}% of one core, {Machine.MachineShare(num12) * 100f:0.0}% of the machine");
				stringBuilder.AppendLine($"  CPU peak        {num13:0} ms/s = {Machine.CoreShare(num13) * 100f:0.0}% of one core");
				stringBuilder.AppendLine($"  headroom        about {Machine.Headroom(num12):0.0}x the current load before one core is full");
				stringBuilder.AppendLine(Machine.GenerationsDistinct ? $"  GC per minute   {Stats.Mean(list, (Sample x) => x.Gc0) * 60f:0} gen0, {Stats.Mean(list, (Sample x) => x.Gc1) * 60f:0} gen1, {Stats.Mean(list, (Sample x) => x.Gc2) * 60f:0.0} gen2" : $"  GC per minute   {Stats.Mean(list, (Sample x) => Machine.Collections(x)) * 60f:0.0} (generations not separated by this runtime)");
				stringBuilder.AppendLine(Machine.HasWorkingSet ? ("  memory          heap " + Stats.Bytes(Stats.Median(list, (Sample x) => x.HeapBytes)) + ", working set " + Stats.Bytes(Stats.Median(list, (Sample x) => x.WorkingSetBytes))) : ("  memory          heap " + Stats.Bytes(Stats.Median(list, (Sample x) => x.HeapBytes)) + ", working set not measurable"));
			}
			else
			{
				string text4 = (string.IsNullOrEmpty(Machine.UnreadableReason) ? "" : (" (" + Machine.UnreadableReason + ")"));
				stringBuilder.AppendLine("  CPU             not measurable" + text4);
			}
			Sampler.TryNewest(out var s2);
			if (s2.NearbyAI > 0)
			{
				stringBuilder.AppendLine($"  simulating      {s2.OwnedAI} of {s2.NearbyAI} creatures loaded nearby");
			}
			stringBuilder.AppendLine($"  object traffic  {Stats.Median(list, (Sample x) => x.ZdosSent):0}/s out, {Stats.Median(list, (Sample x) => x.ZdosRecv):0}/s in");
			if (isServerHere)
			{
				stringBuilder.AppendLine("  worst queue     " + Stats.Bytes(Stats.Max(list, (Sample x) => x.SendQueue)));
			}
			csvPath = Dump(seconds);
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}

		internal static string Dump(int seconds = 0)
		{
			try
			{
				string text = Path.Combine(Paths.ConfigPath, "DiagnoseServerLag");
				Directory.CreateDirectory(text);
				string text2 = (Sampler.IsDedicatedHere ? "dedicated" : (Sampler.IsServerHere ? "host" : "client"));
				string text3 = Path.Combine(text, $"lag-{text2}-{DateTime.Now:yyyyMMdd-HHmmss}.csv");
				List<Sample> list = ((seconds > 0) ? Sampler.History.Recent(seconds) : Sampler.History.Recent(Sampler.History.Capacity));
				CultureInfo invariantCulture = CultureInfo.InvariantCulture;
				StringBuilder stringBuilder = new StringBuilder();
				Sampler.TryNewest(out var s);
				stringBuilder.AppendLine("# mod,0.10.21");
				stringBuilder.AppendLine($"# captured,{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
				stringBuilder.AppendLine("# role," + text2);
				stringBuilder.AppendLine($"# cores,{Machine.ProcessorCount}");
				stringBuilder.AppendLine("# os," + SystemInfo.operatingSystem);
				stringBuilder.AppendLine("# cpu_name," + SystemInfo.processorType);
				stringBuilder.AppendLine($"# seconds,{list.Count}");
				stringBuilder.AppendLine($"# zdos,{s.Zdos}");
				stringBuilder.AppendLine($"# peers,{s.Peers}");
				stringBuilder.AppendLine($"# cpu_readable,{(Machine.Readable ? 1 : 0)}");
				stringBuilder.AppendLine($"# gc_generations_distinct,{(Machine.GenerationsDistinct ? 1 : 0)}");
				stringBuilder.AppendLine($"# working_set_readable,{(Machine.HasWorkingSet ? 1 : 0)}");
				stringBuilder.AppendLine("#");
				stringBuilder.AppendLine("utc,machine,second,frame_avg_ms,frame_max_ms,stalls,frames,ping_ms,ping_measured,ping_round_trip,quality_local,quality_remote,in_bytes_sec,out_bytes_sec,send_queue_bytes,send_rate_bytes_sec,zdos,instances,zdos_sent_sec,zdos_recv_sec,change_queue,peers,cpu_ms_per_sec,cpu_measured,gc0,gc1,gc2,collections,heap_bytes,working_set_bytes,owned_ai,nearby_ai,feed_ms,owned_objects,nearby_objects,unowned_objects,unowned_ai,system_cpu_pct,nearby_pieces,loading,free_memory_mb,terrain_zones,terrain_bytes,gpu_ms,cpu_main_ms,cpu_render_ms,main_cpu_ms_sec,settings_rev");
				foreach (Sample item in list)
				{
					stringBuilder.AppendLine(CaptureCsv.Row(text2, item, invariantCulture));
				}
				File.WriteAllText(text3, stringBuilder.ToString());
				DiagnoseServerLagMod.Log.LogInfo((object)$"[DiagnoseServerLag] Wrote {list.Count} seconds to {text3}");
				return text3;
			}
			catch (Exception arg)
			{
				DiagnoseServerLagMod.Log.LogError((object)$"[DiagnoseServerLag] Could not write the dump: {arg}");
				return null;
			}
		}
	}
	[BepInPlugin("DeathMonger.DiagnoseServerLag", "Diagnose Server Lag", "0.10.21")]
	public class DiagnoseServerLagMod : BaseUnityPlugin
	{
		public const string ModGuid = "DeathMonger.DiagnoseServerLag";

		public const string ModName = "Diagnose Server Lag";

		public const string ModVersion = "0.10.21";

		internal static ConfigEntry<bool> ModEnabled;

		private readonly Harmony _harmony = new Harmony("DeathMonger.DiagnoseServerLag");

		private bool _wasInWorld;

		internal static DiagnoseServerLagMod Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Mod Enabled", true, "Master toggle for the entire mod. Set to false to disable the measurements, the report and the console commands without removing the DLL. Requires a game restart to take effect.");
			if (!ModEnabled.Value)
			{
				Log.LogInfo((object)"[DiagnoseServerLag] Mod Enabled = false in config; measuring nothing.");
				return;
			}
			DslConfig.Bind(this);
			Sampler.ApplyCapacity(DslConfig.HistoryMinutes.Value * 60);
			Commands.Register();
			_harmony.PatchAll();
			Log.LogInfo((object)"[DiagnoseServerLag] 0.10.21 loaded.");
		}

		private void Update()
		{
			if (ModEnabled.Value)
			{
				bool flag = (Object)(object)ZNet.instance != (Object)null;
				if (_wasInWorld && !flag)
				{
					Sampler.Reset();
					LagNetwork.Reset();
					LagPanel.Close();
					LagPause.Release();
				}
				_wasInWorld = flag;
				GraphicsWatch.Hook();
				Sampler.Tick();
				LagNetwork.Update();
				if ((Object)(object)Player.m_localPlayer == (Object)null)
				{
					LagPanel.Close();
					LagPause.Release();
				}
				else
				{
					LagPanel.Update();
					LagHud.Update();
					LagPause.Refresh();
				}
			}
		}

		private void OnDestroy()
		{
			LagPause.Release();
			_harmony.UnpatchSelf();
		}

		internal ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()));
		}

		internal ConfigEntry<KeyboardShortcut> BindKey(string section, string key, KeyboardShortcut defaultValue, string description, params KeyboardShortcut[] supersededDefaults)
		{
			//IL_0008: 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_001b: Expected O, but got Unknown
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<KeyboardShortcut> val = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()));
			foreach (KeyboardShortcut val2 in supersededDefaults)
			{
				if (((object)val.Value/*cast due to .constrained prefix*/).Equals((object?)val2))
				{
					val.Value = defaultValue;
					Log.LogInfo((object)$"[DiagnoseServerLag] Config '{key}' still held the old default {val2}; moved it to {defaultValue}.");
					break;
				}
			}
			return val;
		}

		internal ConfigEntry<float> BindRange(string section, string key, float defaultValue, float min, float max, string description)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			return ((BaseUnityPlugin)this).Config.Bind<float>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>()));
		}

		internal ConfigEntry<int> BindRangeInt(string section, string key, int defaultValue, int min, int max, string description)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			return ((BaseUnityPlugin)this).Config.Bind<int>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(min, max), Array.Empty<object>()));
		}

		internal static void Message(string text)
		{
			if (!((Object)(object)MessageHud.instance == (Object)null))
			{
				MessageHud.instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
			}
		}
	}
	internal static class DslConfig
	{
		internal static ConfigEntry<KeyboardShortcut> OpenKey;

		internal static ConfigEntry<bool> ShowHud;

		internal static ConfigEntry<KeyboardShortcut> HudKey;

		internal static ConfigEntry<KeyboardShortcut> OwnerNamesKey;

		internal static ConfigEntry<bool> ShowOwnerNames;

		internal static ConfigEntry<bool> ShowMyOwnOwnership;

		internal static ConfigEntry<LagHud.Corner> HudPosition;

		internal static ConfigEntry<float> HudX;

		internal static ConfigEntry<float> HudY;

		internal static ConfigEntry<string> PanelSize;

		internal static ConfigEntry<string> PanelPosition;

		internal static ConfigEntry<int> ListScrollRows;

		internal static ConfigEntry<bool> PauseWhileOpen;

		internal static ConfigEntry<bool> ShowPauseButton;

		internal static ConfigEntry<float> StallMs;

		internal static ConfigEntry<int> LoadingObjectsPerSecond;

		internal static ConfigEntry<int> WindowSeconds;

		internal static ConfigEntry<int> BaselineSeconds;

		internal static ConfigEntry<int> HistoryMinutes;

		internal static ConfigEntry<float> WatchSeconds;

		internal static ConfigEntry<float> BackgroundSeconds;

		internal static ConfigEntry<bool> AnswerClients;

		internal static ConfigEntry<bool> SharePeerDetail;

		internal static ConfigEntry<bool> ShareMyPerformance;

		internal static ConfigEntry<float> ServerTickWarnMs;

		internal static ConfigEntry<float> ServerTickSevereMs;

		internal static ConfigEntry<float> SteadyTickRatio;

		internal static ConfigEntry<float> ClientFrameWarnMs;

		internal static ConfigEntry<float> QueueWarnBytes;

		internal static ConfigEntry<float> QueueSevereBytes;

		internal static ConfigEntry<float> QualityWarn;

		internal static ConfigEntry<float> QualitySevere;

		internal static ConfigEntry<float> PingJitterWarnMs;

		internal static ConfigEntry<float> ChurnFloor;

		internal static ConfigEntry<float> ChurnFactor;

		internal static ConfigEntry<float> InstanceRiseWarn;

		internal static ConfigEntry<float> GcCoincidence;

		internal static void Bind(DiagnoseServerLagMod mod)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//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_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			OpenKey = mod.BindKey("Keys", "Open Report", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Opens and closes the lag report. Escape closes it too. Ignored while typing in chat, the console or a text box.", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>()));
			HudKey = mod.BindKey("Keys", "Toggle Readout", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Turns the corner readout on and off without opening the full report.", new KeyboardShortcut((KeyCode)289, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), new KeyboardShortcut((KeyCode)291, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }));
			OwnerNamesKey = mod.BindKey("Keys", "Toggle Owner Names", new KeyboardShortcut((KeyCode)287, Array.Empty<KeyCode>()), "Turns the owner label on creature health bars on and off.");
			ShowOwnerNames = mod.Bind("Display", "Show Owner Names", defaultValue: false, "Puts the owning player's name on a creature's floating health bar, so you can see which particular creature is being simulated by somebody else rather than only how many of them are. Off by default: it is a diagnostic, and a label on everything would be clutter during ordinary play. F6 toggles it.");
			ShowMyOwnOwnership = mod.Bind("Display", "Show My Own Ownership", defaultValue: false, "Whether the owner label also appears on creatures you own yourself. Off by default: the labels exist to show where an interaction is going, and one to your own machine goes nowhere, so a label on everything you own would bury the few that matter. Turn it on to see the labels working when nobody else is online - alone you own everything nearby, so with this off there is nothing to show.");
			ShowHud = mod.Bind("Display", "Show Readout", defaultValue: true, "The corner readout: frame time, stalls, CPU share, round trip to the server, creatures this machine is simulating, and the server's tick. Shift+F8 toggles it. The creature line is the one worth watching - Valheim runs a creature's AI only on the machine that owns it, and ownership falls to whoever was in range first, so it is the only warning you get that you are carrying a zone for everyone else. A config that already exists keeps whatever it was set to.");
			HudPosition = mod.Bind("Display", "Readout Position", LagHud.Corner.BottomRight, "Which corner the readout sits in. Choose Custom to place it anywhere with the two settings below. Changes apply immediately, so you can drag the values around while looking at it.");
			HudX = mod.BindRange("Display", "Readout Custom X", 98f, 0f, 100f, "Custom position only: distance across the screen, as a percentage. 0 is the left edge, 100 the right. Ignored unless Readout Position is Custom.");
			HudY = mod.BindRange("Display", "Readout Custom Y", 4f, 0f, 100f, "Custom position only: distance up the screen, as a percentage. 0 is the bottom edge, 100 the top. Ignored unless Readout Position is Custom.");
			PanelSize = mod.Bind("Display", "Panel Size", "860,640", "Width and height of the report, remembered when you drag its bottom-right corner.");
			PanelPosition = mod.Bind("Display", "Panel Position", "0,0", "Where the report sits, as an offset from the screen center, remembered when you drag it by its title. Set to 0,0 to put it back in the middle.");
			ListScrollRows = mod.BindRangeInt("Display", "List Scroll Rows", 4, 1, 20, "How many rows a list moves per notch of the mouse wheel.");
			PauseWhileOpen = mod.Bind("Display", "Pause While Open", defaultValue: false, "Pause the game while the report is open, the way the ESC menu does. Toggle it from the button in the report's top-right corner. This works by itself when playing solo or hosting alone; on a dedicated server it takes the Pause My Server mod, and the button shows whether the pause actually took effect. While the game is paused the mod stops recording, so the verdict you are reading stays the verdict for the seconds that were actually played.");
			ShowPauseButton = mod.Bind("Display", "Show Pause Button", defaultValue: true, "Show the pause toggle in the report's top-right corner. Turning it off leaves the setting above reachable only from this file.");
			StallMs = mod.BindRange("Measurement", "Stall Milliseconds", 100f, 20f, 1000f, "A frame longer than this counts as a stall. 100 ms is a single frame at 10 per second, which is comfortably past the point where a person notices. Lower it to catch smaller hitches; raise it if a machine that plays fine is reporting stalls constantly.");
			LoadingObjectsPerSecond = mod.BindRangeInt("Measurement", "Loading Objects Per Second", 300, 50, 5000, "How much the loaded world has to change in a second for it to count as streaming rather than playing. Stalls during streaming are still counted and reported, but separately, so the readout does not warn about hitches that are the unavoidable cost of walking somewhere. A teleport moves thousands of objects a second; standing still moves none.");
			WindowSeconds = mod.BindRangeInt("Measurement", "Window Seconds", 10, 3, 120, "How many recent seconds the verdict is made from. Short enough that a bad patch is not diluted by the good minute after it.");
			BaselineSeconds = mod.BindRangeInt("Measurement", "Baseline Seconds", 60, 10, 600, "How much history counts as 'normal for this server' when judging whether something has got worse. Rules with no universal right answer - object churn especially - are judged against this rather than against a fixed number.");
			HistoryMinutes = mod.BindRangeInt("Measurement", "History Minutes", 60, 5, 180, "How many minutes of per-second samples to keep, and therefore the longest capture dsl_bench can summarize. A sample is about 110 bytes, so an hour costs roughly 400 KB and three hours about 1.2 MB - the ceiling is set by what is useful to capture, not by what it costs. Longer is not automatically better: the summary reports medians over whatever is in the window, so a window covering ten minutes of work and ten of standing still describes neither. Match the capture to the activity. Takes effect on restart.");
			WatchSeconds = mod.BindRange("Measurement", "Watch Seconds", 1f, 0.5f, 30f, "How often to ask the server for its numbers while the report is open. Once a second keeps the verdict live; the request and its answer are a few hundred bytes.");
			BackgroundSeconds = mod.BindRange("Measurement", "Background Seconds", 5f, 0f, 120f, "How often to ask while the report is closed, so history exists before you go looking for it. 0 asks only while the report is open, which means a client that never opens it sends nothing at all.");
			AnswerClients = mod.Bind("Server", "Answer Clients", defaultValue: true, "SERVER SIDE. Answer clients that ask for the server's own measurements. Turning this off makes the server indistinguishable from one without the mod, and every client falls back to diagnosing its own end alone.");
			SharePeerDetail = mod.Bind("Server", "Share Peer Detail", defaultValue: true, "SERVER SIDE. Include the per-player table - each player's ping, connection quality, queued bytes and distance from the world center - in the answer to everyone, not only admins. The aggregate numbers that diagnose the server always go to everyone; this is the part that names who is on a bad line. Admins receive it either way.");
			ShareMyPerformance = mod.Bind("Display", "Share My Performance", defaultValue: true, "Answer an admin's group capture with this machine's frame times, stalls, CPU share and collection counts for the window they asked about. It is what lets a capture tell 'everyone hitched at once', which is the server or the network, from 'one machine hitched', which is that machine. Performance numbers only - nothing about what you were doing. Turn it off and you are simply absent from the group view; everything else still works.");
			ServerTickWarnMs = mod.BindRange("Thresholds", "Server Tick Warn Ms", 50f, 5f, 500f, "Server milliseconds per tick above which the server is called slow - but only when the tick time is also uneven; see Steady Tick Ratio. This was 33 ms until a real server turned out to run a rock-steady 33.3 ms frame cap, which is exactly 30 ticks a second, so the threshold sat on top of a perfectly healthy server and accused it permanently. 50 ms is 20 ticks a second.");
			ServerTickSevereMs = mod.BindRange("Thresholds", "Server Tick Severe Ms", 100f, 10f, 1000f, "Server milliseconds per tick above which the server is called badly starved whatever the shape of the measurement: 10 ticks a second, where position updates arrive too late to hide and everyone online rubber-bands. A steady tick is forgiven below this and not above it, because a server holding a metronomic 200 ms is still far too slow to run the game.");
			SteadyTickRatio = mod.BindRange("Thresholds", "Steady Tick Ratio", 1.5f, 1f, 10f, "How close the server's worst tick has to be to its median before the tick rate is read as a deliberate frame cap rather than a struggle. A frame limiter holds every tick to nearly the same length; a machine that genuinely cannot keep up produces variance, because the work that overruns is not the same work every tick. Below this ratio, with no stalls, the server is left alone however slow the number looks. Raise it to forgive more, lower it to accuse more.");
			ClientFrameWarnMs = mod.BindRange("Thresholds", "Client Frame Warn Ms", 33f, 8f, 500f, "Your own milliseconds per frame above which your machine is called the bottleneck. 33 ms is 30 frames a second.");
			QueueWarnBytes = mod.BindRange("Thresholds", "Queue Warn Bytes", 16384f, 1024f, 1048576f, "Bytes handed to the socket and not yet sent, above which the link is called full. Some queue is normal and harmless; what matters is whether it drains, which is why the rule also looks at whether the number is climbing.");
			QueueSevereBytes = mod.BindRange("Thresholds", "Queue Severe Bytes", 65536f, 4096f, 4194304f, "Queued bytes above which the link is called saturated regardless of direction of travel.");
			QualityWarn = mod.BindRange("Thresholds", "Quality Warn", 0.98f, 0.5f, 1f, "Connection quality below which packet loss is reported. This is Steam's own figure: a fraction of packets arriving, where 1.0 is no measured loss. 0.98 is about two percent lost, which Valheim already feels.");
			QualitySevere = mod.BindRange("Thresholds", "Quality Severe", 0.9f, 0.1f, 1f, "Connection quality below which loss is reported as the headline cause.");
			PingJitterWarnMs = mod.BindRange("Thresholds", "Ping Jitter Warn Ms", 40f, 5f, 500f, "Average change in ping from one second to the next, above which the link is called unstable. Jitter is judged separately from latency because they feel nothing alike: a steady 140 ms is playable, while a ping swinging between 20 and 220 averages better and plays far worse.");
			ChurnFloor = mod.BindRange("Thresholds", "Churn Floor", 150f, 10f, 5000f, "Object updates a second below which churn is never reported, however much it has risen. Tripling a very small number is not news.");
			ChurnFactor = mod.BindRange("Thresholds", "Churn Factor", 3f, 1.2f, 20f, "How many times the baseline rate of object updates counts as churn. Judged against this server's own recent normal, because a quiet forest and a working base legitimately differ by an order of magnitude and no fixed number is right for both.");
			GcCoincidence = mod.BindRange("Thresholds", "GC Coincidence", 0.5f, 0.1f, 1f, "What fraction of stalled seconds must contain a gen1 or gen2 garbage collection before the hitches are blamed on the collector. Also required to be at least twice the rate seen in seconds that did not stall, because a machine collecting constantly would otherwise have every stall blamed on it. Gen0 is ignored entirely: it is cheap and continuous.");
			InstanceRiseWarn = mod.BindRange("Thresholds", "Instance Rise Warn", 40f, 5f, 2000f, "New objects a second being built into the scene which, happening at the same time as a stall, attributes that stall to loading the world rather than to the machine being too slow.");
		}
	}
	internal static class Feed
	{
		private static readonly float[] _gaps = new float[64];

		private static readonly float[] _sorted = new float[64];

		private static int _count;

		private static int _next;

		private static float _last = -1f;

		private const float IgnoreAboveMs = 2000f;

		internal static float IntervalMs
		{
			get
			{
				if (_count < 8)
				{
					return 0f;
				}
				Array.Copy(_gaps, _sorted, _count);
				Array.Sort(_sorted, 0, _count);
				return _sorted[_count / 2];
			}
		}

		internal static float PredictedMs(ServerReport report)
		{
			int num = report?.PeerCount ?? 0;
			if (num <= 0)
			{
				return 0f;
			}
			float num2 = Mathf.Max(1f, report.TickMsAvg);
			return 50f + (float)num * num2;
		}

		internal static void Note()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || instance.IsServer())
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (_last >= 0f)
			{
				float num = (unscaledTime - _last) * 1000f;
				if (num > 0f && num < 2000f)
				{
					_gaps[_next] = num;
					_next = (_next + 1) % _gaps.Length;
					if (_count < _gaps.Length)
					{
						_count++;
					}
				}
			}
			_last = unscaledTime;
		}

		internal static void Reset()
		{
			_count = 0;
			_next = 0;
			_last = -1f;
		}
	}
	[HarmonyPatch(typeof(ZDOMan), "RPC_ZDOData")]
	internal static class ZdoFeedPatch
	{
		private static void Postfix()
		{
			try
			{
				Feed.Note();
			}
			catch
			{
			}
		}
	}
	internal static class FrameTimings
	{
		private const int Window = 256;

		private static readonly FrameTiming[] _timings = (FrameTiming[])(object)new FrameTiming[256];

		private static bool _reported;

		internal static bool? Available { get; private set; }

		internal static float GpuMs { get; private set; } = -1f;

		internal static float CpuMainMs { get; private set; } = -1f;

		internal static float CpuRenderMs { get; private set; } = -1f;

		internal static void Sample()
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: 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_0096: Unknown result type (might be due to invalid IL or missing references)
			GpuMs = (CpuMainMs = (CpuRenderMs = -1f));
			try
			{
				FrameTimingManager.CaptureFrameTimings();
				uint latestTimings = FrameTimingManager.GetLatestTimings(256u, _timings);
				if (latestTimings == 0)
				{
					Note(available: false, "the engine returns no frame timings at all, so this build was made with frame timing statistics disabled");
					return;
				}
				double num3 = 0.0;
				double num4 = 0.0;
				double num5 = 0.0;
				int num6 = 0;
				for (uint num7 = 0u; num7 < latestTimings && num7 < 256; num7++)
				{
					FrameTiming val = _timings[num7];
					if (!(val.cpuMainThreadFrameTime <= 0.0) || !(val.cpuRenderThreadFrameTime <= 0.0) || !(val.gpuFrameTime <= 0.0))
					{
						num3 += val.gpuFrameTime;
						num4 += val.cpuMainThreadFrameTime;
						num5 += val.cpuRenderThreadFrameTime;
						num6++;
					}
				}
				if (num6 == 0)
				{
					Note(available: false, "this build reports frame timings as all-zero, so frame timing statistics are off in it");
					return;
				}
				GpuMs = (float)(num3 / (double)num6);
				CpuMainMs = (float)(num4 / (double)num6);
				CpuRenderMs = (float)(num5 / (double)num6);
				Note(available: true, "");
			}
			catch (Exception ex)
			{
				Note(available: false, ex.GetType().Name + ": " + ex.Message);
			}
		}

		private static void Note(bool available, string why)
		{
			if (Available == available)
			{
				return;
			}
			if (!Available.HasValue && available)
			{
				Available = true;
				return;
			}
			Available = available;
			if (!available && !_reported)
			{
				_reported = true;
				DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] per-frame GPU and main-thread times are not available - " + why + ". Frame times are still measured; only the split between processor and graphics card is missing, so gpu_ms and cpu_main_ms stay blank in captures."));
			}
		}
	}
	internal static class GraphicsConfig
	{
		private static MethodInfo _getInt;

		private static MethodInfo _getBool;

		private static bool _looked;

		private static readonly object[] _two = new object[2];

		private static readonly Dictionary<string, string> Aliases = new Dictionary<string, string>
		{
			{ "Vegetation", "ClutterQuality" },
			{ "LOD", "LodBias" },
			{ "FpsLimit", "FPSLimit" },
			{ "Vsync", "VSync" },
			{ "DepthOfField", "DOF" },
			{ "SoftParticles", "SoftPart" },
			{ "SSAO", "SSAO_2" }
		};

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

		private static void Look()
		{
			if (_looked)
			{
				return;
			}
			_looked = true;
			try
			{
				Type type = AccessTools.TypeByName("PlatformPrefs");
				if (!(type == null))
				{
					_getInt = AccessTools.Method(type, "GetInt", new Type[2]
					{
						typeof(string),
						typeof(int)
					}, (Type[])null);
					_getBool = AccessTools.Method(type, "GetBool", new Type[2]
					{
						typeof(string),
						typeof(bool)
					}, (Type[])null);
				}
			}
			catch
			{
			}
		}

		private static int? Int(string key)
		{
			if (_getInt == null)
			{
				return null;
			}
			try
			{
				_two[0] = key;
				_two[1] = int.MinValue;
				int num = (int)_getInt.Invoke(null, _two);
				return (num == int.MinValue) ? ((int?)null) : new int?(num);
			}
			catch
			{
				return null;
			}
		}

		private static bool? Bool(string key)
		{
			if (_getBool == null)
			{
				return null;
			}
			try
			{
				_two[0] = key;
				_two[1] = false;
				bool flag = (bool)_getBool.Invoke(null, _two);
				_two[1] = true;
				bool flag2 = (bool)_getBool.Invoke(null, _two);
				return (flag == flag2) ? new bool?(flag) : ((bool?)null);
			}
			catch
			{
				return null;
			}
		}

		private unsafe static string PresetNote()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: 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_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: 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_00de: 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_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: 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_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0200: Unknown result type (might be due to invalid IL or missing references)
			//IL_0262: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_026d: Unknown result type (might be due to invalid IL or missing references)
			//IL_026e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0233: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_0248: Unknown result type (might be due to invalid IL or missing references)
			//IL_0249: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Unknown result type (might be due to invalid IL or missing references)
			//IL_0281: Unknown result type (might be due to invalid IL or missing references)
			//IL_0290: Unknown result type (might be due to invalid IL or missing references)
			//IL_0291: Unknown result type (might be due to invalid IL or missing references)
			if ((int)SystemInfo.graphicsDeviceType == 4)
			{
				return "";
			}
			GraphicsSettingsManager instance = GraphicsSettingsManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return "";
			}
			GraphicsSettingsState activeSettings = Unsafe.Read<GraphicsSettingsState>((void*)instance.ActiveSettings);
			GraphicsSettingsState currentPlayerSettingsRaw = Unsafe.Read<GraphicsSettingsState>((void*)instance.CurrentPlayerSettingsRaw);
			List<string> list = new List<string>();
			if (activeSettings.m_lod != currentPlayerSettingsRaw.m_lod)
			{
				list.Add($"LOD {currentPlayerSettingsRaw.m_lod}->{activeSettings.m_lod}");
			}
			if (activeSettings.m_shadowQuality != currentPlayerSettingsRaw.m_shadowQuality)
			{
				list.Add($"ShadowQuality {currentPlayerSettingsRaw.m_shadowQuality}->{activeSettings.m_shadowQuality}");
			}
			if (activeSettings.m_lights != currentPlayerSettingsRaw.m_lights)
			{
				list.Add($"Lights {currentPlayerSettingsRaw.m_lights}->{activeSettings.m_lights}");
			}
			if (activeSettings.m_vegetation != currentPlayerSettingsRaw.m_vegetation)
			{
				list.Add($"Vegetation {currentPlayerSettingsRaw.m_vegetation}->{activeSettings.m_vegetation}");
			}
			if (activeSettings.m_pointLights != currentPlayerSettingsRaw.m_pointLights)
			{
				list.Add($"PointLights {currentPlayerSettingsRaw.m_pointLights}->{activeSettings.m_pointLights}");
			}
			if (activeSettings.m_pointLightShadows != currentPlayerSettingsRaw.m_pointLightShadows)
			{
				list.Add($"PointLightShadows {currentPlayerSettingsRaw.m_pointLightShadows}->{activeSettings.m_pointLightShadows}");
			}
			if (activeSettings.m_ssao != currentPlayerSettingsRaw.m_ssao)
			{
				list.Add($"SSAO {currentPlayerSettingsRaw.m_ssao}->{activeSettings.m_ssao}");
			}
			if (activeSettings.m_clothQuality != currentPlayerSettingsRaw.m_clothQuality)
			{
				list.Add($"ClothQuality {currentPlayerSettingsRaw.m_clothQuality}->{activeSettings.m_clothQuality}");
			}
			if (activeSettings.m_simulationDistance != currentPlayerSettingsRaw.m_simulationDistance)
			{
				list.Add($"SimulationDistance {currentPlayerSettingsRaw.m_simulationDistance}->{activeSettings.m_simulationDistance}");
			}
			if (activeSettings.m_presentSettings.m_vsync != currentPlayerSettingsRaw.m_presentSettings.m_vsync)
			{
				list.Add("Vsync " + Yes(currentPlayerSettingsRaw.m_presentSettings.m_vsync) + "->" + Yes(activeSettings.m_presentSettings.m_vsync));
			}
			if (activeSettings.m_presentSettings.m_fpsLimit != currentPlayerSettingsRaw.m_presentSettings.m_fpsLimit)
			{
				list.Add($"FpsLimit {currentPlayerSettingsRaw.m_presentSettings.m_fpsLimit}->{activeSettings.m_presentSettings.m_fpsLimit}");
			}
			if (list.Count != 0)
			{
				return "preset=overrides-the-menu (" + string.Join(" ", list.ToArray()) + ")";
			}
			return "";
		}

		private static string Yes(bool b)
		{
			if (!b)
			{
				return "off";
			}
			return "on";
		}

		private static string Alias(string name)
		{
			if (!Aliases.TryGetValue(name, out var value))
			{
				return null;
			}
			return value;
		}

		private static void NoteDisagreement(string name, string alias, object underName, object underAlias)
		{
			if (_reported.Add(name))
			{
				DiagnoseServerLagMod.Log.LogInfo((object)($"[DiagnoseServerLag] {name} is stored twice: {name}={underName} and {alias}={underAlias}. " + "Reporting " + alias + ", on the assumption it is the current key."));
			}
		}

		internal static string Describe()
		{
			//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_031b: Unknown result type (might be due to invalid IL or missing references)
			Look();
			List<string> list = new List<string>();
			try
			{
				list.Add($"{Screen.width}x{Screen.height}");
				list.Add(Screen.fullScreen ? "fullscreen" : "windowed");
				foreach (object value in Enum.GetValues(typeof(GraphicsSettingInt)))
				{
					string text = value.ToString();
					if (!(text == "None"))
					{
						string text2 = Alias(text);
						int? num = Int(text);
						int? num2 = ((text2 == null) ? ((int?)null) : Int(text2));
						if (num.HasValue && num2.HasValue && num.Value != num2.Value)
						{
							NoteDisagreement(text, text2, num.Value, num2.Value);
						}
						int? num3 = num2 ?? num;
						if (num3.HasValue)
						{
							list.Add($"{text}={num3.Value}");
						}
					}
				}
				foreach (object value2 in Enum.GetValues(typeof(GraphicsSettingBool)))
				{
					string text3 = value2.ToString();
					if (!(text3 == "None"))
					{
						string text4 = Alias(text3);
						bool? flag = Bool(text3);
						bool? flag2 = ((text4 == null) ? ((bool?)null) : Bool(text4));
						if (flag.HasValue && flag2.HasValue && flag.Value != flag2.Value)
						{
							NoteDisagreement(text3, text4, flag.Value, flag2.Value);
						}
						bool? flag3 = flag2 ?? flag;
						if (flag3.HasValue)
						{
							list.Add(text3 + "=" + (flag3.Value ? "on" : "off"));
						}
					}
				}
				list.Add($"vsyncCount={QualitySettings.vSyncCount}");
				if (Application.targetFrameRate > 0)
				{
					list.Add($"fpsCap={Application.targetFrameRate}");
				}
				list.Add($"live:lodBias={QualitySettings.lodBias:0.##}");
				list.Add($"live:shadowDist={QualitySettings.shadowDistance:0}");
				list.Add($"live:cascades={QualitySettings.shadowCascades}");
				list.Add($"live:shadowRes={QualitySettings.shadowResolution}");
				list.Add($"live:pixelLights={QualitySettings.pixelLightCount}");
				list.Add($"live:aniso={QualitySettings.anisotropicFiltering}");
				list.Add("live:softParticles=" + (QualitySettings.softParticles ? "on" : "off"));
				string text5 = PresetNote();
				if (!string.IsNullOrEmpty(text5))
				{
					list.Add(text5);
				}
			}
			catch (Exception ex)
			{
				DiagnoseServerLagMod.Log.LogWarning((object)("[DiagnoseServerLag] could not read graphics settings: " + ex.Message));
			}
			if (list.Count <= 2)
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < list.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(' ');
				}
				stringBuilder.Append(list[i]);
			}
			return stringBuilder.ToString();
		}
	}
	internal static class GraphicsWatch
	{
		internal struct Change
		{
			public long UtcTicks;

			public string What;
		}

		private const float PollSeconds = 5f;

		private static readonly List<Change> _changes = new List<Change>();

		private static string _last;

		private static float _nextPoll;

		private static bool _hooked;

		private static bool _dirty;

		internal static int Revision { get; private set; }

		internal static void Hook()
		{
			if (_hooked)
			{
				return;
			}
			_hooked = true;
			try
			{
				GraphicsSettingsManager.GraphicsSettingsChanged += OnChanged;
			}
			catch (Exception ex)
			{
				DiagnoseServerLagMod.Log.LogWarning((object)("[DiagnoseServerLag] could not subscribe to GraphicsSettingsChanged: " + ex.Message));
			}
		}

		internal static void Unhook()
		{
			if (!_hooked)
			{
				return;
			}
			_hooked = false;
			try
			{
				GraphicsSettingsManager.GraphicsSettingsChanged -= OnChanged;
			}
			catch
			{
			}
		}

		private static void OnChanged()
		{
			_dirty = true;
		}

		internal static void Tick(float now)
		{
			bool flag = now >= _nextPoll;
			if (!_dirty && !flag)
			{
				return;
			}
			_dirty = false;
			_nextPoll = now + 5f;
			string text;
			try
			{
				text = GraphicsConfig.Describe();
			}
			catch
			{
				return;
			}
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			if (_last == null)
			{
				_last = text;
			}
			else if (!(text == _last))
			{
				string text2 = Diff(_last, text);
				_last = text;
				Revision++;
				Change item = new Change
				{
					UtcTicks = DateTime.UtcNow.Ticks,
					What = text2
				};
				_changes.Add(item);
				if (_changes.Count > 64)
				{
					_changes.RemoveAt(0);
				}
				DiagnoseServerLagMod.Log.LogInfo((object)("[DiagnoseServerLag] graphics setting changed: " + text2));
			}
		}

		private static string Diff(string before, string after)
		{
			Dictionary<string, string> dictionary = Parse(before);
			Dictionary<string, string> dictionary2 = Parse(after);
			List<string> list = new List<string>();
			foreach (string key in dictionary.Keys)
			{
				if (!list.Contains(key))
				{
					list.Add(key);
				}
			}
			foreach (string key2 in dictionary2.Keys)
			{
				if (!list.Contains(key2))
				{
					list.Add(key2);
				}
			}
			list.Sort(StringComparer.Ordinal);
			StringBuilder stringBuilder = new StringBuilder();
			foreach (string item in list)
			{
				dictionary.TryGetValue(item, out var value);
				dictionary2.TryGetValue(item, out var value2);
				if (!(value == value2))
				{
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append("; ");
					}
					stringBuilder.Append(item).Append(' ').Append(value ?? "(absent)")
						.Append(" -> ")
						.Append(value2 ?? "(absent)");
				}
			}
			if (stringBuilder.Length != 0)
			{
				return stringBuilder.ToString();
			}
			return "something not in the settings line";
		}

		internal static Dictionary<string, string> Parse(string line)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			if (string.IsNullOrEmpty(line))
			{
				return dictionary;
			}
			string text = null;
			int num = 0;
			string[] array = line.Split(new char[1] { ' ' });
			foreach (string text2 in array)
			{
				if (text2.Length == 0)
				{
					continue;
				}
				if (num > 0 && text != null)
				{
					dictionary[text] = dictionary[text] + " " + text2;
					num += Count(text2, '(') - Count(text2, ')');
					if (num < 0)
					{
						num = 0;
					}
					continue;
				}
				int num2 = Count(text2, '(');
				int num3 = Count(text2, ')');
				if (text != null && (text2[0] == '(' || num2 > num3))
				{
					dictionary[text] = dictionary[text] + " " + text2;
					num = ((num2 > num3) ? (num2 - num3) : 0);
					continue;
				}
				int num4 = text2.IndexOf('=');
				if (num4 > 0)
				{
					text = text2.Substring(0, num4);
					dictionary[text] = text2.Substring(num4 + 1);
				}
				else
				{
					text = text2;
					dictionary[text] = "present";
				}
			}
			return dictionary;
		}

		private static int Count(string s, char c)
		{
			int num = 0;
			for (int i = 0; i < s.Length; i++)
			{
				if (s[i] == c)
				{
					num++;
				}
			}
			return num;
		}

		internal static List<Change> Since(long utcTicksFrom)
		{
			List<Change> list = new List<Change>();
			foreach (Change change in _changes)
			{
				if (change.UtcTicks >= utcTicksFrom)
				{
					list.Add(change);
				}
			}
			return list;
		}
	}
	internal static class GroupReport
	{
		private const int Together = 2;

		private const int BuiltUpFloor = 1000;

		internal static string Build(int seconds, List<ClientSeries> clients, int expected, out string csvPath, out string csvText)
		{
			csvPath = null;
			StringBuilder stringBuilder = new StringBuilder();
			List<Sample> list = Sampler.History.Recent(seconds);
			stringBuilder.AppendLine(string.Format("DiagnoseServerLag {0} group capture - {1}s, ", "0.10.21", list.Count) + string.Format("{0} of {1} client{2} answered", clients.Count, expected, (expected == 1) ? "" : "s"));
			stringBuilder.AppendLine();
			float num = Stats.Median(list, (Sample x) => x.FrameMsAvg);
			int num2 = 0;
			foreach (Sample item in list)
			{
				num2 += item.Stalls;
			}
			float num3 = Stats.Median(list, (Sample x) => x.CpuMsPerSec);
			stringBuilder.AppendLine(string.Format("  {0,-16} tick {1,6:0.0} ms   stalls {2,3}   ", "server", num, num2) + ((num3 > 0f) ? $"CPU {Machine.CoreShare(num3) * 100f,5:0.0}% of a core" : "CPU n/a") + $"   {Stats.Median(list, (Sample x) => x.Zdos):0} objects");
			foreach (ClientSeries client in clients)
			{
				string text = ((client.HasCpu && client.CpuMedianMsPerSec > 0f) ? $"CPU {client.CpuMedianMsPerSec / 10f,5:0.0}% of a core" : "CPU n/a");
				string text2 = ((client.NearbyAI > 0) ? $"   simulating {client.OwnedAI}/{client.NearbyAI} creatures" : "");
				stringBuilder.AppendLine($"  {Trim(client.Name, 16),-16} frame {client.FrameMedianMs,5:0.0} ms   stalls {client.TotalStalls,3}   {text}" + ((client.RoundTripMs > 0) ? $"   rt {client.RoundTripMs} ms" : "") + text2 + Granted(client) + Departed(client));
			}
			List<string> list2 = new List<string>();
			foreach (ClientSeries client2 in clients)
			{
				if (string.IsNullOrEmpty(client2.ModVersion))
				{
					list2.Add(Trim(client2.Name, 16) + " (before 0.10.6)");
				}
				else if (client2.ModVersion != "0.10.21")
				{
					list2.Add(Trim(client2.Name, 16) + " " + client2.ModVersion);
				}
			}
			if (list2.Count > 0)
			{
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("  server is 0.10.21; behind it: " + string.Join(", ", list2.ToArray()));
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("MACHINES");
			string text3 = Hardware.Describe();
			if (!string.IsNullOrEmpty(text3))
			{
				stringBuilder.AppendLine(string.Format("  {0,-16} {1}", "server", text3));
			}
			foreach (ClientSeries client3 in clients)
			{
				if (!string.IsNullOrEmpty(client3.Gpu) || client3.RamMB != 0)
				{
					string text4 = ((client3.ScreenWidth > 0) ? ($"{client3.ScreenWidth}x{client3.ScreenHeight}" + ((client3.RefreshHz > 0) ? $"@{client3.RefreshHz}Hz" : "")) : "?");
					string text5 = ((client3.VramMB > 0) ? $" {(float)client3.VramMB / 1024f:0.#} GB" : "");
					string text6 = ((client3.RamMB > 0) ? $", {(float)client3.RamMB / 1024f:0.#} GB RAM" : "");
					stringBuilder.AppendLine($"  {Trim(client3.Name, 16),-16} {client3.CpuName} ({client3.Cores}c){text6}, {client3.Gpu}{text5}, {text4}");
					if (!string.IsNullOrEmpty(client3.GpuDetail))
					{
						stringBuilder.AppendLine(string.Format("  {0,-16} {1}", "", client3.GpuDetail));
					}
					if (!string.IsNullOrEmpty(client3.Graphics))
					{
						stringBuilder.AppendLine(string.Format("  {0,-16} {1}", "", client3.Graphics));
					}
					string text7 = DisplayCapped(client3);
					if (text7 != null)
					{
						stringBuilder.AppendLine(string.Format("  {0,-16} {1}", "", text7));
					}
					string text8 = StallsInflated(client3);
					if (text8 != null)
					{
						stringBuilder.AppendLine(string.Format("  {0,-16} {1}", "", text8));
					}
				}
			}
			int num4 = 0;
			foreach (ClientSeries client4 in clients)
			{
				if (!client4.Full)
				{
					num4++;
				}
			}
			if (num4 > 0)
			{
				stringBuilder.AppendLine($"  ({num4} client(s) sent the reduced 0.5.x format; their extra columns are empty, not zero)");
			}
			if (clients.Count < expected)
			{
				stringBuilder.AppendLine($"  ({expected - clients.Count} client(s) did not answer: no mod, an older version, or Share My Performance off)");
			}
			stringBuilder.AppendLine();
			List<ClientSeries> list3 = new List<ClientSeries>();
			foreach (ClientSeries client5 in clients)
			{
				if (client5.LeftAtUtcTicks == 0L)
				{
					list3.Add(client5);
				}
			}
			stringBuilder.AppendLine(Correlate(list, list3));
			if (list3.Count != clients.Count)
			{
				stringBuilder.AppendLine($"  ({clients.Count - list3.Count} departed player(s) are in the rows above and " + "in the CSV, but left out of this comparison: their windows end early, and including them would shorten it for everybody.)");
			}
			string text9 = Ownership(clients);
			if (text9 != null)
			{
				stringBuilder.AppendLine();
				stringBuilder.AppendLine(text9);
			}
			csvPath = WriteCsv(list, clients, out csvText);
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}

		private static string Granted(ClientSeries c)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: 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)
			try
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return "";
				}
				ZNetPeer