Decompiled source of ModDoctor v1.1.0

ModDoctor.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Profiling;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace ModDoctor;

[BepInPlugin("bub.moddoctor", "Mod Doctor", "1.1.0")]
public class ModDoctorPlugin : BaseUnityPlugin
{
	public enum ReportLanguage
	{
		Auto,
		English,
		Portuguese
	}

	private class PlayerMods
	{
		public string Name;

		public DateTime When;

		public List<ModDifference> Diff;
	}

	public const string Guid = "bub.moddoctor";

	public const string Version = "1.1.0";

	private const string ReportRpc = "ModDoctor_Report";

	private const string HelloRpc = "ModDoctor_Hello";

	private const string DiffRpc = "ModDoctor_Diff";

	private const string AdminRequestRpc = "ModDoctor_AdminRequest";

	private const string AdminDataRpc = "ModDoctor_AdminData";

	internal static ModDoctorPlugin Instance;

	internal static ManualLogSource Log;

	private ConfigEntry<ReportLanguage> language;

	private ConfigEntry<KeyboardShortcut> toggleKey;

	private ConfigEntry<bool> showToast;

	private ConfigEntry<bool> suppressRepeats;

	private ConfigEntry<int> suppressAfter;

	private ConfigEntry<bool> checkUpdates;

	private ConfigEntry<int> updateCacheHours;

	private ConfigEntry<bool> sendToServer;

	private ConfigEntry<bool> collectFromClients;

	private ConfigEntry<string> serverOnlyMods;

	internal Diagnosis Diagnosis;

	private RepeatFilter repeatFilter;

	private UpdateChecker updates;

	private LoadTimer loadTimer;

	private readonly Health health = new Health();

	private string outputDir;

	private List<InstalledMod> installed;

	private List<string> duplicates;

	private float nextReportWrite;

	private int lastWrittenVersion = -1;

	private float nextSend;

	private int lastSentVersion = -1;

	private ZRoutedRpc registeredRpc;

	private bool helloSent;

	private float nextHello;

	private List<ModDifference> serverDiff;

	private bool serverIsAdmin;

	private AdminData adminData;

	private float adminRequestedAt = -100f;

	private bool diffToastPending;

	private readonly Dictionary<string, PlayerMods> playerMods = new Dictionary<string, PlayerMods>();

	private bool windowOpen;

	private int tab;

	private Rect windowRect = new Rect(60f, 60f, 860f, 640f);

	private Vector2 scroll;

	private float nextWindowRefresh;

	private TabContext view;

	private readonly HashSet<string> expanded = new HashSet<string>();

	private string toastText;

	private float toastUntil;

	private float nextToastCheck;

	private bool wasInWorld;

	private readonly HashSet<string> announced = new HashSet<string>();

	private GUIStyle toastStyle;

	private List<KeyValuePair<string, int>> topPrefabs = new List<KeyValuePair<string, int>>();

	private float nextObjectsRefresh;

	private void Awake()
	{
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: Expected O, but got Unknown
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		language = ((BaseUnityPlugin)this).Config.Bind<ReportLanguage>("General", "Language", ReportLanguage.Auto, "Report language. Auto follows the game language (Portuguese or English).");
		toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "ToggleKey", new KeyboardShortcut((KeyCode)288, (KeyCode[])(object)new KeyCode[0]), "Opens and closes the report window.");
		showToast = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowToast", true, "Show a short notice when a mod starts causing problems.");
		suppressRepeats = ((BaseUnityPlugin)this).Config.Bind<bool>("Log", "SuppressRepeats", true, "Stop identical errors from being written to LogOutput.log over and over. A summary line is written instead.");
		suppressAfter = ((BaseUnityPlugin)this).Config.Bind<int>("Log", "SuppressAfter", 20, "How many identical copies are written before the rest are suppressed.");
		checkUpdates = ((BaseUnityPlugin)this).Config.Bind<bool>("Updates", "CheckThunderstore", true, "Look up the latest version of each installed package on thunderstore.io (public API, no personal data sent).");
		updateCacheHours = ((BaseUnityPlugin)this).Config.Bind<int>("Updates", "CacheHours", 12, "How long a Thunderstore lookup is reused before asking again.");
		sendToServer = ((BaseUnityPlugin)this).Config.Bind<bool>("Server", "SendReportsToServer", true, "Send your findings (installed mods, versions and errors, with your character name) to the server you are playing on, if it runs Mod Doctor.");
		collectFromClients = ((BaseUnityPlugin)this).Config.Bind<bool>("Server", "CollectClientReports", true, "On a server: store the reports players send, in BepInEx/ModDoctor/players and summary.txt.");
		serverOnlyMods = ((BaseUnityPlugin)this).Config.Bind<string>("Server", "ServerOnlyMods", "ArgusMagnus-ServersideQoL*, Smootheim-*, MidnightMods-NetworkPerformanceSystem", "On a server: mod folders that players do not need (comma separated, * wildcard). They are not reported as missing.");
		outputDir = Path.Combine(Paths.BepInExRootPath, "ModDoctor");
		Directory.CreateDirectory(outputDir);
		Diagnosis = new Diagnosis();
		Application.logMessageReceivedThreaded += new LogCallback(OnUnityLog);
		Logger.Listeners.Add((ILogListener)(object)new ModErrorListener(Diagnosis));
		loadTimer = new LoadTimer();
		Logger.Listeners.Add((ILogListener)(object)loadTimer);
		if (suppressRepeats.Value)
		{
			repeatFilter = RepeatFilter.Install(suppressAfter.Value);
		}
		updates = new UpdateChecker(Path.Combine(outputDir, "thunderstore-cache.txt"));
		health.ResetGcWindow();
		Log.LogInfo((object)("Mod Doctor 1.1.0 watching for mod errors. Report: " + Path.Combine(outputDir, "report.txt")));
	}

	private void Start()
	{
		Diagnosis.IndexPlugins();
		Backfill.FromLogOutput(Path.Combine(Paths.BepInExRootPath, "LogOutput.log"), Diagnosis);
		try
		{
			installed = Inventory.Scan();
		}
		catch (Exception ex)
		{
			installed = new List<InstalledMod>();
			Log.LogWarning((object)("Mod scan failed: " + ex.Message));
		}
		try
		{
			duplicates = Inventory.Duplicates();
		}
		catch (Exception)
		{
			duplicates = new List<string>();
		}
		foreach (string duplicate in duplicates)
		{
			Log.LogWarning((object)("Installed twice: " + duplicate));
		}
		if (checkUpdates.Value)
		{
			((MonoBehaviour)this).StartCoroutine(updates.Run(Diagnosis.Packages(), updateCacheHours.Value));
		}
	}

	private void OnDestroy()
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		Application.logMessageReceivedThreaded -= new LogCallback(OnUnityLog);
		WriteReport(force: true);
	}

	private void OnApplicationQuit()
	{
		WriteReport(force: true);
	}

	private void OnUnityLog(string condition, string stackTrace, LogType type)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Invalid comparison between Unknown and I4
		//IL_0004: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Invalid comparison between Unknown and I4
		if ((int)type == 4 || (int)type == 0 || (int)type == 1)
		{
			long timestamp = Stopwatch.GetTimestamp();
			Diagnosis.Record(condition, stackTrace, null);
			Interlocked.Increment(ref health.ErrorsSeen);
			Interlocked.Add(ref health.OwnTicks, Stopwatch.GetTimestamp() - timestamp);
		}
	}

	private void Update()
	{
		//IL_0044: 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)
		long timestamp = Stopwatch.GetTimestamp();
		bool flag = IsHeadless();
		if (flag || windowOpen)
		{
			health.Sample(Time.unscaledDeltaTime);
		}
		Diagnosis.ResolvePending();
		if (!flag)
		{
			UpdateToasts();
		}
		if (!flag)
		{
			KeyboardShortcut value = toggleKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				windowOpen = !windowOpen;
				if (windowOpen)
				{
					health.ResetGcWindow();
					nextWindowRefresh = 0f;
				}
			}
		}
		if (windowOpen && Time.unscaledTime >= nextWindowRefresh)
		{
			RefreshWindow();
		}
		if (repeatFilter != null)
		{
			repeatFilter.FlushSummaries();
		}
		if (Time.unscaledTime >= nextReportWrite)
		{
			nextReportWrite = Time.unscaledTime + 30f;
			WriteReport(force: false);
		}
		UpdateNetworking();
		health.OwnFrames++;
		Interlocked.Add(ref health.OwnTicks, Stopwatch.GetTimestamp() - timestamp);
	}

	private void UpdateToasts()
	{
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_027b: 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)
		if (!showToast.Value || Time.unscaledTime < nextToastCheck)
		{
			return;
		}
		nextToastCheck = Time.unscaledTime + 2f;
		if (!((Object)(object)Player.m_localPlayer != (Object)null))
		{
			wasInWorld = false;
			return;
		}
		if (diffToastPending && Time.unscaledTime >= toastUntil)
		{
			diffToastPending = false;
			int num = serverDiff.Count((ModDifference d) => d.Kind != "extra");
			toastText = Text.T((num == 1) ? "toastdiff1" : "toastdiff", Lang()).Replace("{n}", num.ToString()).Replace("{key}", ((object)toggleKey.Value/*cast due to .constrained prefix*/).ToString());
			toastUntil = Time.unscaledTime + 12f;
			return;
		}
		List<string> list = new List<string>();
		HashSet<string> hashSet = new HashSet<string>();
		foreach (Problem item in Diagnosis.Snapshot())
		{
			if (item.Mod == null)
			{
				continue;
			}
			string text = ReportWriter.Kind(item);
			if (!(text == "error"))
			{
				hashSet.Add(item.Mod.Name);
				if (announced.Add(item.Mod.Name + "|" + text) && wasInWorld && !list.Contains(item.Mod.Name))
				{
					list.Add(item.Mod.Name);
				}
			}
		}
		List<string> list2 = (wasInWorld ? list : hashSet.ToList());
		wasInWorld = true;
		if (list2.Count != 0 && !(Time.unscaledTime < toastUntil - 2f))
		{
			string newValue = string.Join(", ", list2.Take(3).ToArray()) + ((list2.Count > 3) ? (" +" + (list2.Count - 3)) : "");
			toastText = Text.T((list2.Count == 1) ? "toast" : "toastmany", Lang()).Replace("{mod}", newValue).Replace("{key}", ((object)toggleKey.Value/*cast due to .constrained prefix*/).ToString());
			toastUntil = Time.unscaledTime + 12f;
		}
	}

	private static bool IsHeadless()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Invalid comparison between Unknown and I4
		return (int)SystemInfo.graphicsDeviceType == 4;
	}

	internal string Lang()
	{
		if (language.Value == ReportLanguage.English)
		{
			return "en";
		}
		if (language.Value == ReportLanguage.Portuguese)
		{
			return "pt";
		}
		try
		{
			if (Localization.instance != null && Localization.instance.GetSelectedLanguage().StartsWith("Portuguese"))
			{
				return "pt";
			}
		}
		catch (Exception)
		{
		}
		return "en";
	}

	private bool OnDedicatedServerAsClient()
	{
		if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer())
		{
			return ZNet.instance.GetServerPeer() != null;
		}
		return false;
	}

	private string BuildReport(bool rich)
	{
		return ReportWriter.Render(Diagnosis.Snapshot(), updates, Diagnosis.PluginCount, Lang(), null, rich);
	}

	private void WriteReport(bool force)
	{
		int version = Diagnosis.Version;
		if (!force && version == lastWrittenVersion)
		{
			return;
		}
		lastWrittenVersion = version;
		try
		{
			File.WriteAllText(Path.Combine(outputDir, "report.txt"), BuildReport(rich: false), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && ZNet.instance.IsDedicated() && collectFromClients.Value)
			{
				ServerStore.Save(outputDir, "(server)", "1.1.0", Diagnosis.PluginCount, ReportWriter.Serialize(Diagnosis.Snapshot(), updates, 40), Lang());
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Could not write report: " + ex.Message));
		}
	}

	private void UpdateNetworking()
	{
		//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d5: Expected O, but got Unknown
		//IL_0140: Unknown result type (might be due to invalid IL or missing references)
		//IL_0146: Expected O, but got Unknown
		ZRoutedRpc instance = ZRoutedRpc.instance;
		if (instance == null || (Object)(object)ZNet.instance == (Object)null)
		{
			return;
		}
		if (registeredRpc != instance)
		{
			instance.Register<ZPackage>("ModDoctor_Report", (Action<long, ZPackage>)OnClientReport);
			instance.Register<ZPackage>("ModDoctor_Hello", (Action<long, ZPackage>)OnHello);
			instance.Register<ZPackage>("ModDoctor_Diff", (Action<long, ZPackage>)OnDiff);
			instance.Register<ZPackage>("ModDoctor_AdminRequest", (Action<long, ZPackage>)OnAdminRequest);
			instance.Register<ZPackage>("ModDoctor_AdminData", (Action<long, ZPackage>)OnAdminData);
			registeredRpc = instance;
			lastSentVersion = -1;
			helloSent = false;
			serverDiff = null;
			serverIsAdmin = false;
			adminData = null;
		}
		if (ZNet.instance.IsServer() || !sendToServer.Value)
		{
			return;
		}
		ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
		if (serverPeer == null || (Object)(object)Game.instance == (Object)null || Game.instance.GetPlayerProfile() == null)
		{
			return;
		}
		string name = Game.instance.GetPlayerProfile().GetName();
		if (!helloSent && Time.unscaledTime >= nextHello && installed != null)
		{
			nextHello = Time.unscaledTime + 5f;
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(name);
			val.Write("1.1.0");
			val.Write(Inventory.Serialize(installed));
			instance.InvokeRoutedRPC(serverPeer.m_uid, "ModDoctor_Hello", new object[1] { val });
			helloSent = true;
		}
		if (!(Time.unscaledTime < nextSend))
		{
			nextSend = Time.unscaledTime + 60f;
			int version = Diagnosis.Version;
			if (version != lastSentVersion)
			{
				ZPackage val2 = new ZPackage();
				val2.Write(1);
				val2.Write(name);
				val2.Write("1.1.0");
				val2.Write(Diagnosis.PluginCount);
				val2.Write(ReportWriter.Serialize(Diagnosis.Snapshot(), updates, 40));
				instance.InvokeRoutedRPC(serverPeer.m_uid, "ModDoctor_Report", new object[1] { val2 });
				lastSentVersion = version;
			}
		}
	}

	private bool IsServerSide()
	{
		if ((Object)(object)ZNet.instance != (Object)null)
		{
			return ZNet.instance.IsServer();
		}
		return false;
	}

	private bool SenderIsAdmin(long sender)
	{
		ZNetPeer peer = ZNet.instance.GetPeer(sender);
		if (peer == null || peer.m_socket == null)
		{
			return false;
		}
		return ZNet.instance.IsAdmin(peer.m_socket.GetHostName());
	}

	private void OnClientReport(long sender, ZPackage pkg)
	{
		if (!IsServerSide() || !collectFromClients.Value)
		{
			return;
		}
		try
		{
			int num = pkg.ReadInt();
			if (num == 1)
			{
				string player = pkg.ReadString();
				string clientVersion = pkg.ReadString();
				int plugins = pkg.ReadInt();
				string text = pkg.ReadString();
				if (text.Length > 64000)
				{
					text = text.Substring(0, 64000);
				}
				ServerStore.Save(outputDir, player, clientVersion, plugins, text, Lang());
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Ignored a malformed report from peer " + sender + ": " + ex.Message));
		}
	}

	private void OnHello(long sender, ZPackage pkg)
	{
		//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Expected O, but got Unknown
		if (!IsServerSide())
		{
			return;
		}
		try
		{
			if (pkg.ReadInt() == 1)
			{
				string text = pkg.ReadString();
				pkg.ReadString();
				List<InstalledMod> player = Inventory.Parse(pkg.ReadString());
				List<ModDifference> list = ModCompare.Compare(player, installed ?? new List<InstalledMod>(), serverOnlyMods.Value.Split(','));
				PlayerMods playerMods = new PlayerMods();
				playerMods.Name = text;
				playerMods.When = DateTime.Now;
				playerMods.Diff = list;
				lock (this.playerMods)
				{
					this.playerMods[text] = playerMods;
				}
				if (collectFromClients.Value)
				{
					ServerStore.SaveMods(outputDir, text, ModCompare.Serialize(list), Lang());
				}
				ZPackage val = new ZPackage();
				val.Write(1);
				val.Write(SenderIsAdmin(sender));
				val.Write(ModCompare.Serialize(list));
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "ModDoctor_Diff", new object[1] { val });
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Ignored a malformed mod list from peer " + sender + ": " + ex.Message));
		}
	}

	private void OnDiff(long sender, ZPackage pkg)
	{
		if (IsServerSide())
		{
			return;
		}
		try
		{
			if (pkg.ReadInt() == 1)
			{
				serverIsAdmin = pkg.ReadBool();
				serverDiff = ModCompare.Parse(pkg.ReadString());
				if (serverDiff.Any((ModDifference d) => d.Kind != "extra"))
				{
					diffToastPending = true;
				}
				nextWindowRefresh = 0f;
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Ignored a malformed answer from the server: " + ex.Message));
		}
	}

	private void OnAdminRequest(long sender, ZPackage pkg)
	{
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Expected O, but got Unknown
		if (IsServerSide())
		{
			AdminData obj;
			if (!SenderIsAdmin(sender))
			{
				AdminData adminData = new AdminData();
				adminData.NotAdmin = true;
				obj = adminData;
			}
			else
			{
				obj = BuildAdminData();
			}
			AdminData adminData2 = obj;
			string text = adminData2.Serialize();
			if (text.Length > 60000)
			{
				text = text.Substring(0, 60000);
			}
			ZPackage val = new ZPackage();
			val.Write(2);
			val.Write(text);
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "ModDoctor_AdminData", new object[1] { val });
		}
	}

	private void OnAdminData(long sender, ZPackage pkg)
	{
		if (IsServerSide())
		{
			return;
		}
		try
		{
			if (pkg.ReadInt() == 2)
			{
				adminData = AdminData.Parse(pkg.ReadString());
				nextWindowRefresh = 0f;
			}
		}
		catch (Exception)
		{
		}
	}

	private void RequestAdminOverview()
	{
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Expected O, but got Unknown
		ZNetPeer val = (((Object)(object)ZNet.instance == (Object)null) ? null : ZNet.instance.GetServerPeer());
		if (val != null && ZRoutedRpc.instance != null && !(Time.unscaledTime - adminRequestedAt < 5f))
		{
			adminRequestedAt = Time.unscaledTime;
			ZPackage val2 = new ZPackage();
			val2.Write(1);
			val2.Write(Lang());
			ZRoutedRpc.instance.InvokeRoutedRPC(val.m_uid, "ModDoctor_AdminRequest", new object[1] { val2 });
		}
	}

	private AdminData BuildAdminData()
	{
		AdminData adminData = new AdminData();
		adminData.Uptime = Health.Uptime(DateTime.Now - health.Started);
		adminData.Fps = health.AverageFps();
		adminData.FpsLow = health.LowFps();
		adminData.MemoryMb = (double)GC.GetTotalMemory(forceFullCollection: false) / 1048576.0;
		adminData.Objects = WorldObjects.TotalKnown();
		List<ZNetPeer> peers = ZNet.instance.GetPeers();
		adminData.Online = peers.Count;
		List<string> list = (from p in peers
			select p.m_playerName into n
			where !string.IsNullOrEmpty(n)
			select n).ToList();
		foreach (string item in list)
		{
			adminData.Get(item).Online = true;
		}
		lock (playerMods)
		{
			foreach (PlayerMods value in playerMods.Values)
			{
				AdminData.AdminPlayer adminPlayer = adminData.Get(value.Name);
				adminPlayer.HasModList = true;
				adminPlayer.Diffs = value.Diff.Where((ModDifference d) => d.Kind != "extra").ToList();
			}
		}
		foreach (KeyValuePair<string, List<string[]>> item2 in ServerStore.ProblemRows(outputDir))
		{
			adminData.Get(item2.Key).Problems = item2.Value;
		}
		return adminData;
	}

	private void RefreshWindow()
	{
		nextWindowRefresh = Time.unscaledTime + 1f;
		TabContext tabContext = new TabContext();
		tabContext.Lang = Lang();
		tabContext.Problems = Diagnosis.Snapshot();
		tabContext.Updates = updates;
		tabContext.Packages = Diagnosis.Packages().ToList();
		tabContext.Connected = OnDedicatedServerAsClient();
		tabContext.ServerDiff = serverDiff;
		tabContext.Duplicates = duplicates ?? new List<string>();
		tabContext.LoadTimes = loadTimer.Times();
		tabContext.PluginCount = Diagnosis.PluginCount;
		tabContext.InstalledCount = ((installed != null) ? installed.Count : 0);
		tabContext.Health = health;
		tabContext.InWorld = (Object)(object)ZNetScene.instance != (Object)null;
		if (tab == 2 && tabContext.InWorld && Time.unscaledTime >= nextObjectsRefresh)
		{
			nextObjectsRefresh = Time.unscaledTime + 3f;
			topPrefabs = WorldObjects.TopPrefabs(15);
		}
		tabContext.TopPrefabs = topPrefabs;
		tabContext.ObjectsLoaded = WorldObjects.Loaded();
		tabContext.ObjectsKnown = WorldObjects.TotalKnown();
		if (tab == 3 && adminData == null)
		{
			RequestAdminOverview();
		}
		tabContext.Admin = adminData;
		tabContext.Expanded = expanded;
		view = tabContext;
	}

	private void OnGUI()
	{
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Expected O, but got Unknown
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00aa: Expected O, but got Unknown
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Unknown result type (might be due to invalid IL or missing references)
		//IL_0171: Expected O, but got Unknown
		//IL_0182: 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_01bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d5: Expected O, but got Unknown
		//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
		if (!IsHeadless() && ((toastText != null && !(Time.unscaledTime >= toastUntil)) || windowOpen))
		{
			long timestamp = Stopwatch.GetTimestamp();
			Theme.Init();
			if (toastStyle == null)
			{
				toastStyle = new GUIStyle(Theme.CardBox);
				toastStyle.font = Theme.Body.font;
				toastStyle.fontSize = 15;
				toastStyle.wordWrap = true;
				toastStyle.richText = true;
				toastStyle.alignment = (TextAnchor)3;
				toastStyle.padding = new RectOffset(14, 14, 10, 10);
				toastStyle.normal.textColor = Theme.Gold;
				Log.LogInfo((object)("Window fonts: title " + (((Object)(object)Theme.Title.font == (Object)null) ? "default" : ((Object)Theme.Title.font).name) + ", body " + (((Object)(object)Theme.Body.font == (Object)null) ? "default" : ((Object)Theme.Body.font).name)));
				Log.LogDebug((object)("Fonts available: " + Theme.FontReport()));
			}
			if (toastText != null && Time.unscaledTime < toastUntil)
			{
				float num = toastStyle.CalcHeight(new GUIContent(toastText), 460f);
				GUI.Box(new Rect(20f, 20f, 460f, num), toastText, toastStyle);
			}
			if (windowOpen && view != null)
			{
				windowRect = GUILayout.Window(19780, windowRect, new WindowFunction(DrawWindow), "", Theme.Window, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
			Interlocked.Add(ref health.OwnTicks, Stopwatch.GetTimestamp() - timestamp);
		}
	}

	private void DrawWindow(int id)
	{
		//IL_0295: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_024e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0253: Unknown result type (might be due to invalid IL or missing references)
		//IL_03f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_03fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0448: Unknown result type (might be due to invalid IL or missing references)
		TabContext tabContext = view;
		string lang = tabContext.Lang;
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("MOD DOCTOR", Theme.Title, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
		GUILayout.Space(10f);
		GUILayout.Label("1.1.0", Theme.Version, (GUILayoutOption[])(object)new GUILayoutOption[2]
		{
			GUILayout.ExpandWidth(false),
			GUILayout.Height(28f)
		});
		GUILayout.FlexibleSpace();
		if (GUILayout.Button("X", Theme.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(34f) }))
		{
			windowOpen = false;
		}
		GUILayout.EndHorizontal();
		GUILayout.Space(6f);
		int n = (from p in tabContext.Problems
			where p.Mod != null && p.Kind != "engine"
			select p.Mod.Name).Distinct().Count();
		int n2 = ((tabContext.ServerDiff != null) ? (tabContext.ServerDiff.Count((ModDifference d) => d.Kind != "extra") + tabContext.Duplicates.Count) : 0);
		List<string> list = new List<string>();
		list.Add(Text.T("tab_problems", lang) + Badge(n, "#D9534F"));
		list.Add(Text.T("tab_mods", lang) + Badge(n2, "#F0A050"));
		list.Add(Text.T("tab_perf", lang));
		List<string> list2 = list;
		if (tabContext.Connected && (serverIsAdmin || ZNet.instance.LocalPlayerIsAdminOrHost()))
		{
			list2.Add(Text.T("tab_server", lang));
		}
		if (tab >= list2.Count)
		{
			tab = 0;
		}
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		for (int num = 0; num < list2.Count; num++)
		{
			if (GUILayout.Button(list2[num], (num == tab) ? Theme.TabActive : Theme.Tab, (GUILayoutOption[])(object)new GUILayoutOption[0]) && num != tab)
			{
				tab = num;
				scroll = Vector2.zero;
				if (tab == 3)
				{
					adminData = null;
				}
				nextWindowRefresh = 0f;
			}
		}
		GUILayout.EndHorizontal();
		GUILayout.Space(8f);
		scroll = GUILayout.BeginScrollView(scroll, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		switch (tab)
		{
		case 0:
			Tabs.Problems(tabContext);
			break;
		case 1:
			Tabs.Mods(tabContext);
			break;
		case 2:
			Tabs.Performance(tabContext);
			break;
		case 3:
			Tabs.Server(tabContext);
			break;
		}
		GUILayout.EndScrollView();
		GUILayout.Space(6f);
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (tab == 0 && GUILayout.Button(Text.T("copy", lang), Theme.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) }))
		{
			GUIUtility.systemCopyBuffer = BuildReport(rich: false);
		}
		if (tab == 3 && GUILayout.Button(Text.T("refresh", lang), Theme.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) }))
		{
			adminData = null;
			nextWindowRefresh = 0f;
		}
		if (GUILayout.Button(Text.T("folder", lang), Theme.Button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) }))
		{
			Application.OpenURL("file://" + outputDir.Replace('\\', '/'));
		}
		GUILayout.FlexibleSpace();
		GUILayout.Label(Text.T("footer", lang).Replace("{key}", ((object)toggleKey.Value/*cast due to .constrained prefix*/).ToString()), Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
		GUILayout.EndHorizontal();
		GUI.DragWindow(new Rect(0f, 0f, 10000f, 44f));
	}

	private static string Badge(int n, string color)
	{
		if (n > 0)
		{
			return "  <color=" + color + ">" + n + "</color>";
		}
		return "";
	}
}
public class InstalledMod
{
	public string Id;

	public string Version;

	public override string ToString()
	{
		return Id + " " + Version;
	}
}
public static class Inventory
{
	public static List<InstalledMod> Scan()
	{
		List<InstalledMod> list = new List<InstalledMod>();
		string pluginPath = Paths.PluginPath;
		if (!Directory.Exists(pluginPath))
		{
			return list;
		}
		Dictionary<string, string> dictionary = PluginVersionsByFolder(pluginPath);
		string[] directories = Directory.GetDirectories(pluginPath);
		foreach (string text in directories)
		{
			if (Directory.GetFiles(text, "*.dll", SearchOption.AllDirectories).Length == 0)
			{
				continue;
			}
			InstalledMod installedMod = new InstalledMod();
			installedMod.Id = Path.GetFileName(text);
			string path = Path.Combine(text, "manifest.json");
			if (File.Exists(path))
			{
				Match match = Regex.Match(File.ReadAllText(path), "\"version_number\"\\s*:\\s*\"([^\"]+)\"");
				if (match.Success)
				{
					installedMod.Version = match.Groups[1].Value;
				}
			}
			if (installedMod.Version == null)
			{
				dictionary.TryGetValue(installedMod.Id, out var value);
				installedMod.Version = value ?? "?";
			}
			list.Add(installedMod);
		}
		string[] files = Directory.GetFiles(pluginPath, "*.dll");
		foreach (string path2 in files)
		{
			InstalledMod installedMod2 = new InstalledMod();
			installedMod2.Id = Path.GetFileNameWithoutExtension(path2);
			dictionary.TryGetValue(installedMod2.Id, out var value2);
			installedMod2.Version = value2 ?? "?";
			list.Add(installedMod2);
		}
		return list.OrderBy<InstalledMod, string>((InstalledMod m) => m.Id, StringComparer.OrdinalIgnoreCase).ToList();
	}

	private static Dictionary<string, string> PluginVersionsByFolder(string root)
	{
		Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		string fullPath = Path.GetFullPath(root);
		foreach (PluginInfo value in Chainloader.PluginInfos.Values)
		{
			if (string.IsNullOrEmpty(value.Location))
			{
				continue;
			}
			string fullPath2 = Path.GetFullPath(value.Location);
			if (fullPath2.StartsWith(fullPath, StringComparison.OrdinalIgnoreCase))
			{
				string text = fullPath2.Substring(fullPath.Length).TrimStart('\\', '/');
				int num = text.IndexOfAny(new char[2] { '\\', '/' });
				string key = ((num < 0) ? Path.GetFileNameWithoutExtension(text) : text.Substring(0, num));
				if (!dictionary.ContainsKey(key))
				{
					dictionary[key] = value.Metadata.Version.ToString();
				}
			}
		}
		return dictionary;
	}

	public static string Serialize(List<InstalledMod> mods)
	{
		StringBuilder stringBuilder = new StringBuilder();
		foreach (InstalledMod mod in mods)
		{
			stringBuilder.Append(mod.Id).Append('\t').Append(mod.Version)
				.Append('\n');
		}
		return stringBuilder.ToString();
	}

	public static List<InstalledMod> Parse(string text)
	{
		List<InstalledMod> list = new List<InstalledMod>();
		string[] array = text.Split('\n');
		foreach (string text2 in array)
		{
			string[] array2 = text2.Split('\t');
			if (array2.Length >= 2 && array2[0].Length != 0)
			{
				InstalledMod installedMod = new InstalledMod();
				installedMod.Id = array2[0];
				installedMod.Version = array2[1].Trim();
				list.Add(installedMod);
			}
		}
		return list;
	}

	public static List<string> Duplicates()
	{
		List<string> list = new List<string>();
		string pluginPath = Paths.PluginPath;
		if (!Directory.Exists(pluginPath))
		{
			return list;
		}
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		foreach (PluginInfo value2 in Chainloader.PluginInfos.Values)
		{
			if ((Object)(object)value2.Instance != (Object)null)
			{
				hashSet.Add(((object)value2.Instance).GetType().Assembly.GetName().Name);
			}
		}
		Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
		string fullPath = Path.GetFullPath(pluginPath);
		string[] files = Directory.GetFiles(pluginPath, "*.dll", SearchOption.AllDirectories);
		foreach (string text in files)
		{
			string name;
			try
			{
				name = AssemblyName.GetAssemblyName(text).Name;
			}
			catch (Exception)
			{
				continue;
			}
			if (hashSet.Contains(name))
			{
				string text2 = Path.GetFullPath(text).Substring(fullPath.Length).TrimStart('\\', '/');
				int num = text2.IndexOfAny(new char[2] { '\\', '/' });
				string item = ((num < 0) ? ("plugins/" + text2) : text2.Substring(0, num));
				if (!dictionary.TryGetValue(name, out var value))
				{
					value = (dictionary[name] = new List<string>());
				}
				if (!value.Contains(item))
				{
					value.Add(item);
				}
			}
		}
		foreach (KeyValuePair<string, List<string>> item2 in dictionary)
		{
			if (item2.Value.Count > 1)
			{
				list.Add(item2.Key + ": " + string.Join(", ", item2.Value.ToArray()));
			}
		}
		return list;
	}
}
public class ModDifference
{
	public string Kind;

	public string Id;

	public string PlayerVersion;

	public string ServerVersion;
}
public static class ModCompare
{
	public static List<ModDifference> Compare(List<InstalledMod> player, List<InstalledMod> server, string[] serverOnly)
	{
		Dictionary<string, InstalledMod> dictionary = ToMap(player);
		Dictionary<string, InstalledMod> dictionary2 = ToMap(server);
		List<ModDifference> list = new List<ModDifference>();
		foreach (InstalledMod value2 in dictionary2.Values)
		{
			if (!dictionary.TryGetValue(value2.Id, out var value))
			{
				if (!Matches(value2.Id, serverOnly))
				{
					list.Add(Diff("missing", value2.Id, null, value2.Version));
				}
			}
			else if (value.Version != value2.Version)
			{
				list.Add(Diff("version", value2.Id, value.Version, value2.Version));
			}
		}
		foreach (InstalledMod value3 in dictionary.Values)
		{
			if (!dictionary2.ContainsKey(value3.Id))
			{
				list.Add(Diff("extra", value3.Id, value3.Version, null));
			}
		}
		return (from d in list
			orderby (!(d.Kind == "missing")) ? ((d.Kind == "version") ? 1 : 2) : 0, d.Id
			select d).ToList();
	}

	private static Dictionary<string, InstalledMod> ToMap(List<InstalledMod> mods)
	{
		Dictionary<string, InstalledMod> dictionary = new Dictionary<string, InstalledMod>(StringComparer.OrdinalIgnoreCase);
		foreach (InstalledMod mod in mods)
		{
			dictionary[mod.Id] = mod;
		}
		return dictionary;
	}

	private static ModDifference Diff(string kind, string id, string pv, string sv)
	{
		ModDifference modDifference = new ModDifference();
		modDifference.Kind = kind;
		modDifference.Id = id;
		modDifference.PlayerVersion = pv;
		modDifference.ServerVersion = sv;
		return modDifference;
	}

	public static bool Matches(string id, string[] patterns)
	{
		foreach (string text in patterns)
		{
			string text2 = text.Trim();
			if (text2.Length != 0)
			{
				string pattern = "^" + Regex.Escape(text2).Replace("\\*", ".*") + "$";
				if (Regex.IsMatch(id, pattern, RegexOptions.IgnoreCase))
				{
					return true;
				}
			}
		}
		return false;
	}

	public static string Serialize(List<ModDifference> diffs)
	{
		StringBuilder stringBuilder = new StringBuilder();
		foreach (ModDifference diff in diffs)
		{
			stringBuilder.Append(diff.Kind).Append('\t').Append(diff.Id)
				.Append('\t')
				.Append(diff.PlayerVersion ?? "")
				.Append('\t')
				.Append(diff.ServerVersion ?? "")
				.Append('\n');
		}
		return stringBuilder.ToString();
	}

	public static List<ModDifference> Parse(string text)
	{
		List<ModDifference> list = new List<ModDifference>();
		string[] array = text.Split('\n');
		foreach (string text2 in array)
		{
			string[] array2 = text2.Split('\t');
			if (array2.Length >= 4)
			{
				list.Add(Diff(array2[0], array2[1], (array2[2].Length == 0) ? null : array2[2], (array2[3].Trim().Length == 0) ? null : array2[3].Trim()));
			}
		}
		return list;
	}

	public static string Render(List<ModDifference> diffs, string lang, bool rich)
	{
		StringBuilder stringBuilder = new StringBuilder();
		List<ModDifference> list = diffs.Where((ModDifference d) => d.Kind == "missing").ToList();
		List<ModDifference> list2 = diffs.Where((ModDifference d) => d.Kind == "version").ToList();
		List<ModDifference> list3 = diffs.Where((ModDifference d) => d.Kind == "extra").ToList();
		if (list.Count == 0 && list2.Count == 0)
		{
			stringBuilder.Append(Color(Text.T("diff_ok", lang), "#8FE08F", rich)).Append('\n');
		}
		if (list.Count > 0)
		{
			stringBuilder.Append(Color(Text.T("diff_missing", lang), "#FFA366", rich)).Append('\n');
			foreach (ModDifference item in list)
			{
				stringBuilder.Append("  - ").Append(item.Id).Append(' ')
					.Append(item.ServerVersion)
					.Append('\n');
			}
		}
		if (list2.Count > 0)
		{
			stringBuilder.Append(Color(Text.T("diff_version", lang), "#FFA366", rich)).Append('\n');
			foreach (ModDifference item2 in list2)
			{
				stringBuilder.Append("  - ").Append(item2.Id).Append(": ")
					.Append(Text.T("yours", lang))
					.Append(' ')
					.Append(item2.PlayerVersion)
					.Append(", ")
					.Append(Text.T("server", lang))
					.Append(' ')
					.Append(item2.ServerVersion)
					.Append('\n');
			}
		}
		if (list3.Count > 0)
		{
			stringBuilder.Append(Color(Text.T("diff_extra", lang), "#A8A29A", rich)).Append('\n');
			foreach (ModDifference item3 in list3)
			{
				stringBuilder.Append("  - ").Append(item3.Id).Append(' ')
					.Append(item3.PlayerVersion)
					.Append('\n');
			}
		}
		return stringBuilder.ToString();
	}

	private static string Color(string text, string color, bool rich)
	{
		if (!rich)
		{
			return text;
		}
		return "<b><color=" + color + ">" + text + "</color></b>";
	}
}
internal class LoadTimer : ILogListener, IDisposable
{
	private static readonly Regex Loading = new Regex("^Loading \\[(.+?)\\s+([\\d\\.]+)\\]");

	private readonly Stopwatch clock = Stopwatch.StartNew();

	private readonly List<KeyValuePair<string, double>> times = new List<KeyValuePair<string, double>>();

	private string current;

	private double currentStart;

	private bool done;

	public bool Done => done;

	public void LogEvent(object sender, LogEventArgs e)
	{
		if (done || e.Data == null)
		{
			return;
		}
		string text = ((e.Source == null) ? "" : e.Source.SourceName);
		if (!(text != "BepInEx"))
		{
			string text2 = e.Data.ToString();
			double totalSeconds = clock.Elapsed.TotalSeconds;
			Match match = Loading.Match(text2);
			if (match.Success)
			{
				Close(totalSeconds);
				current = match.Groups[1].Value;
				currentStart = totalSeconds;
			}
			else if (text2.StartsWith("Chainloader startup complete"))
			{
				Close(totalSeconds);
				done = true;
			}
		}
	}

	private void Close(double now)
	{
		if (current != null)
		{
			lock (times)
			{
				times.Add(new KeyValuePair<string, double>(current, now - currentStart));
			}
			current = null;
		}
	}

	public List<KeyValuePair<string, double>> Times()
	{
		lock (times)
		{
			return times.ToList();
		}
	}

	public void Dispose()
	{
	}
}
public class Health
{
	private readonly float[] frames = new float[600];

	private int count;

	private int next;

	private int[] gcBaseline = new int[3];

	private DateTime gcBaselineAt = DateTime.Now;

	public readonly DateTime Started = DateTime.Now;

	public long OwnTicks;

	public long OwnFrames;

	public long ErrorsSeen;

	public int SampleCount => count;

	public void Sample(float deltaTime)
	{
		frames[next] = deltaTime;
		next = (next + 1) % frames.Length;
		if (count < frames.Length)
		{
			count++;
		}
	}

	public void ResetGcWindow()
	{
		for (int i = 0; i < 3; i++)
		{
			gcBaseline[i] = GC.CollectionCount(i);
		}
		gcBaselineAt = DateTime.Now;
	}

	public double AverageFps()
	{
		if (count == 0)
		{
			return 0.0;
		}
		double num = 0.0;
		for (int i = 0; i < count; i++)
		{
			num += (double)frames[i];
		}
		if (!(num <= 0.0))
		{
			return (double)count / num;
		}
		return 0.0;
	}

	public double LowFps()
	{
		if (count < 20)
		{
			return 0.0;
		}
		float[] source = (from f in frames.Take(count)
			orderby f descending
			select f).ToArray();
		int num = Math.Max(1, count / 100);
		double num2 = source.Take(num).Average((Func<float, double>)((float f) => f));
		if (!(num2 <= 0.0))
		{
			return 1.0 / num2;
		}
		return 0.0;
	}

	public double WorstFrameMs()
	{
		if (count == 0)
		{
			return 0.0;
		}
		return (double)frames.Take(count).Max() * 1000.0;
	}

	public double[] GcPerMinute()
	{
		double num = Math.Max(1.0 / 60.0, (DateTime.Now - gcBaselineAt).TotalMinutes);
		double[] array = new double[3];
		for (int i = 0; i < 3; i++)
		{
			array[i] = (double)(GC.CollectionCount(i) - gcBaseline[i]) / num;
		}
		return array;
	}

	public double OwnMsPerFrame()
	{
		if (OwnFrames != 0)
		{
			return (double)OwnTicks * 1000.0 / (double)Stopwatch.Frequency / (double)OwnFrames;
		}
		return 0.0;
	}

	public double OwnMsTotal()
	{
		return (double)OwnTicks * 1000.0 / (double)Stopwatch.Frequency;
	}

	public static string Mb(long bytes)
	{
		return ((double)bytes / 1048576.0).ToString("0") + " MB";
	}

	public static string Uptime(TimeSpan t)
	{
		return (int)t.TotalHours + "h" + t.Minutes.ToString("00") + "m";
	}
}
public static class WorldObjects
{
	private static FieldInfo instancesField;

	public static int TotalKnown()
	{
		if (ZDOMan.instance != null)
		{
			return ZDOMan.instance.NrOfObjects();
		}
		return 0;
	}

	public static int Loaded()
	{
		if (!((Object)(object)ZNetScene.instance == (Object)null))
		{
			return ZNetScene.instance.NrOfInstances();
		}
		return 0;
	}

	public static List<KeyValuePair<string, int>> TopPrefabs(int max)
	{
		List<KeyValuePair<string, int>> result = new List<KeyValuePair<string, int>>();
		if ((Object)(object)ZNetScene.instance == (Object)null)
		{
			return result;
		}
		if (instancesField == null)
		{
			instancesField = AccessTools.Field(typeof(ZNetScene), "m_instances");
		}
		IDictionary dictionary = ((instancesField == null) ? null : (instancesField.GetValue(ZNetScene.instance) as IDictionary));
		if (dictionary == null)
		{
			return result;
		}
		Dictionary<string, int> dictionary2 = new Dictionary<string, int>();
		foreach (object value2 in dictionary.Values)
		{
			ZNetView val = (ZNetView)((value2 is ZNetView) ? value2 : null);
			if (!((Object)(object)val == (Object)null))
			{
				string text = ((Object)((Component)val).gameObject).name;
				int num = text.IndexOf("(Clone)", StringComparison.Ordinal);
				if (num > 0)
				{
					text = text.Substring(0, num);
				}
				dictionary2.TryGetValue(text, out var value);
				dictionary2[text] = value + 1;
			}
		}
		return dictionary2.OrderByDescending((KeyValuePair<string, int> kv) => kv.Value).Take(max).ToList();
	}
}
public static class Theme
{
	public static readonly Color Gold = Hex("E8C170");

	public static readonly Color GoldDim = Hex("9C7A45");

	public static readonly Color Text = Hex("EDE3D0");

	public static readonly Color Muted = Hex("D2C6B2");

	public static readonly Color Soft = Hex("C9BCA6");

	public static readonly Color Red = Hex("D9534F");

	public static readonly Color Orange = Hex("F0A050");

	public static readonly Color Yellow = Hex("E6C84A");

	public static readonly Color Green = Hex("7FCF7F");

	public static readonly Color Blue = Hex("7FB2E5");

	public static readonly Color Slate = Hex("8A94A6");

	private static readonly Color PanelTop = Hex("2A2016");

	private static readonly Color PanelBottom = Hex("15100B");

	private static readonly Color Card = Hex("2E2419");

	private static readonly Color Border = Hex("6B5230");

	public static GUIStyle Window;

	public static GUIStyle Title;

	public static GUIStyle Version;

	public static GUIStyle Tab;

	public static GUIStyle TabActive;

	public static GUIStyle CardBox;

	public static GUIStyle CardTitle;

	public static GUIStyle Body;

	public static GUIStyle Small;

	public static GUIStyle Mono;

	public static GUIStyle Hint;

	public static GUIStyle Chip;

	public static GUIStyle TileBox;

	public static GUIStyle TileValue;

	public static GUIStyle TileLabel;

	public static GUIStyle Button;

	public static GUIStyle Section;

	public static GUIStyle Banner;

	public static Texture2D White;

	private static bool ready;

	private static readonly Stack<Color> strips = new Stack<Color>();

	public static Color Hex(string hex)
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		Color result = default(Color);
		ColorUtility.TryParseHtmlString("#" + hex, ref result);
		return result;
	}

	public static void Init()
	{
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: 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_008e: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0105: Unknown result type (might be due to invalid IL or missing references)
		//IL_010a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0120: Unknown result type (might be due to invalid IL or missing references)
		//IL_012a: Expected O, but got Unknown
		//IL_017f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0189: Expected O, but got Unknown
		//IL_0196: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a0: Expected O, but got Unknown
		//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01db: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e5: Expected O, but got Unknown
		//IL_0243: Unknown result type (might be due to invalid IL or missing references)
		//IL_0257: Unknown result type (might be due to invalid IL or missing references)
		//IL_026b: Unknown result type (might be due to invalid IL or missing references)
		//IL_027e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0288: Expected O, but got Unknown
		//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b1: Expected O, but got Unknown
		//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_0308: Unknown result type (might be due to invalid IL or missing references)
		//IL_0312: Expected O, but got Unknown
		//IL_032b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0335: Expected O, but got Unknown
		//IL_0341: Unknown result type (might be due to invalid IL or missing references)
		//IL_034b: Expected O, but got Unknown
		//IL_0354: Unknown result type (might be due to invalid IL or missing references)
		//IL_035e: Expected O, but got Unknown
		//IL_0366: Unknown result type (might be due to invalid IL or missing references)
		//IL_0379: Unknown result type (might be due to invalid IL or missing references)
		//IL_038c: Unknown result type (might be due to invalid IL or missing references)
		//IL_03a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_03b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_03cf: Unknown result type (might be due to invalid IL or missing references)
		//IL_03e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_03f3: Expected O, but got Unknown
		//IL_03fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0407: Expected O, but got Unknown
		//IL_044c: Unknown result type (might be due to invalid IL or missing references)
		//IL_045f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0469: Expected O, but got Unknown
		//IL_0472: Unknown result type (might be due to invalid IL or missing references)
		//IL_047c: Expected O, but got Unknown
		//IL_049c: Unknown result type (might be due to invalid IL or missing references)
		//IL_04a6: Expected O, but got Unknown
		//IL_04c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_04ca: Expected O, but got Unknown
		//IL_04d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_04df: Expected O, but got Unknown
		//IL_04e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_04f2: Expected O, but got Unknown
		//IL_04f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0508: Unknown result type (might be due to invalid IL or missing references)
		//IL_0522: Unknown result type (might be due to invalid IL or missing references)
		//IL_052c: Expected O, but got Unknown
		//IL_0583: Unknown result type (might be due to invalid IL or missing references)
		//IL_05a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_05a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_05a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_05ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_05be: Unknown result type (might be due to invalid IL or missing references)
		//IL_05c8: Expected O, but got Unknown
		//IL_05d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_05dd: Expected O, but got Unknown
		//IL_05e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_05f1: Expected O, but got Unknown
		//IL_0636: Unknown result type (might be due to invalid IL or missing references)
		//IL_0656: Unknown result type (might be due to invalid IL or missing references)
		//IL_0660: Expected O, but got Unknown
		//IL_0669: Unknown result type (might be due to invalid IL or missing references)
		//IL_0673: Expected O, but got Unknown
		if (!ready)
		{
			ready = true;
			Font val = FindFont(new string[2] { "norsebold", "norse" });
			Font val2 = FindFont(new string[3] { "averiaseriflibre-regular", "averiaseriflibre", "averia" });
			White = Solid(Color.white);
			Texture2D val3 = Gradient(PanelTop, PanelBottom, 128, Border);
			Texture2D background = Framed(Card, Hex("4A3A26"));
			Texture2D background2 = Framed(Hex("241B12"), Hex("4A3A26"));
			Texture2D background3 = Framed(Hex("241B12"), Hex("4A3A26"));
			Texture2D val4 = Framed(Hex("5A4222"), Gold);
			Texture2D background4 = Framed(Hex("33271A"), Hex("6B5230"));
			Texture2D val5 = Framed(Hex("443321"), Gold);
			Window = new GUIStyle(GUI.skin.window);
			GUIStyleState normal = Window.normal;
			GUIStyleState onNormal = Window.onNormal;
			GUIStyleState focused = Window.focused;
			Texture2D val6 = (Window.onFocused.background = val3);
			Texture2D val8 = (focused.background = val6);
			Texture2D background5 = (onNormal.background = val8);
			normal.background = background5;
			Window.border = new RectOffset(3, 3, 3, 3);
			Window.padding = new RectOffset(16, 16, 12, 12);
			Title = Label(val, 22, Gold, (FontStyle)0);
			Version = Label(val2, 13, Muted, (FontStyle)0);
			Version.alignment = (TextAnchor)6;
			Tab = new GUIStyle(GUI.skin.button);
			Tab.font = val ?? val2;
			Tab.fontSize = 15;
			Tab.normal.background = background3;
			Tab.hover.background = val5;
			Tab.active.background = val4;
			Tab.normal.textColor = Hex("D8CBB2");
			Tab.hover.textColor = Gold;
			Tab.active.textColor = Gold;
			Tab.border = new RectOffset(3, 3, 3, 3);
			Tab.fixedHeight = 30f;
			Tab.richText = true;
			TabActive = new GUIStyle(Tab);
			GUIStyleState normal2 = TabActive.normal;
			Texture2D background6 = (TabActive.hover.background = val4);
			normal2.background = background6;
			GUIStyleState normal3 = TabActive.normal;
			Color textColor = (TabActive.hover.textColor = Gold);
			normal3.textColor = textColor;
			CardBox = new GUIStyle(GUI.skin.box);
			CardBox.normal.background = background;
			CardBox.border = new RectOffset(3, 3, 3, 3);
			CardBox.padding = new RectOffset(12, 12, 8, 10);
			CardBox.margin = new RectOffset(0, 0, 4, 6);
			CardTitle = Label(val ?? val2, 17, Gold, (FontStyle)0);
			Body = Label(val2, 14, Text, (FontStyle)0);
			Small = Label(val2, 13, Muted, (FontStyle)0);
			Mono = Label(val2, 13, Hex("E6DAC4"), (FontStyle)0);
			Hint = Label(val2, 13, Soft, (FontStyle)2);
			Section = Label(val ?? val2, 16, Gold, (FontStyle)0);
			Section.margin = new RectOffset(0, 0, 10, 2);
			Chip = new GUIStyle(GUI.skin.box);
			Chip.normal.background = White;
			Chip.font = val2;
			Chip.fontSize = 11;
			Chip.fontStyle = (FontStyle)1;
			Chip.normal.textColor = Hex("1A140E");
			Chip.padding = new RectOffset(6, 6, 2, 2);
			Chip.margin = new RectOffset(0, 6, 3, 3);
			Chip.alignment = (TextAnchor)4;
			Chip.stretchWidth = false;
			TileBox = new GUIStyle(GUI.skin.box);
			TileBox.normal.background = background2;
			TileBox.border = new RectOffset(3, 3, 3, 3);
			TileBox.padding = new RectOffset(10, 10, 8, 8);
			TileBox.margin = new RectOffset(0, 8, 4, 4);
			TileValue = Label(val2, 24, Text, (FontStyle)1);
			TileLabel = Label(val2, 12, Muted, (FontStyle)0);
			Button = new GUIStyle(GUI.skin.button);
			Button.font = val2;
			Button.fontSize = 13;
			Button.normal.background = background4;
			GUIStyleState hover = Button.hover;
			Texture2D background7 = (Button.active.background = val5);
			hover.background = background7;
			Button.normal.textColor = Text;
			GUIStyleState hover2 = Button.hover;
			Color textColor2 = (Button.active.textColor = Gold);
			hover2.textColor = textColor2;
			Button.border = new RectOffset(3, 3, 3, 3);
			Button.padding = new RectOffset(10, 10, 5, 5);
			Banner = new GUIStyle(GUI.skin.box);
			Banner.normal.background = White;
			Banner.font = val2;
			Banner.fontSize = 14;
			Banner.fontStyle = (FontStyle)1;
			Banner.normal.textColor = Hex("1A140E");
			Banner.alignment = (TextAnchor)3;
			Banner.padding = new RectOffset(12, 12, 8, 8);
			Banner.margin = new RectOffset(0, 0, 4, 8);
		}
	}

	private static GUIStyle Label(Font font, int size, Color color, FontStyle style)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Expected O, but got Unknown
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: Expected O, but got Unknown
		GUIStyle val = new GUIStyle(GUI.skin.label);
		if ((Object)(object)font != (Object)null)
		{
			val.font = font;
		}
		val.fontSize = size;
		val.fontStyle = style;
		val.normal.textColor = color;
		val.wordWrap = true;
		val.richText = true;
		val.margin = new RectOffset(0, 0, 1, 1);
		return val;
	}

	private static Font FindFont(string[] preferred)
	{
		List<Font> list = new List<Font>();
		try
		{
			TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
			foreach (TMP_FontAsset val in array)
			{
				if ((Object)(object)val != (Object)null && (Object)(object)val.sourceFontFile != (Object)null)
				{
					list.Add(val.sourceFontFile);
				}
			}
			list.AddRange(Resources.FindObjectsOfTypeAll<Font>());
		}
		catch (Exception)
		{
		}
		foreach (string value in preferred)
		{
			foreach (Font item in list)
			{
				if ((Object)(object)item != (Object)null && ((Object)item).name.Replace(" ", "").ToLower().Contains(value))
				{
					return item;
				}
			}
		}
		return null;
	}

	public static string FontReport()
	{
		List<string> list = new List<string>();
		try
		{
			TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
			foreach (TMP_FontAsset val in array)
			{
				if ((Object)(object)val != (Object)null && (Object)(object)val.sourceFontFile != (Object)null)
				{
					list.Add(((Object)val.sourceFontFile).name);
				}
			}
			Font[] array2 = Resources.FindObjectsOfTypeAll<Font>();
			foreach (Font val2 in array2)
			{
				list.Add(((Object)val2).name);
			}
		}
		catch (Exception)
		{
		}
		return string.Join(", ", list.Distinct().ToArray());
	}

	private static Texture2D Solid(Color c)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Expected O, but got Unknown
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		Texture2D val = new Texture2D(1, 1);
		val.SetPixel(0, 0, c);
		val.Apply();
		((Object)val).hideFlags = (HideFlags)61;
		return val;
	}

	private static Texture2D Gradient(Color top, Color bottom, int height, Color border)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Expected O, but got Unknown
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: 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)
		Texture2D val = new Texture2D(8, height);
		for (int i = 0; i < height; i++)
		{
			Color val2 = Color.Lerp(bottom, top, (float)i / (float)(height - 1));
			for (int j = 0; j < 8; j++)
			{
				bool flag = j < 2 || j > 5 || i < 2 || i > height - 3;
				val.SetPixel(j, i, flag ? border : val2);
			}
		}
		val.Apply();
		((Object)val).hideFlags = (HideFlags)61;
		return val;
	}

	private static Texture2D Framed(Color fill, Color border)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Expected O, but got Unknown
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		Texture2D val = new Texture2D(8, 8);
		for (int i = 0; i < 8; i++)
		{
			for (int j = 0; j < 8; j++)
			{
				bool flag = (j == 0 || j == 7) && (i == 0 || i == 7);
				bool flag2 = j == 0 || j == 7 || i == 0 || i == 7;
				val.SetPixel(j, i, (Color)(flag ? new Color(border.r, border.g, border.b, 0.35f) : (flag2 ? border : fill)));
			}
		}
		val.Apply();
		((Object)val).hideFlags = (HideFlags)61;
		return val;
	}

	public static void ChipLabel(string text, Color color)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		Color backgroundColor = GUI.backgroundColor;
		GUI.backgroundColor = color;
		GUILayout.Label(text, Chip, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUI.backgroundColor = backgroundColor;
	}

	public static void BannerLabel(string text, Color color)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		Color backgroundColor = GUI.backgroundColor;
		GUI.backgroundColor = color;
		GUILayout.Label(text, Banner, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUI.backgroundColor = backgroundColor;
	}

	public static void Tile(string value, string label, Color valueColor, float width)
	{
		//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_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginVertical(TileBox, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) });
		Color textColor = TileValue.normal.textColor;
		TileValue.normal.textColor = valueColor;
		GUILayout.Label(value, TileValue, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		TileValue.normal.textColor = textColor;
		GUILayout.Label(label, TileLabel, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.EndVertical();
	}

	public static void Bar(float fraction, Color color, float width, float height)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: 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_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		Rect rect = GUILayoutUtility.GetRect(width, height, (GUILayoutOption[])(object)new GUILayoutOption[2]
		{
			GUILayout.Width(width),
			GUILayout.Height(height)
		});
		((Rect)(ref rect)).y = ((Rect)(ref rect)).y + 3f;
		Color color2 = GUI.color;
		GUI.color = new Color(1f, 1f, 1f, 0.08f);
		GUI.DrawTexture(rect, (Texture)(object)White);
		GUI.color = color;
		GUI.DrawTexture(new Rect(((Rect)(ref rect)).x, ((Rect)(ref rect)).y, Mathf.Max(2f, ((Rect)(ref rect)).width * Mathf.Clamp01(fraction)), ((Rect)(ref rect)).height), (Texture)(object)White);
		GUI.color = color2;
	}

	public static void BeginCard(Color strip)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		strips.Push(strip);
		GUILayout.BeginHorizontal(CardBox, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Space(12f);
		GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[0]);
	}

	public static void EndCard()
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Invalid comparison between Unknown and I4
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: 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_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.EndVertical();
		Rect lastRect = GUILayoutUtility.GetLastRect();
		Color color = ((strips.Count > 0) ? strips.Pop() : Gold);
		if ((int)Event.current.type == 7)
		{
			Color color2 = GUI.color;
			GUI.color = color;
			GUI.DrawTexture(new Rect(((Rect)(ref lastRect)).x - 10f, ((Rect)(ref lastRect)).y + 2f, 4f, ((Rect)(ref lastRect)).height - 4f), (Texture)(object)White);
			GUI.color = color2;
		}
		GUILayout.EndHorizontal();
	}

	public static Color ForKind(string kind)
	{
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		switch (kind)
		{
		case "patch":
		case "incompatible":
			return Red;
		case "repeating":
		case "gamerepeating":
			return Orange;
		case "error":
			return Yellow;
		default:
			return Slate;
		}
	}

	public static Color Grade(double value, double good, double warn, bool higherIsBetter)
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: 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_000b: Unknown result type (might be due to invalid IL or missing references)
		if (higherIsBetter)
		{
			if (!(value >= good))
			{
				if (!(value >= warn))
				{
					return Red;
				}
				return Yellow;
			}
			return Green;
		}
		if (!(value <= good))
		{
			if (!(value <= warn))
			{
				return Red;
			}
			return Yellow;
		}
		return Green;
	}
}
public class AdminData
{
	public class AdminPlayer
	{
		public string Name;

		public bool Online;

		public bool HasModList;

		public List<ModDifference> Diffs = new List<ModDifference>();

		public List<string[]> Problems = new List<string[]>();
	}

	public bool NotAdmin;

	public string Uptime = "";

	public double Fps;

	public double FpsLow;

	public double MemoryMb;

	public int Objects;

	public int Online;

	public List<AdminPlayer> Players = new List<AdminPlayer>();

	public AdminPlayer Get(string name)
	{
		AdminPlayer adminPlayer = Players.FirstOrDefault((AdminPlayer x) => x.Name == name);
		if (adminPlayer == null)
		{
			adminPlayer = new AdminPlayer();
			adminPlayer.Name = name;
			Players.Add(adminPlayer);
		}
		return adminPlayer;
	}

	public string Serialize()
	{
		StringBuilder stringBuilder = new StringBuilder();
		if (NotAdmin)
		{
			return "X\tnotadmin\n";
		}
		stringBuilder.Append("H\t").Append(Uptime).Append('\t')
			.Append(Fps.ToString("0"))
			.Append('\t')
			.Append(FpsLow.ToString("0"))
			.Append('\t')
			.Append(MemoryMb.ToString("0"))
			.Append('\t')
			.Append(Objects)
			.Append('\t')
			.Append(Online)
			.Append('\n');
		foreach (AdminPlayer player in Players)
		{
			stringBuilder.Append("P\t").Append(Clean(player.Name)).Append('\t')
				.Append(player.Online ? 1 : 0)
				.Append('\t')
				.Append(player.HasModList ? 1 : 0)
				.Append('\n');
			foreach (ModDifference diff in player.Diffs)
			{
				stringBuilder.Append("D\t").Append(Clean(player.Name)).Append('\t')
					.Append(diff.Kind)
					.Append('\t')
					.Append(Clean(diff.Id))
					.Append('\t')
					.Append(Clean(diff.PlayerVersion ?? ""))
					.Append('\t')
					.Append(Clean(diff.ServerVersion ?? ""))
					.Append('\n');
			}
			foreach (string[] problem in player.Problems)
			{
				stringBuilder.Append("E\t").Append(Clean(player.Name)).Append('\t')
					.Append(string.Join("\t", problem.Select(Clean).ToArray()))
					.Append('\n');
			}
		}
		return stringBuilder.ToString();
	}

	public static AdminData Parse(string text)
	{
		AdminData adminData = new AdminData();
		string[] array = text.Split('\n');
		foreach (string text2 in array)
		{
			string[] array2 = text2.Split('\t');
			if (array2.Length == 0)
			{
				continue;
			}
			try
			{
				switch (array2[0])
				{
				case "X":
					adminData.NotAdmin = true;
					break;
				case "H":
					adminData.Uptime = array2[1];
					adminData.Fps = double.Parse(array2[2]);
					adminData.FpsLow = double.Parse(array2[3]);
					adminData.MemoryMb = double.Parse(array2[4]);
					adminData.Objects = int.Parse(array2[5]);
					adminData.Online = int.Parse(array2[6]);
					break;
				case "P":
				{
					AdminPlayer adminPlayer = adminData.Get(array2[1]);
					adminPlayer.Online = array2[2] == "1";
					adminPlayer.HasModList = array2[3] == "1";
					break;
				}
				case "D":
				{
					ModDifference modDifference = new ModDifference();
					modDifference.Kind = array2[2];
					modDifference.Id = array2[3];
					modDifference.PlayerVersion = ((array2[4].Length == 0) ? null : array2[4]);
					modDifference.ServerVersion = ((array2[5].Length == 0) ? null : array2[5]);
					adminData.Get(array2[1]).Diffs.Add(modDifference);
					break;
				}
				case "E":
					adminData.Get(array2[1]).Problems.Add(array2.Skip(2).ToArray());
					break;
				}
			}
			catch (Exception)
			{
			}
		}
		return adminData;
	}

	private static string Clean(string s)
	{
		return (s ?? "").Replace('\t', ' ').Replace('\n', ' ').Replace('\r', ' ');
	}
}
public class TabContext
{
	public string Lang;

	public List<Problem> Problems;

	public UpdateChecker Updates;

	public IEnumerable<ModRef> Packages;

	public bool Connected;

	public List<ModDifference> ServerDiff;

	public List<string> Duplicates;

	public List<KeyValuePair<string, double>> LoadTimes;

	public int PluginCount;

	public int InstalledCount;

	public Health Health;

	public List<KeyValuePair<string, int>> TopPrefabs;

	public bool InWorld;

	public int ObjectsLoaded;

	public int ObjectsKnown;

	public AdminData Admin;

	public HashSet<string> Expanded;
}
public static class Tabs
{
	private static string T(string key, TabContext c)
	{
		return Text.T(key, c.Lang);
	}

	private static bool Toggle(TabContext c, string key, string label)
	{
		bool flag = c.Expanded.Contains(key);
		if (GUILayout.Button((flag ? "[-]  " : "[+]  ") + label, Theme.Section, (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			if (flag)
			{
				c.Expanded.Remove(key);
			}
			else
			{
				c.Expanded.Add(key);
			}
			flag = !flag;
		}
		return flag;
	}

	public static void Problems(TabContext c)
	{
		//IL_0164: Unknown result type (might be due to invalid IL or missing references)
		//IL_015d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01be: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_021d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0244: Unknown result type (might be due to invalid IL or missing references)
		//IL_0296: Unknown result type (might be due to invalid IL or missing references)
		//IL_03c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_06ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_0496: Unknown result type (might be due to invalid IL or missing references)
		List<Problem> list = c.Problems.Where((Problem p) => p.Kind != "engine").ToList();
		List<Problem> list2 = c.Problems.Where((Problem p) => p.Kind == "engine").ToList();
		List<IGrouping<string, Problem>> list3 = (from p in list
			group p by (p.Mod == null) ? ((p.PatchedBy.Count <= 0) ? "" : "?") : p.Mod.Name into g
			orderby (!(g.Key == "")) ? g.Sum((Problem p) => ((ReportWriter.Kind(p) == "patch" || ReportWriter.Kind(p) == "incompatible") ? 1000000 : 0) + Math.Min(p.Count, 999999)) : (-1) descending
			select g).ToList();
		int num = list3.Count((IGrouping<string, Problem> g) => g.Key != "" && g.Key != "?");
		List<KeyValuePair<ModRef, string>> list4 = ((c.Updates == null) ? new List<KeyValuePair<ModRef, string>>() : c.Updates.Updates(from m in c.Packages
			group m by m.FullPackage into g
			select g.First()).ToList());
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		Theme.Tile(num.ToString(), T("t_brokenmods", c), (num == 0) ? Theme.Green : Theme.Red, 150f);
		Theme.Tile(list.Sum((Problem p) => p.Count).ToString("N0"), T("t_errors", c), (list.Count == 0) ? Theme.Green : Theme.Orange, 150f);
		Theme.Tile(list4.Count.ToString(), T("t_updates", c), (list4.Count == 0) ? Theme.Muted : Theme.Blue, 150f);
		Theme.Tile(c.PluginCount.ToString(), T("t_plugins", c), Theme.Text, 150f);
		GUILayout.EndHorizontal();
		if (list.Count == 0)
		{
			Theme.BannerLabel(T("noproblems", c), Theme.Green);
		}
		foreach (IGrouping<string, Problem> item in list3)
		{
			Problem problem = item.OrderBy((Problem p) => Rank(ReportWriter.Kind(p))).First();
			Theme.BeginCard(Theme.ForKind(ReportWriter.Kind(problem)));
			GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
			if (item.Key == "")
			{
				GUILayout.Label(T("unknown", c), Theme.CardTitle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
			else if (item.Key == "?")
			{
				GUILayout.Label(T("suspects", c), Theme.CardTitle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
			else
			{
				GUILayout.Label(problem.Mod.Name, Theme.CardTitle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
				GUILayout.Space(8f);
				GUILayout.Label(problem.Mod.PackageVersion ?? problem.Mod.Version, Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
				GUILayout.FlexibleSpace();
				if (c.Updates != null && c.Updates.HasUpdate(problem.Mod))
				{
					Theme.ChipLabel(T("chip_update", c) + " " + c.Updates.Latest(problem.Mod), Theme.Green);
				}
			}
			GUILayout.EndHorizontal();
			if (problem.Mod != null && problem.Mod.FullPackage != null)
			{
				GUILayout.Label(problem.Mod.FullPackage, Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
			foreach (Problem item2 in from x in item
				orderby Rank(ReportWriter.Kind(x)), x.Count descending
				select x)
			{
				string text = ReportWriter.Kind(item2);
				GUILayout.Space(4f);
				GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
				Theme.ChipLabel(T("chip_" + text, c), Theme.ForKind(text));
				string text2 = item2.Count.ToString("N0") + "×" + ((item2.PerSecond >= 5.0) ? ("  ·  ~" + Math.Round(item2.PerSecond) + "/s") : "");
				GUILayout.Label(text2, Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
				GUILayout.EndHorizontal();
				GUILayout.Label(item2.FirstLine, Theme.Mono, (GUILayoutOption[])(object)new GUILayoutOption[0]);
				if (item2.Location != null)
				{
					GUILayout.Label(T("where", c) + " " + item2.Location, Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
				}
				if (item2.PatchedBy.Count > 0)
				{
					GUILayout.Label(T("patchedby", c) + " " + string.Join(", ", item2.PatchedBy.Select((ModRef m) => m.Display).ToArray()), Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
				}
				if (item2.LikelyFromOtherFailure)
				{
					GUILayout.Label(T("likely", c), Theme.Hint, (GUILayoutOption[])(object)new GUILayoutOption[0]);
				}
				GUILayout.Label(T("hint_" + text, c), Theme.Hint, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
			Theme.EndCard();
		}
		HashSet<string> withProblems = new HashSet<string>(list3.Select((IGrouping<string, Problem> g) => g.Key));
		List<KeyValuePair<ModRef, string>> list5 = list4.Where((KeyValuePair<ModRef, string> u) => !withProblems.Contains(u.Key.Name)).ToList();
		if (list5.Count > 0)
		{
			GUILayout.Label(T("updates", c), Theme.Section, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			foreach (KeyValuePair<ModRef, string> item3 in list5)
			{
				GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
				Theme.ChipLabel(item3.Value, Theme.Blue);
				GUILayout.Label(item3.Key.Name + "  <color=#D2C6B2>" + (item3.Key.PackageVersion ?? item3.Key.Version) + " -> " + item3.Value + "</color>", Theme.Body, (GUILayoutOption[])(object)new GUILayoutOption[0]);
				GUILayout.EndHorizontal();
			}
		}
		if (list2.Count <= 0 || !Toggle(c, "engine", T("engine", c) + "  (" + list2.Count + ")"))
		{
			return;
		}
		foreach (Problem item4 in list2.OrderByDescending((Problem x) => x.Count))
		{
			GUILayout.Label("•  " + item4.FirstLine + "  <color=#D2C6B2>" + item4.Count + "×</color>", Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
	}

	private static int Rank(string kind)
	{
		return kind switch
		{
			"patch" => 0, 
			"incompatible" => 1, 
			"repeating" => 2, 
			"gamerepeating" => 3, 
			"error" => 4, 
			_ => 5, 
		};
	}

	public static void Mods(TabContext c)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_012c: Unknown result type (might be due to invalid IL or missing references)
		//IL_04c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_016c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0155: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.Label(T("tab_server_diff", c), Theme.Section, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (!c.Connected)
		{
			Theme.BannerLabel(T("notconnected", c), Theme.Slate);
		}
		else if (c.ServerDiff == null)
		{
			Theme.BannerLabel(T("noserverreply", c), Theme.Slate);
		}
		else
		{
			List<ModDifference> list = c.ServerDiff.Where((ModDifference d) => d.Kind != "extra").ToList();
			if (list.Count == 0)
			{
				Theme.BannerLabel(T("diff_ok", c), Theme.Green);
			}
			else
			{
				Theme.BannerLabel(T((list.Count == 1) ? "diff_count1" : "diff_count", c).Replace("{n}", list.Count.ToString()), Theme.Orange);
			}
			foreach (ModDifference item in c.ServerDiff)
			{
				GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
				if (item.Kind == "missing")
				{
					Theme.ChipLabel(T("chip_missing", c), Theme.Red);
				}
				else if (item.Kind == "version")
				{
					Theme.ChipLabel(T("chip_version", c), Theme.Orange);
				}
				else
				{
					Theme.ChipLabel(T("chip_extra", c), Theme.Slate);
				}
				string text = ((item.Kind == "missing") ? (T("server", c) + " " + item.ServerVersion) : ((item.Kind == "version") ? (T("yours", c) + " " + item.PlayerVersion + "  ·  " + T("server", c) + " " + item.ServerVersion) : item.PlayerVersion));
				GUILayout.Label(item.Id + "   <color=#D2C6B2>" + text + "</color>", Theme.Body, (GUILayoutOption[])(object)new GUILayoutOption[0]);
				GUILayout.EndHorizontal();
			}
			if (c.ServerDiff.Any((ModDifference d) => d.Kind == "extra"))
			{
				GUILayout.Label(T("extra_hint", c), Theme.Hint, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
		}
		if (c.Duplicates.Count > 0)
		{
			GUILayout.Label(T("duplicates", c), Theme.Section, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			Theme.BeginCard(Theme.Red);
			foreach (string duplicate in c.Duplicates)
			{
				GUILayout.Label(duplicate, Theme.Body, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
			GUILayout.Label(T("duplicates_hint", c), Theme.Hint, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			Theme.EndCard();
		}
		GUILayout.Label(T("loadtimes", c), Theme.Section, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (c.LoadTimes.Count == 0)
		{
			GUILayout.Label(T("loadtimes_none", c), Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		else
		{
			double num = c.LoadTimes.Sum((KeyValuePair<string, double> t) => t.Value);
			GUILayout.Label(T("loadtimes_total", c).Replace("{n}", c.LoadTimes.Count.ToString()).Replace("{s}", num.ToString("0.0")), Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			List<KeyValuePair<string, double>> list2 = c.LoadTimes.OrderByDescending((KeyValuePair<string, double> t) => t.Value).Take(15).ToList();
			double num2 = Math.Max(0.1, list2[0].Value);
			foreach (KeyValuePair<string, double> item2 in list2)
			{
				GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
				GUILayout.Label(item2.Key, Theme.Body, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(250f) });
				Theme.Bar((float)(item2.Value / num2), Theme.Grade(item2.Value, 2.0, 6.0, higherIsBetter: false), 300f, 12f);
				GUILayout.Label(item2.Value.ToString("0.0") + " s", Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) });
				GUILayout.EndHorizontal();
			}
			int num3 = c.PluginCount - c.LoadTimes.Count;
			if (num3 > 0)
			{
				GUILayout.Label(T("loadtimes_before", c).Replace("{n}", num3.ToString()), Theme.Hint, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
		}
		GUILayout.Space(6f);
		GUILayout.Label(T("installed", c).Replace("{n}", c.InstalledCount.ToString()), Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
	}

	public static void Performance(TabContext c)
	{
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_010f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0144: Unknown result type (might be due to invalid IL or missing references)
		//IL_0168: 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_024a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0274: Unknown result type (might be due to invalid IL or missing references)
		//IL_03f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0437: Unknown result type (might be due to invalid IL or missing references)
		//IL_0461: Unknown result type (might be due to invalid IL or missing references)
		//IL_030c: Unknown result type (might be due to invalid IL or missing references)
		Health health = c.Health;
		GUILayout.Label(T("perf_game", c), Theme.Section, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (health.SampleCount < 30)
		{
			Theme.BannerLabel(T("measuring", c), Theme.Slate);
		}
		else
		{
			double value = health.AverageFps();
			double value2 = health.LowFps();
			double value3 = health.WorstFrameMs();
			GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
			Theme.Tile(value.ToString("0"), T("t_fps", c), Theme.Grade(value, 50.0, 30.0, higherIsBetter: true), 150f);
			Theme.Tile(value2.ToString("0"), T("t_fpslow", c), Theme.Grade(value2, 40.0, 20.0, higherIsBetter: true), 150f);
			Theme.Tile(value3.ToString("0") + " ms", T("t_worst", c), Theme.Grade(value3, 50.0, 120.0, higherIsBetter: false), 150f);
			GUILayout.EndHorizontal();
		}
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		Theme.Tile(Health.Mb(GC.GetTotalMemory(forceFullCollection: false)), T("t_memmods", c), Theme.Text, 150f);
		Theme.Tile(Health.Mb(Profiler.GetTotalAllocatedMemoryLong()), T("t_memunity", c), Theme.Text, 150f);
		Theme.Tile(health.GcPerMinute()[0].ToString("0.0"), T("t_gc", c), Theme.Text, 150f);
		GUILayout.EndHorizontal();
		GUILayout.Label(T("gc_hint", c), Theme.Hint, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label(T("objects", c), Theme.Section, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (!c.InWorld)
		{
			GUILayout.Label(T("notinworld", c), Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		else
		{
			GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
			Theme.Tile(c.ObjectsLoaded.ToString("N0"), T("t_loaded", c), Theme.Grade(c.ObjectsLoaded, 6000.0, 12000.0, higherIsBetter: false), 140f);
			Theme.Tile(c.ObjectsKnown.ToString("N0"), T("t_known", c), Theme.Text, 140f);
			GUILayout.EndHorizontal();
			if (c.TopPrefabs.Count > 0)
			{
				double num = c.TopPrefabs[0].Value;
				foreach (KeyValuePair<string, int> topPrefab in c.TopPrefabs)
				{
					GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
					GUILayout.Label(topPrefab.Key, Theme.Body, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(250f) });
					Theme.Bar((float)((double)topPrefab.Value / num), Theme.Gold, 300f, 12f);
					GUILayout.Label(topPrefab.Value.ToString("N0"), Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) });
					GUILayout.EndHorizontal();
				}
			}
			GUILayout.Label(T("objects_hint", c), Theme.Hint, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		GUILayout.Label("Mod Doctor", Theme.Section, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		Theme.Tile(health.OwnMsPerFrame().ToString("0.000") + " ms", T("t_own", c), Theme.Grade(health.OwnMsPerFrame(), 0.05, 0.3, higherIsBetter: false), 140f);
		Theme.Tile((health.OwnMsTotal() / 1000.0).ToString("0.0") + " s", T("t_owntotal", c), Theme.Text, 140f);
		Theme.Tile(health.ErrorsSeen.ToString("N0"), T("t_ownerrors", c), Theme.Text, 140f);
		GUILayout.EndHorizontal();
	}

	public static void Server(TabContext c)
	{
		//IL_0015: 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_00b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_012f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0154: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_05bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_05c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0343: Unknown result type (might be due to invalid IL or missing references)
		//IL_033c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0405: Unknown result type (might be due to invalid IL or missing references)
		//IL_03fe: Unknown result type (might be due to invalid IL or missing references)
		AdminData admin = c.Admin;
		if (admin == null)
		{
			Theme.BannerLabel(T("loading", c), Theme.Slate);
			return;
		}
		if (admin.NotAdmin)
		{
			Theme.BannerLabel(T("notadmin", c), Theme.Red);
			return;
		}
		GUILayout.Label(T("srv_health", c), Theme.Section, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		Theme.Tile(admin.Uptime, T("uptime", c), Theme.Text, 120f);
		Theme.Tile(admin.Fps.ToString("0"), T("t_fps", c), Theme.Grade(admin.Fps, 25.0, 15.0, higherIsBetter: true), 120f);
		Theme.Tile(admin.FpsLow.ToString("0"), T("t_fpslow", c), Theme.Grade(admin.FpsLow, 20.0, 10.0, higherIsBetter: true), 120f);
		Theme.Tile(admin.MemoryMb.ToString("0") + " MB", T("t_memmods", c), Theme.Text, 120f);
		Theme.Tile(admin.Online.ToString(), T("t_online", c), Theme.Text, 120f);
		GUILayout.EndHorizontal();
		GUILayout.Label(T("objects_known", c) + " " + admin.Objects.ToString("N0"), Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label(T("srv_players", c), Theme.Section, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		List<AdminData.AdminPlayer> list = (from p in admin.Players
			where p.Name != "(server)"
			orderby p.Online descending, p.Name
			select p).ToList();
		if (list.Count == 0)
		{
			GUILayout.Label(T("noplayers", c), Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		foreach (AdminData.AdminPlayer item in list)
		{
			List<ModDifference> list2 = item.Diffs.Where((ModDifference d) => d.Kind != "extra").ToList();
			Theme.BeginCard((list2.Count > 0 || item.Problems.Any((string[] e) => e.Length > 2 && (e[2] == "patch" || e[2] == "incompatible" || e[2] == "repeating"))) ? Theme.Orange : Theme.Green);
			GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label(item.Name, Theme.CardTitle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
			GUILayout.Space(10f);
			Theme.ChipLabel(item.Online ? T("online", c) : T("offline", c), item.Online ? Theme.Green : Theme.Slate);
			GUILayout.FlexibleSpace();
			GUILayout.EndHorizontal();
			if (!item.HasModList)
			{
				GUILayout.Label(T("nomoddoctor", c), Theme.Hint, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
			else if (list2.Count == 0)
			{
				GUILayout.Label(T("diff_ok", c), Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
			foreach (ModDifference item2 in list2)
			{
				GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
				Theme.ChipLabel((item2.Kind == "missing") ? T("chip_missing", c) : T("chip_version", c), (item2.Kind == "missing") ? Theme.Red : Theme.Orange);
				GUILayout.Label(item2.Id + "   <color=#D2C6B2>" + ((item2.Kind == "missing") ? (T("server", c) + " " + item2.ServerVersion) : (item2.PlayerVersion + " · " + T("server", c) + " " + item2.ServerVersion)) + "</color>", Theme.Body, (GUILayoutOption[])(object)new GUILayoutOption[0]);
				GUILayout.EndHorizontal();
			}
			foreach (string[] item3 in item.Problems.Take(6))
			{
				ProblemLine(c, item3);
			}
			if (item.HasModList && item.Problems.Count == 0)
			{
				GUILayout.Label(T("noproblems", c), Theme.Small, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
			Theme.EndCard();
		}
		AdminData.AdminPlayer adminPlayer = admin.Players.FirstOrDefault((AdminData.AdminPlayer p) => p.Name == "(server)");
		GUILayout.Label(T("srv_own", c), Theme.Section, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (adminPlayer == null || adminPlayer.Problems.Count == 0)
		{
			Theme.BannerLabel(T("noproblems", c), Theme.Green);
			return;
		}
		Theme.BeginCard(Theme.Orange);
		foreach (string[] item4 in adminPlayer.Problems.Take(8))
		{
			ProblemLine(c, item4);
		}
		Theme.EndCard();
	}

	private static void ProblemLine(TabContext c, string[] e)
	{
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		if (e.Length >= 5)
		{
			GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
			Theme.ChipLabel(T("chip_" + e[2], c), Theme.ForKind(e[2]));
			GUILayout.Label(((e[0].Length > 0) ? (e[0] + " " + e[1]) : T("gamecode", c)) + "   <color=#D2C6B2>" + e[3] + "×</color>", Theme.Body, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.EndHorizontal();
			GUILayout.Label(e[4], Theme.Mono, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
	}
}
public class ModRef
{
	public string Guid;

	public string Name;

	public string Version;

	public string Folder;

	public string PackageNamespace;

	public string PackageName;

	public string PackageVersion;

	public string FullPackage
	{
		get
		{
			if (PackageNamespace != null)
			{
				return PackageNamespace + "-" + PackageName;
			}
			return null;
		}
	}

	public string Display => Name + " " + (PackageVersion ?? Version);
}
public class Problem
{
	public string Key;

	public string Kind;

	public string ExceptionType;

	public string FirstLine;

	public string Location;

	public ModRef Mod;

	public List<ModRef> PatchedBy = new List<ModRef>();

	public string LogSource;

	public int Count;

	public DateTime First;

	public DateTime Last;

	public string RawStack;

	public bool Resolved;

	public bool LikelyFromOtherFailure;

	public double PerSecond
	{
		get
		{
			double totalSeconds = (Last - First).TotalSeconds;
			if (!(totalSeconds < 1.0))
			{
				return (double)Count / totalSeconds;
			}
			return 0.0;
		}
	}
}
public class Diagnosis
{
	private readonly object sync = new object();

	private readonly Dictionary<string, Problem> problems = new Dictionary<string, Problem>();

	private readonly Queue<Problem> pending = new Queue<Problem>();

	private readonly Dictionary<Assembly, ModRef> assemblyOwner = new Dictionary<Assembly, ModRef>();

	private readonly Dictionary<string, ModRef> typeOwner = new Dictionary<string, ModRef>();

	private readonly Dictionary<string, ModRef> byGuid = new Dictionary<string, ModRef>();

	private readonly Dictionary<string, ModRef> byLogName = new Dictionary<string, ModRef>();

	private readonly Dictionary<string, ModRef> harmonyOwner = new Dictionary<string, ModRef>();

	private bool indexed;

	private int version;

	public int PluginCount;

	private static readonly Regex DmdFrame = new Regex("DMD<([^:>]+)::([^>]+)>");

	private static readonly Regex ManagedFrame = new Regex("^\\s*(?:at\\s+)?(?:\\(wrapper [^)]*\\)\\s*)?([A-Za-z_<][\\w\\.\\+/<>`\\[\\],]*)\\s*\\(");

	private static readonly string[] IncompatibleMarkers = new string[9] { "MissingMethodException", "MissingFieldException", "TypeLoadException", "BadImageFormatException", "MissingMemberException", "EntryPointNotFoundException", "Undefined target method", "Could not find method", "expected reference type" };

	private static readonly Regex Numbers = new Regex("\\d+");

	public int Version
	{
		get
		{
			lock (sync)
			{
				return version;
			}
		}
	}

	public void Record(string condition, string stackTrace, string logSource)
	{
		if (string.IsNullOrEmpty(condition) || condition.StartsWith("[Mod Doctor]"))
		{
			return;
		}
		string text = FirstLine(condition);
		string text2 = (string.IsNullOrEmpty(stackTrace) ? ExtractInlineStack(condition) : stackTrace);
		string key = Normalize(text) + "|" + TopFrames(text2, 3) + "|" + logSource;
		lock (sync)
		{
			if (problems.TryGetValue(key, out var value))
			{
				value.Count++;
				value.Last = DateTime.Now;
				if (value.Count == 100 || value.Count == 1000)
				{
					version++;
				}
				return;
			}
			value = new Problem();
			value.Key = key;
			value.FirstLine = ((text.Length > 300) ? text.Substring(0, 300) : text);
			value.ExceptionType = ExceptionType(text);
			value.RawStack = text2;
			value.LogSource = logSource;
			value.Count = 1;
			value.First = (value.Last = DateTime.Now);
			problems[key] = value;
			pending.Enqueue(value);
			version++;
		}
	}

	public void ResolvePending()
	{
		if (!indexed)
		{
			return;
		}
		while (true)
		{
			Problem problem;
			lock (sync)
			{
				if (pending.Count == 0)
				{
					break;
				}
				problem = pending.Dequeue();
			}
			try
			{
				Resolve(problem);
			}
			catch (Exception ex)
			{
				ModDoctorPlugin.Log.LogDebug((object)("Could not attribute an error: " + ex.Message));
			}
			lock (sync)
			{
				problem.Resolved = true;
				version++;
			}
		}
	}

	public List<Problem> Snapshot()
	{
		lock (sync)
		{
			return problems.Values.Where((Problem p) => p.Resolved).ToList();
		}
	}

	public IEnumerable<ModRef> Packages()
	{
		return byGuid.Values.Where((ModRef m) => m.PackageNamespace != null);
	}

	public void IndexPlugins()
	{
		string fullPath = Path.GetFullPath(Paths.PluginPath);
		Dictionary<string, ModRef> dictionary = new Dictionary<string, ModRef>(StringComparer.OrdinalIgnoreCase);
		foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
		{
			PluginInfo value = pluginInfo.Value;
			ModRef modRef = new ModRef();
			modRef.Guid = value.Metadata.GUID;
			modRef.Name = value.Metadata.Name;
			modRef.Version = value.Metadata.Version.ToString();
			modRef.Folder = TopFolder(fullPath, value.Location);
			ReadPackage(fullPath, modRef);
			byGuid[modRef.Guid] = modRef;
			byLogName[modRef.Name] = modRef;
			harmonyOwner[modRef.Guid] = modRef;
			if (modRef.Folder != null && !dictionary.ContainsKey(modRef.Folder))
			{
				dictionary[modRef.Folder] = modRef;
			}
			if ((Object)(object)value.Instance != (Object)null)
			{
				assemblyOwner[((object)value.Instance).GetType().Assembly] = modRef;
			}
		}
		PluginCount = byGuid.Count;
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly in assemblies)
		{
			if (!assemblyOwner.ContainsKey(assembly) && !assembly.IsDynamic)
			{
				string location;
				try
				{
					location = assembly.Location;
				}
				catch (Exception)
				{
					continue;
				}
				string text = TopFolder(fullPath, location);
				if (text != null && dictionary.TryGetValue(text, out var value2))
				{
					assemblyOwner[assembly] = value2;
				}
			}
		}
		foreach (KeyValuePair<Assembly, ModRef> item in assemblyOwner)
		{
			Type[] types;
			try
			{
				types = item.Key.GetTypes();
			}
			catch (ReflectionTypeLoadException ex2)
			{
				types = ex2.Types;
			}
			catch (Exception)
			{
				continue;
			}
			Type[] array = types;
			foreach (Type type in array)
			{
				if (!(type == null) && type.FullName != null)
				{
					typeOwner[type.FullName.Replace('/', '+')] = item.Value;
				}
			}
		}
		indexed = true;
	}

	private static string TopFolder(string pluginRoot, string location)
	{
		if (string.IsNullOrEmpty(location))
		{
			return null;
		}
		string fullPath = Path.GetFullPath(location);
		if (!fullPath.StartsWith(pluginRoot, StringComparison.OrdinalIgnoreCase))
		{
			return null;
		}
		string text = fullPath.Substring(pluginRoot.Length).TrimStart('\\', '/');
		int num = text.IndexOfAny(new char[2] { '\\', '/' });
		if (num >= 0)
		{
			return text.Substring(0, num);
		}
		return null;
	}

	private static void ReadPackage(string pluginRoot, ModRef mod)
	{
		if (mod.Folder == null)
		{
			return;
		}
		int num = mod.Folder.IndexOf('-');
		if (num <= 0 || num == mod.Folder.Length - 1)
		{
			return;
		}
		mod.PackageNamespace = mod.Folder.Substring(0, num);
		mod.PackageName = mod.Folder.Substring(num + 1);
		string path = Path.Combine(Path.Combine(pluginRoot, mod.Folder), "manifest.json");
		if (File.Exists(path))
		{
			Match match = Regex.Match(File.ReadAllText(path), "\"version_number\"\\s*:\\s*\"([^\"]+)\"");
			if (match.Success)
			{
				mod.PackageVersion = match.Groups[1].Value;
			}
		}
	}

	private void Resolve(Problem p)
	{
		if (p.LogSource != null && byLogName.TryGetValue(p.LogSource, out var value))
		{
			p.Mod = value;
		}
		string[] array = (p.RawStack ?? "").Split('\n');
		foreach (string text in array)
		{
			string text2 = text.Trim();
			if (text2.Length == 0)
			{
				continue;
			}
			Match match = DmdFrame.Match(text2);
			if (match.Success)
			{
				string text3 = match.Groups[1].Value.Replace('/', '+');
				string value2 = match.Groups[2].Value;
				if (p.Location == null)
				{
					p.Location = text3 + "." + value2;
				}
				if (typeOwner.TryGetValue(text3, out var value3))
				{
					if (p.Mod == null)
					{
						p.Mod = value3;
					}
					break;
				}
				foreach (ModRef item in PatchersOf(text3, value2))
				{
					if (!p.PatchedBy.Contains(item))
					{
						p.PatchedBy.Add(item);
					}
				}
				if (p.PatchedBy.Count > 0)
				{
					break;
				}
				continue;
			}
			Match match2 = ManagedFrame.Match(text2);
			if (!match2.Success)
			{
				continue;
			}
			string text4 = match2.Groups[1].Value.Replace('/', '+');
			int num = text4.LastIndexOf('.');
			if (num <= 0)
			{
				continue;
			}
			string text5 = text4.Substring(0, num);
			if (p.Location == null && !IsFramework(text5))
			{
				p.Location = text4;
			}
			if (typeOwner.TryGetValue(text5, out var value4))
			{
				if (p.Mod == null)
				{
					p.Mod = value4;
				}
				p.Location = text4;
				break;
			}
		}
		p.Kind = Classify(p);
		if (p.Mod == null && p.PatchedBy.Count == 1)
		{
			p.Mod = p.PatchedBy[0];
		}
		if (p.Mod != null || p.PatchedBy.Count <= 1)
		{
			return;
		}
		List<ModRef> list;
		lock (sync)
		{
			list = p.PatchedBy.Where((ModRef m) => problems.Values.Any((Problem o) => o != p && o.Resolved && o.Mod == m && (o.Kind == "patch" || o.Kind == "incompatible"))).ToList();
		}
		if (list.Count == 1)
		{
			p.Mod = list[0];
			p.LikelyFromOtherFailure = true;
		}
	}

	private IEnumerable<ModRef> PatchersOf(string typeName, string methodName)
	{
		List<ModRef> list = new List<ModRef>();
		Type type = AccessTools.TypeByName(typeName);
		if (type == null)
		{
			return list;
		}
		IEnumerable<MethodBase> enumerable = ((methodName == ".ctor") ? type.GetConstructors(AccessTools.all).Cast<MethodBase>() : (from m in type.GetMethods(AccessTools.all)
			where m.Name == methodName
			select m).Cast<MethodBase>());
		foreach (MethodBase item in enumerable)
		{
			Patches patchInfo = Harmony.GetPatchInfo(item);
			if (patchInfo == null)
			{
				continue;
			}
			IEnumerable<Patch> enumerable2 = patchInfo.Prefixes.Concat(patchInfo.Postfixes).Concat(patchInfo.Transpilers).Concat(patchInfo.Finalizers);
			foreach (Patch item2 in enumerable2)
			{
				ModRef value = null;
				MethodInfo patchMethod = item2.PatchMethod;
				if (patchMethod != null && patchMethod.DeclaringType != null)
				{
					assemblyOwner.TryGetValue(patchMethod.DeclaringType.Assembly, out value);
				}
				if (value == n