Decompiled source of Moderation Improvements v1.0.1

BepInEx/plugins/ModerationImprovements.dll

Decompiled a day ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Epic.OnlineServices;
using Epic.OnlineServices.Connect;
using HarmonyLib;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem.Collections.Generic;
using Microsoft.CodeAnalysis;
using Mirror;
using Mirror.Authenticators;
using ModSettingsMenu.Api;
using PlayEveryWare.EpicOnlineServices;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("AdamMady")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ModerationImprovements")]
[assembly: AssemblyTitle("ModerationImprovements")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BigOrb
{
	internal static class Chime
	{
		private static bool _broken;

		private static AudioSource _source;

		private static AudioClip _join;

		private static AudioClip _leave;

		private static readonly Queue<bool> _pending = new Queue<bool>();

		private static float _nextPlay;

		private const int Rate = 44100;

		private const float NoteLen = 0.32f;

		private const float NoteFade = 0.03f;

		private const float NoteGap = 0.115f;

		private const float Spacing = 0.45f;

		private const int MaxQueued = 3;

		internal static bool Enabled
		{
			get
			{
				if (!_broken)
				{
					return Plugin.ChimeEnabled.Value;
				}
				return false;
			}
		}

		internal static void SetEnabled(bool on)
		{
			Plugin.ChimeEnabled.Value = on;
			if (on)
			{
				_broken = false;
			}
		}

		internal static void Play(bool join)
		{
			if (Enabled && _pending.Count < 3)
			{
				_pending.Enqueue(join);
			}
		}

		internal static void Step(GameObject holder)
		{
			if (_pending.Count == 0 || Time.unscaledTime < _nextPlay)
			{
				return;
			}
			_nextPlay = Time.unscaledTime + 0.45f;
			bool flag = _pending.Dequeue();
			try
			{
				if (Ensure(holder))
				{
					AudioClip val = (flag ? _join : _leave);
					if ((Object)(object)val != (Object)null)
					{
						_source.PlayOneShot(val, Mathf.Clamp01(Plugin.ChimeVolume.Value));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("chime: " + ex.Message));
				_pending.Clear();
				_broken = true;
			}
		}

		private static bool Ensure(GameObject holder)
		{
			if ((Object)(object)_source != (Object)null && (Object)(object)_join != (Object)null && (Object)(object)_leave != (Object)null)
			{
				return true;
			}
			if ((Object)(object)holder == (Object)null)
			{
				return false;
			}
			if ((Object)(object)_source == (Object)null)
			{
				_source = holder.GetComponent<AudioSource>();
				if ((Object)(object)_source == (Object)null)
				{
					_source = holder.AddComponent<AudioSource>();
				}
				_source.playOnAwake = false;
				_source.spatialBlend = 0f;
				_source.bypassEffects = true;
				_source.bypassListenerEffects = true;
				_source.bypassReverbZones = true;
				_source.ignoreListenerPause = true;
				_source.ignoreListenerVolume = true;
			}
			if (_join == null)
			{
				_join = Build("orbJoin", 659.25f, 880f);
			}
			if (_leave == null)
			{
				_leave = Build("orbLeave", 880f, 587.33f);
			}
			if ((Object)(object)_source != (Object)null && (Object)(object)_join != (Object)null)
			{
				return (Object)(object)_leave != (Object)null;
			}
			return false;
		}

		private static AudioClip Build(string name, float f1, float f2)
		{
			int num = 19183;
			Il2CppStructArray<float> val = new Il2CppStructArray<float>((long)num);
			for (int i = 0; i < num; i++)
			{
				float num2 = (float)i / 44100f;
				((Il2CppArrayBase<float>)(object)val)[i] = Mathf.Clamp(0.5f * (Note(num2, f1) + Note(num2 - 0.115f, f2)), -1f, 1f);
			}
			AudioClip obj = AudioClip.Create(name, num, 1, 44100, false);
			obj.SetData(val, 0);
			return obj;
		}

		private static float Note(float t, float freq)
		{
			if (t < 0f || t > 0.32f)
			{
				return 0f;
			}
			float num = Mathf.Min(1f, t / 0.006f) * Mathf.Exp((0f - t) * 14f);
			if (t > 0.29f)
			{
				num *= (0.32f - t) / 0.03f;
			}
			return num * (Mathf.Sin((float)Math.PI * 2f * freq * t) + 0.35f * Mathf.Sin((float)Math.PI * 4f * freq * t));
		}
	}
	internal static class Guard
	{
		[HarmonyPatch(typeof(HouseAuthenticator), "OnInitialAuthRequestMessage")]
		internal static class AuthPatch
		{
			private static void Postfix(NetworkConnectionToClient __0, InitialialAuthRequestMessage __1)
			{
				try
				{
					Patches.MarkFired("auth");
					if (__0 == null)
					{
						return;
					}
					string text = null;
					string text2 = null;
					string text3 = null;
					try
					{
						text = __0.address;
					}
					catch
					{
					}
					try
					{
						text2 = ((__1 != null) ? __1.playerIdentifier : null);
					}
					catch
					{
					}
					try
					{
						text3 = ((__1 != null) ? __1.versionNumber : null);
					}
					catch
					{
					}
					OrbState.AuthSeen(((NetworkConnection)__0).connectionId, text, text2, text3);
					string name = OrbState.RosterName(text2) ?? text2 ?? "?";
					OrbState.AddEvent("auth", text2, name, $"conn {((NetworkConnection)__0).connectionId} addr {text ?? "?"} claims {text2 ?? "?"} v{text3 ?? "?"}");
					if (OrbState.IsBannedAddress(text))
					{
						OrbState.AddEvent("autokick", text2, name, "banned address " + text + " tried to join as " + text2);
						((NetworkConnection)__0).Disconnect();
					}
					else if (OrbState.IsBanned(text2))
					{
						OrbState.AddEvent("autokick", text2, name, "banned identifier tried to join from " + (text ?? "?"));
						OrbState.BanAttachAddress(text2, text);
						((NetworkConnection)__0).Disconnect();
					}
					else if (!OrbState.LobbyAllows(text2, text))
					{
						OrbState.AddEvent("lockreject", text2, name, "lobby locked; connection " + (text ?? "?") + " was not present when locked");
						((NetworkConnection)__0).Disconnect();
					}
					else
					{
						if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2) || text == "localhost")
						{
							return;
						}
						if (IsSteam(text) && IsSteam(text2) && text != text2)
						{
							Act("idspoof", text2, name, "claims SteamID " + text2 + " but the connection is " + text, text, __0);
							return;
						}
						string item = Base(text2);
						lock (IdsByAddress)
						{
							if (!IdsByAddress.TryGetValue(text, out var value))
							{
								value = (IdsByAddress[text] = new HashSet<string>());
							}
							value.Add(item);
							if (value.Count >= 2)
							{
								string detail = $"address {text} has claimed {value.Count} identifiers: {string.Join(", ", value)}";
								if (value.Count >= 3)
								{
									Act("idrotate", text2, name, detail, text, __0);
									return;
								}
								OrbState.AddAlert("idrotate", text2, name, detail);
							}
						}
						Eos.Lookup(text, text2);
					}
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogError((object)("auth gate: " + ex.Message));
				}
			}
		}

		internal static class Eos
		{
			private struct QueryOptions
			{
				public int ApiVersion;

				public IntPtr LocalUserId;

				public IntPtr AccountIdTypeDeprecated;

				public IntPtr ProductUserIds;

				public uint Count;
			}

			private struct QueryInfo
			{
				public int ResultCode;

				public IntPtr ClientData;

				public IntPtr LocalUserId;
			}

			private struct CountOptions
			{
				public int ApiVersion;

				public IntPtr TargetUserId;
			}

			private struct CopyOptions
			{
				public int ApiVersion;

				public IntPtr TargetUserId;

				public uint Index;
			}

			private struct AccountInfo
			{
				public int ApiVersion;

				public IntPtr ProductUserId;

				public IntPtr DisplayName;

				public IntPtr AccountId;

				public int AccountIdType;

				public long LastLoginTime;
			}

			[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			private delegate void OnQuery(IntPtr info);

			internal class Mapping
			{
				public string Type;

				public string AccountId;

				public string DisplayName;

				public string When;

				public List<string> All = new List<string>();
			}

			private const string Dll = "EOSSDK-Win64-Shipping";

			private static OnQuery _cb;

			private static readonly string[] Types = new string[15]
			{
				"epic", "steam", "psn", "xbl", "discord", "gog", "nintendo", "uplay", "openid", "apple",
				"google", "oculus", "itchio", "amazon", "viveport"
			};

			internal static readonly Dictionary<string, Mapping> Map = new Dictionary<string, Mapping>(StringComparer.OrdinalIgnoreCase);

			private static readonly Dictionary<string, string> ClaimedBy = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

			private static readonly HashSet<string> Pending = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

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

			private static long _seq;

			private static string File => Path.Combine(OrbState.DataDir, "eosmap.tsv");

			[DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)]
			private static extern IntPtr EOS_ProductUserId_FromString(string s);

			[DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)]
			private static extern void EOS_Connect_QueryProductUserIdMappings(IntPtr h, ref QueryOptions o, IntPtr clientData, OnQuery cb);

			[DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)]
			private static extern uint EOS_Connect_GetProductUserExternalAccountCount(IntPtr h, ref CountOptions o);

			[DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)]
			private static extern int EOS_Connect_CopyProductUserExternalAccountByIndex(IntPtr h, ref CopyOptions o, out IntPtr info);

			[DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)]
			private static extern void EOS_Connect_ExternalAccountInfo_Release(IntPtr info);

			internal static void Load()
			{
				try
				{
					if (!System.IO.File.Exists(File))
					{
						return;
					}
					string[] array = System.IO.File.ReadAllLines(File);
					for (int i = 0; i < array.Length; i++)
					{
						string[] array2 = array[i].Split('\t');
						if (array2.Length >= 5)
						{
							Mapping mapping = new Mapping
							{
								Type = array2[1],
								AccountId = array2[2],
								DisplayName = array2[3],
								When = array2[4]
							};
							if (array2.Length > 5)
							{
								mapping.All.AddRange(array2[5].Split('|'));
							}
							Map[array2[0]] = mapping;
						}
					}
				}
				catch
				{
				}
			}

			internal static void Lookup(string addr, string claimed)
			{
				if (!IsEosAddr(addr))
				{
					return;
				}
				ClaimedBy[addr] = claimed;
				if (Map.TryGetValue(addr, out var value))
				{
					Compare(addr, value, claimed);
				}
				else
				{
					if (Pending.Contains(addr))
					{
						return;
					}
					try
					{
						EOSSingleton instance = EOSManager.Instance;
						ConnectInterface val = ((instance != null) ? instance.GetEOSConnectInterface() : null);
						ProductUserId val2 = ((instance != null) ? instance.GetProductUserId() : null);
						if ((Handle)(object)val == (Handle)null || (Handle)(object)val2 == (Handle)null)
						{
							return;
						}
						IntPtr intPtr = EOS_ProductUserId_FromString(addr);
						if (!(intPtr == IntPtr.Zero))
						{
							IntPtr intPtr2 = Marshal.AllocHGlobal(IntPtr.Size);
							Marshal.WriteIntPtr(intPtr2, intPtr);
							long num = ++_seq;
							lock (Inflight)
							{
								Inflight[num] = addr;
							}
							if (_cb == null)
							{
								_cb = OnQueried;
							}
							QueryOptions o = new QueryOptions
							{
								ApiVersion = 2,
								LocalUserId = ((Handle)val2).InnerHandle,
								ProductUserIds = intPtr2,
								Count = 1u
							};
							Pending.Add(addr);
							EOS_Connect_QueryProductUserIdMappings(((Handle)val).InnerHandle, ref o, (IntPtr)num, _cb);
						}
					}
					catch (Exception ex)
					{
						Plugin.Logger.LogError((object)("eos lookup: " + ex.Message));
						Pending.Remove(addr);
					}
				}
			}

			private static void OnQueried(IntPtr infoPtr)
			{
				try
				{
					QueryInfo queryInfo = Marshal.PtrToStructure<QueryInfo>(infoPtr);
					string value;
					lock (Inflight)
					{
						Inflight.TryGetValue((long)queryInfo.ClientData, out value);
						Inflight.Remove((long)queryInfo.ClientData);
					}
					if (value != null)
					{
						Pending.Remove(value);
						if (queryInfo.ResultCode != 0)
						{
							OrbState.AddEvent("eos", null, "host", $"lookup {value} → result {queryInfo.ResultCode}");
						}
						else
						{
							Read(value);
						}
					}
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogError((object)("eos callback: " + ex.Message));
				}
			}

			private static void Read(string addr)
			{
				EOSSingleton instance = EOSManager.Instance;
				ConnectInterface val = ((instance != null) ? instance.GetEOSConnectInterface() : null);
				if ((Handle)(object)val == (Handle)null)
				{
					return;
				}
				IntPtr targetUserId = EOS_ProductUserId_FromString(addr);
				CountOptions o = new CountOptions
				{
					ApiVersion = 1,
					TargetUserId = targetUserId
				};
				uint num = EOS_Connect_GetProductUserExternalAccountCount(((Handle)val).InnerHandle, ref o);
				Mapping mapping = new Mapping
				{
					When = OrbState.Now()
				};
				for (uint num2 = 0u; num2 < num; num2++)
				{
					CopyOptions o2 = new CopyOptions
					{
						ApiVersion = 1,
						TargetUserId = targetUserId,
						Index = num2
					};
					if (EOS_Connect_CopyProductUserExternalAccountByIndex(((Handle)val).InnerHandle, ref o2, out var info) != 0 || info == IntPtr.Zero)
					{
						continue;
					}
					try
					{
						AccountInfo accountInfo = Marshal.PtrToStructure<AccountInfo>(info);
						string text = ((accountInfo.AccountIdType >= 0 && accountInfo.AccountIdType < Types.Length) ? Types[accountInfo.AccountIdType] : ("type" + accountInfo.AccountIdType));
						string text2 = ((accountInfo.AccountId != IntPtr.Zero) ? Marshal.PtrToStringUTF8(accountInfo.AccountId) : "");
						string text3 = ((accountInfo.DisplayName != IntPtr.Zero) ? Marshal.PtrToStringUTF8(accountInfo.DisplayName) : "");
						mapping.All.Add($"{text}:{text2}:{text3}");
						if (mapping.Type == null || (mapping.Type == "epic" && text != "epic"))
						{
							mapping.Type = text;
							mapping.AccountId = text2;
							mapping.DisplayName = text3;
						}
					}
					finally
					{
						EOS_Connect_ExternalAccountInfo_Release(info);
					}
				}
				ClaimedBy.TryGetValue(addr, out var value);
				if (num == 0)
				{
					mapping.Type = "none";
				}
				Map[addr] = mapping;
				Save(addr, mapping);
				OrbState.AddEvent("eos", value, OrbState.RosterName(value) ?? value ?? "?", (num == 0) ? (addr + " has no linked account") : $"{addr} → {mapping.Type} {mapping.AccountId} \"{mapping.DisplayName}\"");
				if (num == 0)
				{
					string name = OrbState.RosterName(value) ?? value ?? "?";
					string text4 = addr + " has no external account on Epic: anonymous device-id login, not a real Steam/PSN/Xbox client (claimed " + (value ?? "?") + ")";
					OrbState.AddAlert("eosanon", value, name, text4);
					if (Plugin.GuardBanAnonymous.Value && AutoBan)
					{
						BanAddress(addr, name, "eosanon: " + text4, value);
					}
				}
				else
				{
					Compare(addr, mapping, value);
				}
			}

			private static void Compare(string addr, Mapping m, string claimed)
			{
				if (string.IsNullOrEmpty(claimed) || m == null || m.Type == "none")
				{
					return;
				}
				string text = Base(claimed);
				bool flag = false;
				bool flag2 = false;
				foreach (string item in m.All)
				{
					string[] array = item.Split(':');
					if (array.Length > 1 && array[1].Length > 0)
					{
						flag = true;
						if (array[1] == text)
						{
							flag2 = true;
						}
					}
				}
				if (!(!flag || flag2))
				{
					string name = OrbState.RosterName(claimed) ?? claimed;
					string text2 = $"Epic says {addr} is {m.Type} account {m.AccountId} (\"{m.DisplayName}\") but the client claimed {claimed}";
					OrbState.AddAlert("eosspoof", claimed, name, text2);
					if (AutoBan)
					{
						BanAddress(addr, name, "eosspoof: " + text2, claimed);
					}
				}
			}

			private static void Save(string addr, Mapping m)
			{
				try
				{
					List<string> list = new List<string>();
					if (System.IO.File.Exists(File))
					{
						list.AddRange(System.IO.File.ReadAllLines(File));
					}
					list.RemoveAll((string l) => l.StartsWith(addr + "\t", StringComparison.OrdinalIgnoreCase));
					list.Add($"{addr}\t{m.Type}\t{m.AccountId}\t{(m.DisplayName ?? "").Replace('\t', ' ')}\t{m.When}\t{string.Join("|", m.All).Replace('\t', ' ')}");
					System.IO.File.WriteAllLines(File, list);
				}
				catch
				{
				}
			}

			internal static string Json()
			{
				StringBuilder stringBuilder = new StringBuilder("{");
				foreach (KeyValuePair<string, Mapping> item in Map)
				{
					if (stringBuilder.Length > 1)
					{
						stringBuilder.Append(',');
					}
					stringBuilder.Append(OrbState.J(item.Key)).Append(":{\"type\":").Append(OrbState.J(item.Value.Type))
						.Append(",\"accountId\":")
						.Append(OrbState.J(item.Value.AccountId))
						.Append(",\"displayName\":")
						.Append(OrbState.J(item.Value.DisplayName))
						.Append(",\"when\":")
						.Append(OrbState.J(item.Value.When))
						.Append('}');
				}
				return stringBuilder.Append('}').ToString();
			}
		}

		internal static class Voice
		{
			private sealed class Bucket
			{
				public int Count;

				public int LastRate;

				public int Dropped;

				public float Window;

				public float OverSince = -1f;

				public float LastAlert = -999f;
			}

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

			private const float BanAfter = 5f;

			internal static void Patch(Harmony harmony)
			{
				//IL_009e: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ac: Expected O, but got Unknown
				MethodInfo methodInfo = null;
				try
				{
					Type[] types = typeof(PlayerNetworking).Assembly.GetTypes();
					foreach (Type type in types)
					{
						if (type.Name == "MirrorIgnoranceServer")
						{
							methodInfo = type.GetMethod("OnMessageReceived", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
							break;
						}
					}
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogWarning((object)("voice relay type scan: " + ex.Message));
				}
				if (methodInfo == null)
				{
					Plugin.Logger.LogWarning((object)"voice relay hook not found, voice limiter off");
					return;
				}
				try
				{
					harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Voice).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				catch (Exception ex2)
				{
					Plugin.Logger.LogWarning((object)("voice relay patch: " + ex2.Message));
				}
			}

			public static bool Prefix(object __0)
			{
				int value = Plugin.GuardVoiceLimit.Value;
				if (value <= 0)
				{
					return true;
				}
				try
				{
					NetworkConnection val = (NetworkConnection)((__0 is NetworkConnection) ? __0 : null);
					if (val == null)
					{
						return true;
					}
					int connectionId = val.connectionId;
					if (connectionId == 0)
					{
						return true;
					}
					float unscaledTime = Time.unscaledTime;
					lock (B)
					{
						if (!B.TryGetValue(connectionId, out var value2))
						{
							Dictionary<int, Bucket> b = B;
							Bucket obj = new Bucket
							{
								Window = unscaledTime
							};
							value2 = obj;
							b[connectionId] = obj;
						}
						if (unscaledTime - value2.Window >= 1f)
						{
							value2.LastRate = value2.Count;
							value2.Count = 0;
							value2.Window = unscaledTime;
						}
						value2.Count++;
						if (value2.Count > value)
						{
							value2.Dropped++;
							return false;
						}
					}
				}
				catch
				{
				}
				return true;
			}

			internal static void Tick()
			{
				int value = Plugin.GuardVoiceLimit.Value;
				if (value <= 0)
				{
					return;
				}
				float unscaledTime = Time.unscaledTime;
				List<(int, Bucket)> list = null;
				lock (B)
				{
					foreach (KeyValuePair<int, Bucket> item in B)
					{
						Bucket value2 = item.Value;
						if (value2.Count > value || (unscaledTime - value2.Window < 1f && value2.LastRate > value))
						{
							if (value2.OverSince < 0f)
							{
								value2.OverSince = unscaledTime;
							}
							(list ?? (list = new List<(int, Bucket)>())).Add((item.Key, value2));
						}
						else
						{
							value2.OverSince = -1f;
						}
					}
					foreach (int item2 in B.Keys.Where((int c) => !NetworkServer.connections.ContainsKey(c)).ToList())
					{
						B.Remove(item2);
					}
				}
				if (list == null)
				{
					return;
				}
				NetworkConnectionToClient val = default(NetworkConnectionToClient);
				foreach (var (num, bucket) in list)
				{
					if (!NetworkServer.connections.TryGetValue(num, ref val) || val == null)
					{
						continue;
					}
					string text = OrbState.AuthFor(num)?.ClaimedId;
					string name = OrbState.RosterName(text) ?? text ?? $"conn {num}";
					if (unscaledTime - bucket.LastAlert > 30f)
					{
						bucket.LastAlert = unscaledTime;
						OrbState.AddAlert("voiceflood", text, name, $"{Math.Max(bucket.Count, bucket.LastRate)} voice packets/s (limit {value}), {bucket.Dropped} dropped");
					}
					if (unscaledTime - bucket.OverSince >= 5f)
					{
						string address = null;
						try
						{
							address = val.address;
						}
						catch
						{
						}
						if (AutoBan)
						{
							BanAddress(address, name, $"voiceflood: {5f:0}s sustained over {value} packets/s", text);
						}
						try
						{
							((NetworkConnection)val).Disconnect();
						}
						catch
						{
						}
						bucket.OverSince = -1f;
					}
				}
			}
		}

		[HarmonyPatch(typeof(PlayerNetworking), "InvokeUserCode_CmdSendTextChatMessage__String")]
		internal static class FakeChatPatch
		{
			private static bool Prefix(NetworkBehaviour __0, NetworkReader __1)
			{
				if (!Plugin.GuardChatFilter.Value)
				{
					return true;
				}
				try
				{
					PlayerNetworking val = ((Il2CppObjectBase)__0).TryCast<PlayerNetworking>();
					if ((Object)(object)val == (Object)null || ((NetworkBehaviour)val).isLocalPlayer)
					{
						return true;
					}
					int position = __1.Position;
					string text = null;
					try
					{
						text = NetworkReaderExtensions.ReadString(__1);
					}
					catch
					{
					}
					__1.Position = position;
					if (string.IsNullOrEmpty(text) || !FakeSystem.IsMatch(text))
					{
						return true;
					}
					Patches.MarkFired("chat/fake");
					OrbState.AddAlert("fakesys", val.identifier, Patches.Display(val), "chat styled as a system message, dropped: \"" + text + "\"");
					return false;
				}
				catch
				{
					return true;
				}
			}
		}

		private const float KickGrace = 1f;

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

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

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

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

		private static readonly Regex FakeSystem = new Regex("^\\s*(?:\\[?(?:server|system|host|admin)\\]?\\s*[:\\-]|(?:the\\s+)?(?:host|server|admin|you|player|[\\w\\-]+)\\s+(?:has|have)\\s+been\\s+(?:removed|kicked|banned|disconnected)|[\\w\\-]+\\s+(?:has|have)\\s+(?:joined|left)\\s+the\\s+(?:game|lobby|server)|(?:you\\s+(?:have\\s+been|were)\\s+)?(?:kicked|banned)\\s+(?:by|from)\\s+(?:the\\s+)?(?:host|server)|(?:connection|host)\\s+(?:lost|timed\\s+out)|lost\\s+connection\\s+to\\s+(?:the\\s+)?host)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		internal static bool AutoBan => Plugin.GuardAutoBan.Value;

		internal static void Kick(PlayerNetworking pn, string reason = null)
		{
			if ((Object)(object)pn == (Object)null)
			{
				return;
			}
			NetworkConnectionToClient val = null;
			try
			{
				val = ((NetworkBehaviour)pn).connectionToClient;
			}
			catch
			{
			}
			if (val == null)
			{
				return;
			}
			lock (PendingDisconnect)
			{
				if (PendingDisconnect.ContainsKey(((NetworkConnection)val).connectionId))
				{
					return;
				}
				PendingDisconnect[((NetworkConnection)val).connectionId] = Time.unscaledTime + 1f;
			}
			try
			{
				pn.RPCKickUser(val);
			}
			catch
			{
			}
		}

		internal static void StepKicks()
		{
			List<int> list = null;
			lock (PendingDisconnect)
			{
				foreach (KeyValuePair<int, float> item in PendingDisconnect)
				{
					if (Time.unscaledTime >= item.Value)
					{
						(list ?? (list = new List<int>())).Add(item.Key);
					}
				}
			}
			if (list == null)
			{
				return;
			}
			NetworkConnectionToClient val = default(NetworkConnectionToClient);
			foreach (int item2 in list)
			{
				lock (PendingDisconnect)
				{
					PendingDisconnect.Remove(item2);
				}
				try
				{
					if (NetworkServer.connections.TryGetValue(item2, ref val) && val != null)
					{
						((NetworkConnection)val).Disconnect();
					}
				}
				catch
				{
				}
			}
		}

		internal static void BanAddress(string address, string name, string why, string id = null)
		{
			if (string.IsNullOrEmpty(address) || address == "localhost")
			{
				return;
			}
			if (!OrbState.IsBannedAddress(address))
			{
				OrbState.BanAdd(id, name, 0uL, address);
			}
			OrbState.AddEvent("autoban", id, name, why);
			Enumerator<int, NetworkConnectionToClient> enumerator = NetworkServer.connections.GetEnumerator();
			while (enumerator.MoveNext())
			{
				NetworkConnectionToClient value = enumerator.Current.Value;
				if (value == null || ((NetworkConnection)value).connectionId == 0)
				{
					continue;
				}
				string text = null;
				try
				{
					text = value.address;
				}
				catch
				{
				}
				if (text == address)
				{
					try
					{
						((NetworkConnection)value).Disconnect();
					}
					catch
					{
					}
				}
			}
		}

		private static void Act(string kind, string id, string name, string detail, string address, NetworkConnectionToClient conn)
		{
			OrbState.AddAlert(kind, id, name, detail);
			if (AutoBan)
			{
				BanAddress(address, name ?? id ?? "?", kind + ": " + detail, id);
				return;
			}
			try
			{
				if (conn != null)
				{
					((NetworkConnection)conn).Disconnect();
				}
			}
			catch
			{
			}
		}

		private static string Base(string id)
		{
			if (id != null)
			{
				return Regex.Replace(id, "-\\d+$", "");
			}
			return null;
		}

		private static bool IsSteam(string id)
		{
			if (id != null && id.Length == 17 && id.StartsWith("7656119"))
			{
				return id.All(char.IsDigit);
			}
			return false;
		}

		private static bool IsNumeric(string id)
		{
			if (!string.IsNullOrEmpty(id) && id.Length >= 15)
			{
				return id.All(char.IsDigit);
			}
			return false;
		}

		private static bool IsEosAddr(string a)
		{
			if (a != null && a.Length == 32)
			{
				return a.All(Uri.IsHexDigit);
			}
			return false;
		}

		internal static void Tick()
		{
			StepKicks();
			Voice.Tick();
			foreach (PlayerCharacter item in OrbBehaviour.Players())
			{
				try
				{
					PlayerNetworking playerNetworking = item.playerNetworking;
					if ((Object)(object)playerNetworking == (Object)null || ((NetworkBehaviour)playerNetworking).isLocalPlayer)
					{
						continue;
					}
					string text = null;
					string text2 = null;
					string text3 = null;
					ulong num = 0uL;
					try
					{
						text = playerNetworking.identifier;
					}
					catch
					{
					}
					try
					{
						NetworkConnectionToClient connectionToClient = ((NetworkBehaviour)playerNetworking).connectionToClient;
						text2 = ((connectionToClient != null) ? connectionToClient.address : null);
					}
					catch
					{
					}
					try
					{
						text3 = playerNetworking.epicUserId;
					}
					catch
					{
					}
					try
					{
						num = playerNetworking.userPlatformId;
					}
					catch
					{
					}
					if (OrbState.IsUnsetIdentifier(text) || text2 == null)
					{
						continue;
					}
					string text4 = text3 + "|" + num;
					if (Judged.TryGetValue(((NetworkBehaviour)playerNetworking).netId, out var value) && value == text4)
					{
						continue;
					}
					Judged[((NetworkBehaviour)playerNetworking).netId] = text4;
					string name = Patches.Display(playerNetworking);
					if (IsEosAddr(text2) && !string.IsNullOrEmpty(text3) && !string.Equals(text3, text2, StringComparison.OrdinalIgnoreCase))
					{
						Act("epicspoof", text, name, "connection is " + text2 + " but the client reports epic id " + text3, text2, ((NetworkBehaviour)playerNetworking).connectionToClient);
					}
					else
					{
						if (num == 0L || !IsNumeric(Base(text)) || !(num.ToString() != Base(text)))
						{
							continue;
						}
						string text5 = null;
						foreach (PlayerCharacter item2 in OrbBehaviour.Players())
						{
							PlayerNetworking playerNetworking2 = item2.playerNetworking;
							if ((Object)(object)playerNetworking2 != (Object)null && ((Il2CppObjectBase)playerNetworking2).Pointer != ((Il2CppObjectBase)playerNetworking).Pointer && Base(playerNetworking2.identifier) == num.ToString())
							{
								text5 = Patches.Display(playerNetworking2);
								break;
							}
						}
						Act("platspoof", text, name, $"claims identifier {text} but platform id is {num}" + ((text5 != null) ? (" (" + text5 + "'s, who is connected)") : ""), text2, ((NetworkBehaviour)playerNetworking).connectionToClient);
						continue;
					}
				}
				catch
				{
				}
			}
			if (Judged.Count <= 64)
			{
				return;
			}
			HashSet<uint> live = new HashSet<uint>();
			foreach (PlayerCharacter item3 in OrbBehaviour.Players())
			{
				try
				{
					live.Add(((NetworkBehaviour)item3.playerNetworking).netId);
				}
				catch
				{
				}
			}
			foreach (uint item4 in Judged.Keys.Where((uint k) => !live.Contains(k)).ToList())
			{
				Judged.Remove(item4);
			}
		}
	}
	public class OrbBehaviour : MonoBehaviour
	{
		private class Track
		{
			public Vector3 LastPos;

			public float Speed;

			public float AirSec;

			public float SpeedSec;

			public float LastFlag = -999f;

			public bool Seen;

			public string Name;
		}

		internal static readonly string[] LookHex = new string[24]
		{
			"#4b72af", "#bb3102", "#f4cc48", "#4895a8", "#264186", "#ff2c2b", "#eaaa32", "#295b35", "#4a1538", "#2b2b2b",
			"#d55701", "#f9b8be", "#cfe190", "#8f8a84", "#962550", "#1c9063", "#60361d", "#7a021b", "#ede3d9", "#00a996",
			"#b29672", "#ffa300", "#ecfaff", "#9f500e"
		};

		internal static volatile bool NametagsOn;

		private readonly Dictionary<string, Track> _tracks = new Dictionary<string, Track>();

		private readonly Dictionary<string, GameObject> _tags = new Dictionary<string, GameObject>();

		private float _nextTick;

		private float _nextSnap;

		private bool _wasHosting;

		public OrbBehaviour(IntPtr ptr)
			: base(ptr)
		{
		}

		private void Update()
		{
			Action result;
			while (OrbState.MainQueue.TryDequeue(out result))
			{
				try
				{
					result();
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogError((object)("cmd: " + ex.Message));
				}
			}
			bool active = NetworkServer.active;
			if (active && !_wasHosting)
			{
				string worldName = null;
				try
				{
					worldName = SaveManager.worldName;
				}
				catch
				{
				}
				OrbState.NewSession(worldName);
			}
			_wasHosting = active;
			if (Time.unscaledTime >= _nextTick)
			{
				_nextTick = Time.unscaledTime + 0.25f;
				try
				{
					Tick(active);
				}
				catch (Exception ex2)
				{
					Plugin.Logger.LogError((object)("tick: " + ex2.Message));
				}
			}
			if (Time.unscaledTime >= _nextSnap)
			{
				_nextSnap = Time.unscaledTime + 0.5f;
				try
				{
					Snapshot(active);
				}
				catch (Exception ex3)
				{
					Plugin.Logger.LogError((object)("snap: " + ex3.Message));
				}
			}
			Chime.Step(((Component)this).gameObject);
			try
			{
				UpdateNametags();
			}
			catch
			{
			}
		}

		internal static IEnumerable<PlayerCharacter> Players()
		{
			List<PlayerCharacter> all = PlayerCharacter.allPlayerCharacters;
			if (all == null)
			{
				yield break;
			}
			for (int i = 0; i < all.Count; i++)
			{
				PlayerCharacter val = all[i];
				if ((Object)(object)val != (Object)null && (Object)(object)val.playerNetworking != (Object)null)
				{
					yield return val;
				}
			}
		}

		internal static PlayerCharacter ById(string id)
		{
			foreach (PlayerCharacter item in Players())
			{
				if (item.playerNetworking.identifier == id)
				{
					return item;
				}
			}
			return null;
		}

		private static PeckEffectTextInput FindSign(uint netId)
		{
			Il2CppArrayBase<PeckEffectTextInput> val = Object.FindObjectsOfType<PeckEffectTextInput>(true);
			for (int i = 0; i < val.Length; i++)
			{
				if ((Object)(object)val[i] != (Object)null && ((NetworkBehaviour)val[i]).netId == netId)
				{
					return val[i];
				}
			}
			return null;
		}

		internal static PlayerCharacter Local()
		{
			foreach (PlayerCharacter item in Players())
			{
				if (((NetworkBehaviour)item.playerNetworking).isLocalPlayer)
				{
					return item;
				}
			}
			return null;
		}

		internal static string LobbyCode()
		{
			try
			{
				EOSLobbyManager instance = EOSLobbyManager.Instance;
				return ((instance != null) ? instance.CurrentLobbyCode : null) ?? "";
			}
			catch
			{
				return "";
			}
		}

		internal static HouseAuthenticator Auth()
		{
			try
			{
				NetworkManager singleton = NetworkManager.singleton;
				object result;
				if (singleton == null)
				{
					result = null;
				}
				else
				{
					NetworkAuthenticator authenticator = singleton.authenticator;
					result = ((authenticator != null) ? ((Il2CppObjectBase)authenticator).TryCast<HouseAuthenticator>() : null);
				}
				return (HouseAuthenticator)result;
			}
			catch
			{
				return null;
			}
		}

		private void Tick(bool hosting)
		{
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ca: 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_0408: Unknown result type (might be due to invalid IL or missing references)
			if (hosting)
			{
				try
				{
					Guard.Tick();
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogError((object)("guard: " + ex.Message));
				}
			}
			foreach (Track value3 in _tracks.Values)
			{
				value3.Seen = false;
			}
			foreach (PlayerCharacter item in Players())
			{
				PlayerNetworking pn = item.playerNetworking;
				string identifier = pn.identifier;
				if (string.IsNullOrEmpty(identifier))
				{
					continue;
				}
				string name = Patches.Display(pn);
				Vector3 position = ((Component)item).transform.position;
				if (!_tracks.TryGetValue(identifier, out var value))
				{
					Dictionary<string, Track> tracks = _tracks;
					Track obj = new Track
					{
						LastPos = position,
						Name = name
					};
					value = obj;
					tracks[identifier] = obj;
					OrbState.AddEvent("join", identifier, name, OrbState.Platform(identifier));
					Chime.Play(join: true);
				}
				value.Seen = true;
				value.Name = name;
				try
				{
					OrbState.RosterSeen(identifier, name, SafeStr(() => pn.username), pn.userPlatformId, Hex(pn.lookIdHead), Hex(pn.lookIdTorso), Hex(pn.lookIdLegs), online: true);
				}
				catch
				{
				}
				float num = 0.25f;
				Vector3 val = position - value.LastPos;
				Vector3 val2 = new Vector3(val.x, 0f, val.z);
				float num2 = (value.Speed = ((Vector3)(ref val2)).magnitude / num);
				value.LastPos = position;
				if (!hosting || ((NetworkBehaviour)pn).isLocalPlayer)
				{
					continue;
				}
				string address = null;
				try
				{
					NetworkConnectionToClient connectionToClient = ((NetworkBehaviour)pn).connectionToClient;
					address = ((connectionToClient != null) ? connectionToClient.address : null);
				}
				catch
				{
				}
				if (OrbState.IsBanned(identifier) || OrbState.IsBannedAddress(address))
				{
					OrbState.AddEvent("autokick", identifier, name, "banned while in session");
					OrbState.BanAttachAddress(identifier, address);
					Guard.Kick(pn);
					continue;
				}
				if (!OrbState.LobbyAllows(identifier, address))
				{
					OrbState.AddEvent("lockreject", identifier, name, "lobby locked; player was not present when locked");
					Guard.Kick(pn);
					continue;
				}
				float num3 = val.y / num;
				value.AirSec = ((num3 > 1.5f) ? (value.AirSec + num) : 0f);
				value.SpeedSec = ((num2 > Plugin.FlyMaxSpeed.Value) ? (value.SpeedSec + num) : 0f);
				bool flag = value.AirSec > Plugin.FlyMaxAirSeconds.Value;
				bool flag2 = value.SpeedSec > 1.5f;
				if ((flag || flag2) && Time.unscaledTime - value.LastFlag > 60f)
				{
					value.LastFlag = Time.unscaledTime;
					string text = (flag ? $"climbing steadily for {value.AirSec:0.0}s" : $"sustained {num2:0.0} m/s");
					OrbState.AddAlert(flag ? "fly" : "speed", identifier, name, text + $" at {position.x:0},{position.y:0},{position.z:0}");
					if (Plugin.FlyAutoKick.Value)
					{
						OrbState.AddEvent("autokick", identifier, name, "anticheat: " + text);
						Guard.Kick(pn);
					}
				}
			}
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, Track> track in _tracks)
			{
				if (!track.Value.Seen)
				{
					OrbState.AddEvent("leave", track.Key, track.Value.Name);
					Chime.Play(join: false);
					OrbState.RosterOffline(track.Key);
					if (_tags.TryGetValue(track.Key, out var value2) && (Object)(object)value2 != (Object)null)
					{
						Object.Destroy((Object)(object)value2);
					}
					_tags.Remove(track.Key);
					list.Add(track.Key);
				}
			}
			foreach (string item2 in list)
			{
				_tracks.Remove(item2);
			}
		}

		private void Snapshot(bool hosting)
		{
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0334: Unknown result type (might be due to invalid IL or missing references)
			//IL_0348: Unknown result type (might be due to invalid IL or missing references)
			//IL_035c: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder(4096);
			stringBuilder.Append("{\"hosting\":").Append(hosting ? "true" : "false").Append(",\"session\":")
				.Append(OrbState.J(OrbState.SessionName))
				.Append(",\"nametags\":")
				.Append(NametagsOn ? "true" : "false")
				.Append(",\"locked\":")
				.Append(OrbState.LobbyLocked ? "true" : "false")
				.Append(",\"lockAllowed\":")
				.Append(OrbState.LobbyLockCount)
				.Append(",\"code\":")
				.Append(OrbState.J(hosting ? LobbyCode() : ""))
				.Append(",\"password\":")
				.Append(OrbState.J(hosting ? SafeStr(delegate
				{
					HouseAuthenticator obj = Auth();
					return (obj == null) ? null : obj.password;
				}) : ""))
				.Append(",\"players\":[");
			bool flag = true;
			foreach (PlayerCharacter item in Players())
			{
				PlayerNetworking pn = item.playerNetworking;
				string text = pn.identifier ?? "";
				Vector3 position = ((Component)item).transform.position;
				_tracks.TryGetValue(text, out var value);
				if (!flag)
				{
					stringBuilder.Append(',');
				}
				flag = false;
				stringBuilder.Append('{').Append("\"id\":").Append(OrbState.J(text))
					.Append(",\"name\":")
					.Append(OrbState.J(Patches.Display(pn)))
					.Append(",\"username\":")
					.Append(OrbState.J(SafeStr(() => pn.username)))
					.Append(",\"modName\":")
					.Append(OrbState.J(SafeStr(() => pn.moderationNameSanitized)))
					.Append(",\"platformId\":\"")
					.Append(pn.userPlatformId)
					.Append('"')
					.Append(",\"platform\":")
					.Append(OrbState.J(OrbState.Platform(text)))
					.Append(",\"local\":")
					.Append(((NetworkBehaviour)pn).isLocalPlayer ? "true" : "false")
					.Append(",\"isHost\":")
					.Append(pn.isHost ? "true" : "false")
					.Append(",\"muted\":")
					.Append(pn.isMuted ? "true" : "false")
					.Append(",\"colors\":[\"")
					.Append(Hex(pn.lookIdHead))
					.Append("\",\"")
					.Append(Hex(pn.lookIdTorso))
					.Append("\",\"")
					.Append(Hex(pn.lookIdLegs))
					.Append("\"]")
					.Append(",\"pos\":[")
					.Append((int)position.x)
					.Append(',')
					.Append((int)position.y)
					.Append(',')
					.Append((int)position.z)
					.Append(']')
					.Append(",\"speed\":")
					.Append((value?.Speed ?? 0f).ToString("0.0", CultureInfo.InvariantCulture))
					.Append(",\"air\":")
					.Append((value?.AirSec ?? 0f).ToString("0.0", CultureInfo.InvariantCulture))
					.Append(",\"banned\":")
					.Append(OrbState.IsBanned(text) ? "true" : "false")
					.Append(",\"addr\":")
					.Append(OrbState.J(SafeStr(delegate
					{
						if (!((NetworkBehaviour)pn).isLocalPlayer)
						{
							NetworkConnectionToClient connectionToClient = ((NetworkBehaviour)pn).connectionToClient;
							if (connectionToClient == null)
							{
								return (string)null;
							}
							return connectionToClient.address;
						}
						return "";
					})))
					.Append('}');
			}
			stringBuilder.Append("]}");
			OrbState.SnapshotJson = stringBuilder.ToString();
		}

		private static string Hex(int lookId)
		{
			return LookHex[(lookId % 24 + 24) % 24];
		}

		private static string SafeStr(Func<string> get)
		{
			try
			{
				return get() ?? "";
			}
			catch
			{
				return "";
			}
		}

		internal static void Cmd(string action, string id, string key, int val, string text = null)
		{
			OrbState.MainQueue.Enqueue(delegate
			{
				Run(action, id, key, val, text);
			});
		}

		private static void Run(string action, string id, string key, int val, string text)
		{
			PlayerCharacter val2 = ((id != null) ? ById(id) : null);
			PlayerNetworking val3 = (((Object)(object)val2 != (Object)null) ? val2.playerNetworking : null);
			if (action == "ban" && IsHostBanTarget(id, val3))
			{
				OrbState.AddEvent("banblocked", id, ((Object)(object)val3 != (Object)null) ? Patches.Display(val3) : "host", "the lobby host cannot be banned");
				return;
			}
			switch (action)
			{
			case "kick":
				if ((Object)(object)val3 != (Object)null)
				{
					OrbState.AddEvent("kick", id, Patches.Display(val3));
					Guard.Kick(val3);
					break;
				}
				goto default;
			case "ban":
				if ((Object)(object)val3 != (Object)null)
				{
					string address = null;
					try
					{
						NetworkConnectionToClient connectionToClient3 = ((NetworkBehaviour)val3).connectionToClient;
						address = ((connectionToClient3 != null) ? connectionToClient3.address : null);
					}
					catch
					{
					}
					if (OrbState.BanAdd(id, Patches.Display(val3), val3.userPlatformId, address))
					{
						Guard.Kick(val3);
					}
				}
				else
				{
					OrbState.BanAdd(id, OrbState.RosterName(id) ?? "(offline ban)", 0uL, OrbState.LastAddressFor(id));
				}
				break;
			case "banaddr":
				if (!string.IsNullOrEmpty(key))
				{
					Guard.BanAddress(key, string.IsNullOrEmpty(text) ? "(address ban)" : text, "manual address ban");
					break;
				}
				goto default;
			case "banimport":
			{
				(int, int, int) tuple = OrbState.BansImport(text);
				OrbState.AddEvent("banimport", null, "host", $"import: {tuple.Item1} added, {tuple.Item2} skipped, {tuple.Item3} bad");
				break;
			}
			case "eoslookup":
			{
				string text2 = key;
				if (string.IsNullOrEmpty(text2) && (Object)(object)val3 != (Object)null)
				{
					try
					{
						NetworkConnectionToClient connectionToClient2 = ((NetworkBehaviour)val3).connectionToClient;
						text2 = ((connectionToClient2 != null) ? connectionToClient2.address : null);
					}
					catch
					{
					}
				}
				if (!string.IsNullOrEmpty(text2))
				{
					Guard.Eos.Lookup(text2, ((Object)(object)val3 != (Object)null) ? id : null);
				}
				break;
			}
			case "unban":
				OrbState.BanRemove(id);
				break;
			case "locklobby":
			{
				List<string> list = new List<string>();
				List<string> list2 = new List<string>();
				foreach (PlayerCharacter item in Players())
				{
					PlayerNetworking playerNetworking = item.playerNetworking;
					if ((Object)(object)playerNetworking == (Object)null)
					{
						continue;
					}
					list.Add(playerNetworking.identifier);
					if (!((NetworkBehaviour)playerNetworking).isLocalPlayer)
					{
						try
						{
							NetworkConnectionToClient connectionToClient = ((NetworkBehaviour)playerNetworking).connectionToClient;
							list2.Add((connectionToClient != null) ? connectionToClient.address : null);
						}
						catch
						{
						}
					}
				}
				int value = OrbState.SetLobbyLocked(list, list2);
				OrbState.AddEvent("lobbylock", null, "host", $"locked to {value} current player identifier(s)");
				break;
			}
			case "unlocklobby":
				OrbState.UnlockLobby();
				OrbState.AddEvent("lobbyunlock", null, "host", "new players may join again");
				break;
			case "signset":
			{
				if (key != null && uint.TryParse(key, out var result))
				{
					PeckEffectTextInput val5 = FindSign(result);
					if ((Object)(object)val5 != (Object)null)
					{
						PlayerCharacter val6 = Local();
						string text3 = (((Object)(object)val6 != (Object)null) ? val6.playerNetworking.identifier : "");
						val5.UserCode_CmdSendNewText__String__String(text ?? "", text3);
						if (OrbState.LockedText(result) != null)
						{
							OrbState.LockSign(result, ((Object)((Component)val5).gameObject).name, text ?? "");
						}
						OrbState.AddEvent("signset", null, "host", string.IsNullOrEmpty(text) ? $"erased sign {result}" : $"set sign {result} to \"{text}\"");
					}
					else
					{
						OrbState.AddEvent("signset", null, "host", $"FAILED: sign netId {result} not found");
					}
					break;
				}
				goto default;
			}
			case "signlock":
			{
				if (key != null && uint.TryParse(key, out var result3))
				{
					PeckEffectTextInput val7 = FindSign(result3);
					if ((Object)(object)val7 == (Object)null)
					{
						OrbState.AddEvent("signlock", null, "host", $"FAILED: sign netId {result3} not found");
						break;
					}
					string text4 = null;
					try
					{
						text4 = val7.networkedText;
					}
					catch
					{
					}
					string text5 = text ?? text4 ?? "";
					PlayerCharacter val8 = Local();
					string text6 = (((Object)(object)val8 != (Object)null) ? val8.playerNetworking.identifier : "");
					if (text5 != text4)
					{
						val7.UserCode_CmdSendNewText__String__String(text5, text6);
					}
					string text7 = "sign";
					try
					{
						text7 = ((Object)((Component)val7).gameObject).name;
					}
					catch
					{
					}
					OrbState.LockSign(result3, text7, text5);
					OrbState.AddEvent("signlock", null, "host", $"locked sign {result3} ({text7}) as \"{text5}\"");
					break;
				}
				goto default;
			}
			case "signunlock":
			{
				if (key != null && uint.TryParse(key, out var result2))
				{
					if (OrbState.UnlockSign(result2))
					{
						OrbState.AddEvent("signunlock", null, "host", $"unlocked sign {result2}");
					}
					break;
				}
				goto default;
			}
			case "setpassword":
			{
				HouseAuthenticator val4 = Auth();
				if ((Object)(object)val4 == (Object)null)
				{
					OrbState.AddEvent("password", null, "host", "FAILED: no authenticator (not hosting?)");
					break;
				}
				val4.password = text ?? "";
				OrbState.AddEvent("password", null, "host", string.IsNullOrEmpty(text) ? "password removed" : "password changed");
				break;
			}
			case "nametags":
				NametagsOn = val != 0;
				break;
			default:
				OrbState.AddEvent("cmd", null, "host", "unknown action \"" + action + "\"");
				break;
			}
		}

		private static bool IsHostBanTarget(string id, PlayerNetworking target)
		{
			try
			{
				if ((Object)(object)target != (Object)null && (((NetworkBehaviour)target).isLocalPlayer || target.isHost))
				{
					return true;
				}
				PlayerCharacter obj = Local();
				PlayerNetworking val = ((obj != null) ? obj.playerNetworking : null);
				return (Object)(object)val != (Object)null && !string.IsNullOrEmpty(id) && id == val.identifier;
			}
			catch
			{
				return false;
			}
		}

		private void UpdateNametags()
		{
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Expected O, but got Unknown
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: 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_01a8: Unknown result type (might be due to invalid IL or missing references)
			if (!NametagsOn)
			{
				if (_tags.Count <= 0)
				{
					return;
				}
				foreach (GameObject value2 in _tags.Values)
				{
					if ((Object)(object)value2 != (Object)null)
					{
						Object.Destroy((Object)(object)value2);
					}
				}
				_tags.Clear();
				return;
			}
			Camera main = Camera.main;
			Color color = default(Color);
			foreach (PlayerCharacter item in Players())
			{
				PlayerNetworking playerNetworking = item.playerNetworking;
				if (((NetworkBehaviour)playerNetworking).isLocalPlayer)
				{
					continue;
				}
				string identifier = playerNetworking.identifier;
				if (!string.IsNullOrEmpty(identifier))
				{
					if (!_tags.TryGetValue(identifier, out var value) || (Object)(object)value == (Object)null)
					{
						value = new GameObject("OrbTag_" + identifier);
						((Object)value).hideFlags = (HideFlags)61;
						TextMesh obj = value.AddComponent<TextMesh>();
						obj.fontSize = 48;
						obj.characterSize = 0.022f;
						obj.anchor = (TextAnchor)7;
						obj.alignment = (TextAlignment)1;
						_tags[identifier] = value;
					}
					TextMesh component = value.GetComponent<TextMesh>();
					component.text = Patches.Display(playerNetworking);
					if (ColorUtility.TryParseHtmlString(Hex(playerNetworking.lookIdHead), ref color))
					{
						component.color = color;
					}
					value.transform.position = ((Component)item).transform.position + Vector3.up * 1.75f;
					if ((Object)(object)main != (Object)null)
					{
						value.transform.rotation = Quaternion.LookRotation(value.transform.position - ((Component)main).transform.position);
					}
				}
			}
		}
	}
	public class OrbMenu : MonoBehaviour
	{
		private const string DiscordUrl = "https://discord.gg/5z3WvVhxCf";

		private const string SourceUrl = "https://github.com/RadioFreeOpportunity/bigorb";

		private const string RepoUrl = "https://github.com/AdamMady/Moderation-Improvements/";

		private readonly string[] _tabs = new string[5] { "Players", "Bans & Roster", "Signs", "Logs", "Settings" };

		private bool _open;

		private bool _oldCursorVisible;

		private bool _oldMenuMode;

		private bool _showPassword;

		private CursorLockMode _oldCursorLock;

		private int _tab;

		private float _openedAt;

		private float _nextRefresh;

		private float _scroll;

		private float _contentHeight;

		private float _y;

		private float _width;

		private float _viewHeight;

		private string _focus;

		private string _status = "";

		private string _password = "";

		private string _banId = "";

		private string _csvPath = "";

		private string _signText = "";

		private string _logKind = "alerts";

		private JsonDocument _state;

		private Action _pending;

		private string _confirmation;

		private GUIStyle _label;

		private GUIStyle _heading;

		public static bool IsOpen { get; private set; }

		private JsonElement Root
		{
			get
			{
				if (_state != null)
				{
					return _state.RootElement;
				}
				return default(JsonElement);
			}
		}

		public OrbMenu(IntPtr ptr)
			: base(ptr)
		{
		}

		private static JsonElement Get(JsonElement value, string key)
		{
			if (value.ValueKind != JsonValueKind.Object || !value.TryGetProperty(key, out var value2))
			{
				return default(JsonElement);
			}
			return value2;
		}

		private static string Str(JsonElement value, string key)
		{
			return Get(value, key).ToString();
		}

		private static bool Flag(JsonElement value, string key)
		{
			return Get(value, key).ValueKind == JsonValueKind.True;
		}

		private static IEnumerable<JsonElement> Rows(JsonElement value)
		{
			if (value.ValueKind != JsonValueKind.Array)
			{
				return Enumerable.Empty<JsonElement>();
			}
			return value.EnumerateArray();
		}

		private void Update()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			bool keyDown = Input.GetKeyDown(Plugin.MenuKey.Value);
			if (!_open)
			{
				if (keyDown)
				{
					SetOpen(value: true);
				}
				return;
			}
			if (Time.unscaledTime - _openedAt > 0.15f && (keyDown || Input.GetKeyDown((KeyCode)27)))
			{
				SetOpen(value: false);
				return;
			}
			Cursor.lockState = (CursorLockMode)0;
			Cursor.visible = true;
			if (!(Time.unscaledTime < _nextRefresh))
			{
				_nextRefresh = Time.unscaledTime + 0.5f;
				RefreshState();
			}
		}

		private void RefreshState()
		{
			OrbState.Polled();
			try
			{
				JsonDocument state = JsonDocument.Parse("{\"snap\":" + OrbState.SnapshotJson + ",\"bans\":" + OrbState.BansJson() + ",\"roster\":" + OrbState.RosterJson() + ",\"signlocks\":" + OrbState.SignLocksJson() + ",\"signs\":" + OrbState.Tail("signs") + ",\"chat\":" + OrbState.Tail("chat") + ",\"alerts\":" + OrbState.Tail("alerts") + ",\"events\":" + OrbState.Tail("events") + ",\"identities\":" + Guard.Eos.Json() + "}");
				_state?.Dispose();
				_state = state;
			}
			catch (Exception ex)
			{
				_status = "Could not refresh: " + ex.Message;
			}
		}

		private void SetOpen(bool value)
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if (_open != value)
			{
				_open = value;
				IsOpen = value;
				_focus = null;
				_pending = null;
				if (_open)
				{
					CloseMapMenu();
					_openedAt = Time.unscaledTime;
					_oldCursorLock = Cursor.lockState;
					_oldCursorVisible = Cursor.visible;
					_oldMenuMode = ControlsManager.menuModeActive;
					ControlsManager.SetMenuMode(true);
					Cursor.lockState = (CursorLockMode)0;
					Cursor.visible = true;
					_nextRefresh = 0f;
					RefreshState();
				}
				else
				{
					ControlsManager.SetMenuMode(_oldMenuMode);
					Cursor.lockState = _oldCursorLock;
					Cursor.visible = _oldCursorVisible;
				}
			}
		}

		private static void CloseMapMenu()
		{
			try
			{
				Type.GetType("AdamMady.Minimap.MapController, AdamMady_Minimap", throwOnError: false)?.GetMethod("CloseMenu", BindingFlags.Static | BindingFlags.Public)?.Invoke(null, null);
			}
			catch
			{
			}
		}

		public static void CloseMenu()
		{
			Il2CppArrayBase<OrbMenu> val = Object.FindObjectsOfType<OrbMenu>(true);
			for (int i = 0; i < val.Length; i++)
			{
				val[i]?.SetOpen(value: false);
			}
		}

		private void OnDisable()
		{
			SetOpen(value: false);
		}

		private void OnDestroy()
		{
			SetOpen(value: false);
			_state?.Dispose();
		}

		private void OnGUI()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Invalid comparison between Unknown and I4
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: 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)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Invalid comparison between Unknown and I4
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Invalid comparison between Unknown and I4
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_034e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0354: Invalid comparison between Unknown and I4
			//IL_0305: Unknown result type (might be due to invalid IL or missing references)
			//IL_049f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0465: Unknown result type (might be due to invalid IL or missing references)
			//IL_035a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0364: Invalid comparison between Unknown and I4
			//IL_037b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0385: Invalid comparison between Unknown and I4
			//IL_0367: Unknown result type (might be due to invalid IL or missing references)
			//IL_0371: Invalid comparison between Unknown and I4
			//IL_03ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d4: Invalid comparison between Unknown and I4
			//IL_03eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f5: Invalid comparison between Unknown and I4
			//IL_03d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e1: Invalid comparison between Unknown and I4
			//IL_0652: Unknown result type (might be due to invalid IL or missing references)
			//IL_0582: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0661: Unknown result type (might be due to invalid IL or missing references)
			//IL_0667: Invalid comparison between Unknown and I4
			//IL_0628: Unknown result type (might be due to invalid IL or missing references)
			//IL_066a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0670: Invalid comparison between Unknown and I4
			//IL_0673: Unknown result type (might be due to invalid IL or missing references)
			//IL_0679: Invalid comparison between Unknown and I4
			if (!_open)
			{
				return;
			}
			Event current = Event.current;
			if ((int)current.type == 4 && Time.unscaledTime - _openedAt > 0.15f && (int)current.keyCode == 27)
			{
				current.Use();
				SetOpen(value: false);
				return;
			}
			if (_label == null)
			{
				_label = new GUIStyle(GUI.skin.label)
				{
					fontSize = 15,
					wordWrap = true
				};
				_heading = new GUIStyle(_label)
				{
					fontSize = 23,
					fontStyle = (FontStyle)1
				};
			}
			GUI.depth = -1000;
			Color color = GUI.color;
			GUI.color = new Color(0.055f, 0.065f, 0.09f, 0.94f);
			GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.whiteTexture);
			GUI.color = color;
			GUI.Label(new Rect(24f, 16f, (float)(Screen.width - 500), 36f), "MODERATION IMPROVEMENTS  /  " + (NetworkServer.active ? OrbState.SessionName : "Not hosting"), _heading);
			if (GUI.Button(new Rect((float)(Screen.width - 306), 18f, 142f, 32f), "Join The Discord!"))
			{
				Application.OpenURL("https://discord.gg/5z3WvVhxCf");
			}
			if (GUI.Button(new Rect((float)(Screen.width - 156), 18f, 132f, 32f), "Close [" + ((object)Plugin.MenuKey.Value/*cast due to .constrained prefix*/).ToString() + "]"))
			{
				SetOpen(value: false);
				return;
			}
			float num = ((float)Screen.width - 48f) / (float)_tabs.Length;
			for (int i = 0; i < _tabs.Length; i++)
			{
				GUI.backgroundColor = (Color)((i == _tab) ? new Color(0.35f, 0.7f, 1f) : Color.white);
				if (GUI.Button(new Rect(24f + (float)i * num, 64f, num - 4f, 38f), _tabs[i]))
				{
					_tab = i;
					_scroll = 0f;
					_focus = null;
				}
			}
			GUI.backgroundColor = Color.white;
			GUI.Label(new Rect(24f, (float)(Screen.height - 35), (float)(Screen.width - 48), 30f), _status, _label);
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(24f, 114f, (float)(Screen.width - 48), (float)Math.Max(50, Screen.height - 160));
			_viewHeight = ((Rect)(ref val)).height;
			_width = ((Rect)(ref val)).width - 22f;
			if (_pending == null && (int)current.type == 6)
			{
				_scroll = Mathf.Clamp(_scroll + current.delta.y * 35f, 0f, Math.Max(0f, _contentHeight - _viewHeight));
				current.Use();
			}
			if (_pending == null && (int)current.type == 4)
			{
				if ((int)current.keyCode == 281 || (int)current.keyCode == 274)
				{
					_scroll = Mathf.Clamp(_scroll + (((int)current.keyCode == 281) ? (_viewHeight * 0.8f) : 42f), 0f, Math.Max(0f, _contentHeight - _viewHeight));
					current.Use();
				}
				else if ((int)current.keyCode == 280 || (int)current.keyCode == 273)
				{
					_scroll = Mathf.Clamp(_scroll - (((int)current.keyCode == 280) ? (_viewHeight * 0.8f) : 42f), 0f, Math.Max(0f, _contentHeight - _viewHeight));
					current.Use();
				}
			}
			if (_contentHeight > _viewHeight)
			{
				_scroll = GUI.VerticalScrollbar(new Rect(((Rect)(ref val)).xMax - 16f, ((Rect)(ref val)).y, 16f, _viewHeight), _scroll, _viewHeight, 0f, _contentHeight);
			}
			bool enabled = GUI.enabled;
			GUI.enabled = _pending == null;
			GUI.BeginGroup(val);
			_y = 0f;
			try
			{
				switch (_tab)
				{
				case 0:
					Players();
					break;
				case 1:
					Bans();
					break;
				case 2:
					Signs();
					break;
				case 3:
					Logs();
					break;
				case 4:
					Settings();
					break;
				}
				_contentHeight = _y;
			}
			finally
			{
				GUI.EndGroup();
				GUI.enabled = enabled;
			}
			_scroll = Mathf.Clamp(_scroll, 0f, Math.Max(0f, _contentHeight - _viewHeight));
			if (_pending != null)
			{
				Rect val2 = default(Rect);
				((Rect)(ref val2))..ctor((float)(Screen.width - 520) / 2f, (float)(Screen.height - 190) / 2f, 520f, 190f);
				GUI.Box(val2, "Confirm action");
				GUI.Label(new Rect(((Rect)(ref val2)).x + 20f, ((Rect)(ref val2)).y + 35f, 480f, 90f), _confirmation, _label);
				if (GUI.Button(new Rect(((Rect)(ref val2)).x + 20f, ((Rect)(ref val2)).y + 140f, 230f, 32f), "Cancel"))
				{
					_pending = null;
				}
				if (GUI.Button(new Rect(((Rect)(ref val2)).x + 270f, ((Rect)(ref val2)).y + 140f, 230f, 32f), "Confirm"))
				{
					Action pending = _pending;
					_pending = null;
					pending?.Invoke();
				}
			}
			if ((int)current.type == 0)
			{
				_focus = null;
			}
			if ((int)current.type == 4 || (int)current.type == 5 || (int)current.type == 6)
			{
				current.Use();
			}
		}

		private Rect Line(float height = 34f)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			Rect result = new Rect(0f, _y - _scroll, _width, height);
			_y += height + 6f;
			return result;
		}

		private bool Visible(Rect rect)
		{
			if (((Rect)(ref rect)).yMax >= 0f)
			{
				return ((Rect)(ref rect)).y < _viewHeight;
			}
			return false;
		}

		private void Text(string text, bool title = false)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: 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_0045: Unknown result type (might be due to invalid IL or missing references)
			GUIStyle val = (title ? _heading : _label);
			float height = Math.Max(title ? 34 : 24, val.CalcHeight(new GUIContent(text), _width));
			Rect val2 = Line(height);
			if (Visible(val2))
			{
				GUI.Label(val2, text, val);
			}
		}

		private void Buttons(params (string label, Action action)[] buttons)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			int num = Math.Max(1, (int)(_width / 185f));
			for (int i = 0; i < buttons.Length; i += num)
			{
				Rect rect = Line();
				int num2 = Math.Min(num, buttons.Length - i);
				float num3 = _width / (float)num2;
				if (!Visible(rect))
				{
					continue;
				}
				for (int j = 0; j < num2; j++)
				{
					if (GUI.Button(new Rect((float)j * num3, ((Rect)(ref rect)).y, num3 - 6f, ((Rect)(ref rect)).height), buttons[i + j].label))
					{
						_focus = null;
						buttons[i + j].action();
					}
				}
			}
		}

		private void InputField(string key, string caption, ref string value)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Invalid comparison between Unknown and I4
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Invalid comparison between Unknown and I4
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Invalid comparison between Unknown and I4
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Invalid comparison between Unknown and I4
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Invalid comparison between Unknown and I4
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Invalid comparison between Unknown and I4
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Invalid comparison between Unknown and I4
			Text(caption);
			Rect val = Line();
			if (!Visible(val))
			{
				return;
			}
			GUI.Box(val, value + ((_focus == key) ? " |" : ""), GUI.skin.textField);
			Event current = Event.current;
			if (GUI.enabled && (int)current.type == 0 && ((Rect)(ref val)).Contains(current.mousePosition))
			{
				_focus = key;
				current.Use();
			}
			if (GUI.enabled && !(_focus != key) && (int)current.type == 4)
			{
				if ((current.control || current.command) && (int)current.keyCode == 118)
				{
					value += GUIUtility.systemCopyBuffer;
				}
				else if ((int)current.keyCode == 8 && value.Length > 0)
				{
					value = value.Substring(0, value.Length - 1);
				}
				else if ((int)current.keyCode == 127)
				{
					value = "";
				}
				else if ((int)current.keyCode == 13 || (int)current.keyCode == 271 || (int)current.keyCode == 9)
				{
					_focus = null;
				}
				else if (!current.control && !current.alt && !char.IsControl(current.character))
				{
					value += current.character;
				}
				current.Use();
			}
		}

		private void Send(string action, string id = null, string key = null, int val = 0, string text = null, bool confirm = false)
		{
			bool flag = action == "nametags" || action == "ban" || action == "unban";
			if (!NetworkServer.active && !flag)
			{
				_status = "Host a lobby to use this control.";
			}
			else if (confirm)
			{
				_confirmation = action + " - " + (text ?? key ?? id ?? "selected target") + "?";
				_pending = delegate
				{
					Send(action, id, key, val, text);
				};
			}
			else
			{
				OrbBehaviour.Cmd(action, id, key, val, text);
				_status = ((!NetworkServer.active && (action == "ban" || action == "unban")) ? "Local ban list updated for future hosted lobbies." : ("Queued: " + action + ". Check Events for outcome."));
			}
		}

		private void Details(JsonElement value)
		{
			if (value.ValueKind != JsonValueKind.Object)
			{
				Text(value.ToString());
				return;
			}
			Text(string.Join("   |   ", from p in value.EnumerateObject()
				where p.Value.ValueKind != JsonValueKind.Array && p.Value.ValueKind != JsonValueKind.Object
				select p.Name + ": " + p.Value));
		}

		private void Players()
		{
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			JsonElement snap = Get(Root, "snap");
			Text("Join code: " + Str(snap, "code"));
			Buttons(("Copy join code", delegate
			{
				GUIUtility.systemCopyBuffer = Str(snap, "code");
			}), ("Name tags: " + OrbBehaviour.NametagsOn, delegate
			{
				Send("nametags", null, null, (!OrbBehaviour.NametagsOn) ? 1 : 0);
			}));
			if (NetworkServer.active)
			{
				bool locked = Flag(snap, "locked");
				Buttons((locked ? "Unlock Lobby" : "Lock Lobby (keep current players)", delegate
				{
					Send(locked ? "unlocklobby" : "locklobby", null, null, 0, null, confirm: true);
				}));
				if (locked)
				{
					Text("Locked: current players may reconnect; new players are rejected.");
				}
			}
			Color color = default(Color);
			foreach (JsonElement item in Rows(Get(snap, "players")))
			{
				Text(Str(item, "name") + (Flag(item, "local") ? " (you)" : "") + " - " + Str(item, "platform"), title: true);
				Rect rect = Line(16f);
				int num = 0;
				foreach (JsonElement item2 in Rows(Get(item, "colors")))
				{
					if (Visible(rect) && ColorUtility.TryParseHtmlString(item2.GetString(), ref color))
					{
						GUI.color = color;
						GUI.DrawTexture(new Rect((float)(num * 32), ((Rect)(ref rect)).y, 26f, 16f), (Texture)(object)Texture2D.whiteTexture);
						GUI.color = Color.white;
					}
					num++;
				}
				Text("ID: " + Str(item, "id") + "   Position: " + Get(item, "pos").ToString() + "   Speed: " + Str(item, "speed") + " m/s");
				Text("Username: " + Str(item, "username") + "   Moderation name: " + Str(item, "modName"));
				if (Flag(item, "local") || Flag(item, "isHost"))
				{
					continue;
				}
				string id = Str(item, "id");
				if (NetworkServer.active)
				{
					Buttons(("Kick", delegate
					{
						Send("kick", id, null, 0, null, confirm: true);
					}), ("Ban", delegate
					{
						Send("ban", id, null, 0, null, confirm: true);
					}), ("Lookup identity", delegate
					{
						Send("eoslookup", id);
					}));
				}
				else
				{
					Buttons(("Ban for future hosted lobbies", delegate
					{
						Send("ban", id, null, 0, null, confirm: true);
					}));
				}
			}
			InputField("password", "New session password (empty removes password)", ref _password);
			Text("Current password: " + (_showPassword ? Str(snap, "password") : "[hidden]"));
			Buttons(("Set password", delegate
			{
				Send("setpassword", null, null, 0, _password);
			}), (_showPassword ? "Hide password" : "Show password", delegate
			{
				_showPassword = !_showPassword;
			}));
		}

		private void Bans()
		{
			InputField("ban", "Ban identifier or connection address", ref _banId);
			Buttons(("Ban identifier", delegate
			{
				if (!string.IsNullOrWhiteSpace(_banId))
				{
					Send("ban", _banId.Trim(), null, 0, null, confirm: true);
				}
			}), ("Ban address", delegate
			{
				if (!string.IsNullOrWhiteSpace(_banId))
				{
					Send("banaddr", null, _banId.Trim(), 0, "Manual ban", confirm: true);
				}
			}));
			InputField("csv", "CSV file path (empty uses config/ModerationImprovements/bans-export.csv)", ref _csvPath);
			Buttons(("Export bans CSV", delegate
			{
				Csv(import: false);
			}), ("Import bans CSV", delegate
			{
				Csv(import: true);
			}));
			Text("Banned players", title: true);
			foreach (JsonElement item in Rows(Get(Root, "bans")))
			{
				Details(item);
				string id = Str(item, "id");
				Buttons(("Unban", delegate
				{
					Send("unban", id);
				}));
			}
			Text("Everyone this session", title: true);
			foreach (JsonElement item2 in Rows(Get(Root, "roster")))
			{
				Details(item2);
				string id2 = Str(item2, "id");
				bool banned = Flag(item2, "banned");
				PlayerCharacter obj = OrbBehaviour.Local();
				PlayerNetworking val = ((obj != null) ? obj.playerNetworking : null);
				if (!((Object)(object)val != (Object)null) || !(id2 == val.identifier))
				{
					Buttons((banned ? "Unban" : "Ban", delegate
					{
						Send(banned ? "unban" : "ban", id2, null, 0, null, !banned);
					}));
				}
			}
		}

		private void Csv(bool import)
		{
			try
			{
				string text = (string.IsNullOrWhiteSpace(_csvPath) ? Path.Combine(OrbState.DataDir, "bans-export.csv") : _csvPath.Trim());
				if (import)
				{
					Send("banimport", null, null, 0, File.ReadAllText(text));
					return;
				}
				File.WriteAllText(text, OrbState.BansCsv());
				_status = "Exported: " + text;
			}
			catch (Exception ex)
			{
				_status = "CSV: " + ex.Message;
			}
		}

		private void Signs()
		{
			if (!NetworkServer.active)
			{
				Text("Whiteboard controls are available only while hosting.");
				return;
			}
			InputField("sign", "Replacement sign text (used by Set text)", ref _signText);
			Text("Locked signs", title: true);
			foreach (JsonElement item in Rows(Get(Root, "signlocks")))
			{
				Details(item);
				string key = Str(item, "net");
				Buttons(("Unlock", delegate
				{
					Send("signunlock", null, key);
				}));
			}
			Text("Sign edit history", title: true);
			foreach (JsonElement item2 in Rows(Get(Root, "signs")).Reverse())
			{
				Details(item2);
				string key2 = Str(item2, "net");
				string original = Str(item2, "text");
				if (!string.IsNullOrEmpty(key2) && !(key2 == "0"))
				{
					Buttons(("Erase", delegate
					{
						Send("signset", null, key2, 0, "");
					}), ("Restore this text", delegate
					{
						Send("signset", null, key2, 0, original);
					}), ("Lock this text", delegate
					{
						Send("signlock", null, key2, 0, original);
					}), ("Set text", delegate
					{
						Send("signset", null, key2, 0, _signText);
					}));
				}
			}
		}

		private void Logs()
		{
			Buttons(("Alerts", delegate
			{
				_logKind = "alerts";
				_scroll = 0f;
			}), ("Chat", delegate
			{
				_logKind = "chat";
				_scroll = 0f;
			}), ("Events", delegate
			{
				_logKind = "events";
				_scroll = 0f;
			}), ("Identity lookups", delegate
			{
				_logKind = "identities";
				_scroll = 0f;
			}));
			Text(_logKind, title: true);
			JsonElement value = Get(Root, _logKind);
			if (value.ValueKind == JsonValueKind.Array)
			{
				foreach (JsonElement item in Rows(value).Reverse())
				{
					Details(item);
				}
				return;
			}
			if (value.ValueKind != JsonValueKind.Object)
			{
				return;
			}
			foreach (JsonProperty item2 in value.EnumerateObject())
			{
				Text(item2.Name);
				Details(item2.Value);
			}
		}

		private void Settings()
		{
			//IL_0280: Unknown result type (might be due to invalid IL or missing references)
			//IL_0285: Unknown result type (might be due to invalid IL or missing references)
			Text("Moderation settings", title: true);
			Buttons(("Join/leave chime: " + Plugin.ChimeEnabled.Value, delegate
			{
				Chime.SetEnabled(!Plugin.ChimeEnabled.Value);
			}));
			Slider("Chime volume", Plugin.ChimeVolume, 0f, 1f);
			Buttons(("Auto-kick speed/fly: " + Plugin.FlyAutoKick.Value, delegate
			{
				Plugin.FlyAutoKick.Value = !Plugin.FlyAutoKick.Value;
			}), ("Guard auto-ban: " + Plugin.GuardAutoBan.Value, delegate
			{
				Plugin.GuardAutoBan.Value = !Plugin.GuardAutoBan.Value;
			}), ("Anonymous login guard: " + Plugin.GuardBanAnonymous.Value, delegate
			{
				Plugin.GuardBanAnonymous.Value = !Plugin.GuardBanAnonymous.Value;
			}), ("Filter fake system chat: " + Plugin.GuardChatFilter.Value, delegate
			{
				Plugin.GuardChatFilter.Value = !Plugin.GuardChatFilter.Value;
			}));
			Text("Speed/fly flags are estimates. Carrying, launches, and lag can trigger them.");
			Slider("Speed limit (m/s)", Plugin.FlyMaxSpeed, 1f, 100f);
			Slider("Climbing time limit (seconds)", Plugin.FlyMaxAirSeconds, 1f, 60f);
			Text("Voice packets / second: " + Plugin.GuardVoiceLimit.Value + " (0 disables limit)");
			Buttons(("-10 packets/s", delegate
			{
				Plugin.GuardVoiceLimit.Value = Math.Max(0, Plugin.GuardVoiceLimit.Value - 10);
			}), ("+10 packets/s", delegate
			{
				ConfigEntry<int> guardVoiceLimit = Plugin.GuardVoiceLimit;
				guardVoiceLimit.Value += 10;
			}));
			Text("Press " + ((object)Plugin.MenuKey.Value/*cast due to .constrained prefix*/).ToString() + " or Escape to close.");
			Text("Moderation code taken from Big Orb by RadioFreeOpportunity. AdamMady made this in-game menu.");
			Buttons(("Open Original Big Orb Repository", delegate
			{
				Application.OpenURL("https://github.com/RadioFreeOpportunity/bigorb");
			}), ("Moderation Improvements Repository", delegate
			{
				Application.OpenURL("https://github.com/AdamMady/Moderation-Improvements/");
			}));
		}

		private void Slider(string caption, ConfigEntry<float> config, float min, float max)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			Text(caption + ": " + config.Value.ToString("0.00"));
			Rect val = Line(22f);
			if (Visible(val))
			{
				float num = GUI.HorizontalSlider(val, config.Value, min, max);
				if (Math.Abs(num - config.Value) > 0.001f)
				{
					config.Value = num;
				}
			}
		}
	}
	internal static class OrbState
	{
		internal class BanRecord
		{
			public string Identifier;

			public string Name;

			public ulong PlatformId;

			public string When;

			public string Address;
		}

		internal class SeenRecord
		{
			public string Id;

			public string Name;

			public string Username;

			public ulong PlatformId;

			public string C1;

			public string C2;

			public string C3;

			public string FirstSeen;

			public string LastSeen;

			public bool Online;
		}

		internal class AuthInfo
		{
			public int ConnId;

			public string Address;

			public string ClaimedId;

			public string Version;

			public string When;
		}

		internal class SignLock
		{
			public uint NetId;

			public string Key;

			public string Text;

			public string When;
		}

		internal static readonly ConcurrentQueue<Action> MainQueue = new ConcurrentQueue<Action>();

		internal static string DataDir;

		private static readonly object Lock = new object();

		private static readonly List<string> Chat = new List<string>();

		private static readonly List<string> Signs = new List<string>();

		private static readonly List<string> Alerts = new List<string>();

		private static readonly List<string> Events = new List<string>();

		internal static readonly Dictionary<string, BanRecord> Bans = new Dictionary<string, BanRecord>();

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

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

		internal static volatile string SnapshotJson = "{\"hosting\":false,\"players\":[]}";

		private static long LastPollTicks;

		internal static volatile string SessionName = "no session";

		private static volatile string _sessionTag = "nosession";

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

		private static readonly (string addr, string name)[] DefaultBans = new(string, string)[1] { ("0002f3f3f940422b9e85ae1057c285c9", "spoofing client (anonymous EOS login, impersonates players, voice flood)") };

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

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

		private static readonly Dictionary<uint, SignLock> SignLocks = new Dictionary<uint, SignLock>();

		internal static bool LobbyLocked { get; private set; }

		internal static int LobbyLockCount
		{
			get
			{
				lock (Lock)
				{
					return LobbyLockIds.Count;
				}
			}
		}

		private static string BansPath => Path.Combine(DataDir, "bans.json");

		internal static void Polled()
		{
			Interlocked.Exchange(ref LastPollTicks, Environment.TickCount64);
		}

		internal static void NewSession(string worldName)
		{
			SessionName = (string.IsNullOrEmpty(worldName) ? "unnamed" : worldName);
			string value = new string(SessionName.Select((char c) => (!char.IsLetterOrDigit(c)) ? '_' : c).ToArray());
			_sessionTag = $"{value}-{DateTime.Now:yyyyMMdd-HHmmss}";
			lock (Lock)
			{
				Chat.Clear();
				Signs.Clear();
				Alerts.Clear();
				Events.Clear();
				Roster.Clear();
				SignLocks.Clear();
				LobbyLocked = false;
				LobbyLockIds.Clear();
				LobbyLockAddresses.Clear();
			}
			AddEvent("session", null, "host", "hosting started: " + SessionName);
		}

		internal static int SetLobbyLocked(IEnumerable<string> ids, IEnumerable<string> addresses)
		{
			lock (Lock)
			{
				LobbyLockIds.Clear();
				LobbyLockAddresses.Clear();
				foreach (string item in ids ?? Enumerable.Empty<string>())
				{
					if (!IsUnsetIdentifier(item))
					{
						LobbyLockIds.Add(item);
					}
				}
				foreach (string item2 in addresses ?? Enumerable.Empty<string>())
				{
					if (!string.IsNullOrEmpty(item2) && item2 != "localhost")
					{
						LobbyLockAddresses.Add(item2);
					}
				}
				LobbyLocked = true;
				return LobbyLockIds.Count;
			}
		}

		internal static void UnlockLobby()
		{
			lock (Lock)
			{
				LobbyLocked = false;
				LobbyLockIds.Clear();
				LobbyLockAddresses.Clear();
			}
		}

		internal static bool LobbyAllows(string id, string address)
		{
			lock (Lock)
			{
				return !LobbyLocked || (!IsUnsetIdentifier(id) && LobbyLockIds.Contains(id)) || (!string.IsNullOrEmpty(address) && LobbyLockAddresses.Contains(address));
			}
		}

		internal static void RosterSeen(string id, string name, string username, ulong platformId, string c1, string c2, string c3, bool online)
		{
			lock (Lock)
			{
				if (!Roster.TryGetValue(id, out var value))
				{
					Dictionary<string, SeenRecord> roster = Roster;
					SeenRecord obj = new SeenRecord
					{
						Id = id,
						FirstSeen = DateTime.Now.ToString("HH:mm:ss")
					};
					value = obj;
					roster[id] = obj;
				}
				value.Name = name;
				value.Username = username;
				value.PlatformId = platformId;
				value.C1 = c1;
				value.C2 = c2;
				value.C3 = c3;
				value.Online = online;
				if (online)
				{
					value.LastSeen = DateTime.Now.ToString("HH:mm:ss");
				}
			}
		}

		internal static void RosterOffline(string id)
		{
			lock (Lock)
			{
				if (Roster.TryGetValue(id, out var value))
				{
					value.Online = false;
				}
			}
		}

		internal static string RosterName(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				return null;
			}
			lock (Lock)
			{
				SeenRecord value;
				return Roster.TryGetValue(id, out value) ? value.Name : null;
			}
		}

		internal static string RosterJson()
		{
			lock (Lock)
			{
				return "[" + string.Join(",", from r in Roster.Values
					orderby r.FirstSeen
					select $"{{\"id\":{J(r.Id)},\"name\":{J(r.Name)},\"username\":{J(r.Username)},\"platformId\":\"{r.PlatformId}\",\"colors\":[{J(r.C1)},{J(r.C2)},{J(r.C3)}],\"first\":{J(r.FirstSeen)},\"last\":{J(r.LastSeen)},\"online\":{(r.Online ? "true" : "false")},\"banned\":{(IsBanned(r.Id) ? "true" : "false")}}}") + "]";
			}
		}

		internal static void Init()
		{
			DataDir = Path.Combine(Paths.ConfigPath, "ModerationImprovements");
			Directory.CreateDirectory(DataDir);
			Directory.CreateDirectory(Path.Combine(DataDir, "logs"));
			LoadBans();
			SeedDefaultBans();
		}

		internal static string J(string s)
		{
			if (s == null)
			{
				return "null";
			}
			StringBuilder stringBuilder = new StringBuilder("\"");
			for (int i = 0; i < s.Length; i++)
			{
				char c = s[i];
				StringBuilder stringBuilder2 = stringBuilder;
				stringBuilder2.Append(c switch
				{
					'"' => "\\\"", 
					'\\' => "\\\\", 
					'\n' => "\\n", 
					'\r' => "\\r", 
					'\t' => "\\t", 
					_ => (c >= ' ') ? c.ToString() : $"\\u{c:x4}", 
				});
			}
			return stringBuilder.Append('"').ToString();
		}

		internal static string Now()
		{
			return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
		}

		private static void Add(List<string> list, string json, string file)
		{
			lock (Lock)
			{
				list.Add(json);
				if (list.Count > 500)
				{
					list.RemoveAt(0);
				}
			}
			try
			{
				File.AppendAllText(Path.Combine(DataDir, "logs", _sessionTag + "-" + file + ".jsonl"), json + "\n");
			}
			catch
			{
			}
		}

		internal static void AddChat(string id, string name, string msg)
		{
			Add(Chat, $"{{\"t\":{J(Now())},\"id\":{J(id)},\"name\":{J(name)},\"msg\":{J(msg)}}}", "chat");
		}

		internal static void AddSign(string key, string text, string byId, string byName, uint netId = 0u)
		{
			Add(Signs, $"{{\"t\":{J(Now())},\"key\":{J(key)},\"text\":{J(text)},\"byId\":{J(byId)},\"byName\":{J(byName)},\"net\":{netId}}}", "signs");
		}

		internal static void AddAlert(string kind, string id, string name, string detail)
		{
			Add(Alerts, $"{{\"t\":{J(Now())},\"kind\":{J(kind)},\"id\":{J(id)},\"name\":{J(name)},\"detail\":{J(detail)}}}", "alerts");
		}

		internal static void AddEvent(string kind, string id, string name, string detail = null)
		{
			Add(Events, $"{{\"t\":{J(Now())},\"kind\":{J(kind)},\"id\":{J(id)},\"name\":{J(name)},\"detail\":{J(detail)}}}", "events");
		}

		internal static string Tail(string which, int n = 100)
		{
			List<string> list = which switch
			{
				"chat" => Chat, 
				"signs" => Signs, 
				"alerts" => Alerts, 
				_ => Events, 
			};
			lock (Lock)
			{
				return "[" + string.Join(",", list.Skip(Math.Max(0, list.Count - n))) + "]";
			}
		}

		internal static void LoadBans()
		{
			try
			{
				if (!File.Exists(BansPath))
				{
					return;
				}
				lock (Lock)
				{
					Bans.Clear();
					string[] array = File.ReadAllLines(BansPath);
					for (int i = 0; i < array.Length; i++)
					{
						string[] array2 = array[i].Split('\t');
						if (array2.Length >= 4 && array2[0].Length > 0)
						{
							Bans[array2[0]] = new BanRecord
							{
								Identifier = array2[0],
								Name = array2[1],
								PlatformId = (ulong.TryParse(array2[2], out var result) ? result : 0),
								When = array2[3],
								Address = ((array2.Length >= 5 && array2[4].Length > 0) ? array2[4] : null)
							};
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("loading bans: " + ex.Message));
			}
		}

		private static void SaveBans()
		{
			lock (Lock)
			{
				File.WriteAllLines(BansPath, Bans.Values.Select((BanRecord b) => $"{b.Identifier}\t{b.Name?.Replace('\t', ' ')}\t{b.PlatformId}\t{b.When}\t{b.Address ?? ""}"));
			}
		}

		internal static bool IsUnsetIdentifier(string id)
		{
			if (!string.IsNullOrEmpty(id))
			{
				return id == "0";
			}
			return true;
		}

		internal static string Platform(string id)
		{
			if (IsUnsetIdentifier(id))
			{
				return "unset";
			}
			if (id.Length == 17 && id.StartsWith("7656119") && id.All(char.IsDigit))
			{
				return "steam";
			}
			if (id.Length >= 18 && id.All(char.IsDigit))
			{
				return "psn";
			}
			if (id.Length >= 36 && id[8] == '-' && id[13] == '-' && id[18] == '-' && id[23] == '-')
			{
				if (id.Length <= 36 || id[36] != '-')
				{
					return "uuid";
				}
				return "uuid" + id.Substring(36);
			}
			return "other";
		}

		internal static bool BanAdd(string id, string name, ulong platformId, string address = null)
		{
			if (IsUnsetIdentifier(id))
			{
				if (string.IsNullOrEmpty(address))
				{
					AddAlert("banfail", id, name, "identifier not synced yet, ban NOT recorded, retry in a moment");
					return false;
				}
				id = "addr:" + address;
			}
			lock (Lock)
			{
				Bans[id] = new BanRecord
				{
					Identifier = id,
					Name = name,
					PlatformId = platformId,
					When = Now(),
					Address = address
				};
			}
			SaveBans();
			AddEvent("ban", id, name, Platform(id) + ((address != null) ? (" addr " + address) : ""));
			return true;
		}

		internal static bool IsBannedAddress(string address)
		{
			if (string.IsNullOrEmpty(address) || address == "localhost")
			{
				return false;
			}
			lock (Lock)
			{
				return Bans.Values.Any((BanRecord b) => b.Address == address);
			}
		}

		internal static void BanAttachAddress(string id, string address)
		{
			if (string.IsNullOrEmpty(address))
			{
				return;
			}
			bool flag = false;
			lock (Lock)
			{
				if (Bans.TryGetValue(id, out var value) && value.Address == null)
				{
					value.Address = address;
					flag = true;
				}
			}
			if (flag)
			{
				SaveBans();
				AddEvent("ban", id, RosterName(id), "ban now carries address " + address);
			}
		}

		internal static string BansCsv()
		{
			StringBuilder stringBuilder = new StringBuilder("identifier,name,platformId,when,address\n");
			lock (Lock)
			{
				foreach (BanRecord item in Bans.Values.OrderBy((BanRecord b) => b.When))
				{
					stringBuilder.Append(Csv(item.Identifier)).Append(',').Append(Csv(item.Name))
						.Append(',')
						.Append(item.PlatformId)
						.Append(',')
						.Append(Csv(item.When))
						.Append(',')
						.Append(Csv(item.Address))
						.Append('\n');
				}
			}
			return stringBuilder.ToString();
		}

		private static string Csv(string s)
		{
			if (s == null)
			{
				s = "";
			}
			if (s.IndexOfAny(new char[3] { ',', '"', '\n' }) < 0)
			{
				return s;
			}
			return "\"" + s.Replace("\"", "\"\"") + "\"";
		}

		internal static (int added, int skipped, int bad) BansImport(string csv)
		{
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			if (string.IsNullOrEmpty(csv))
			{
				return (added: 0, skipped: 0, bad: 0);
			}
			string[] array = csv.Split('\n');
			foreach (string obj in array)
			{
				string text = obj.TrimEnd('\r').Trim();
				if (text.Length == 0 || text.StartsWith("identifier,") || text.StartsWith("#"))
				{
					continue;
				}
				List<string> list = SplitCsv(text);
				if (list.Count < 1)
				{
					num3++;
					continue;
				}
				string text2 = list[0].Trim();
				string name = ((list.Count > 1) ? list[1] : "");
				string addr = ((list.Count > 4) ? list[4].Trim() : "");
				ulong.TryParse((list.Count > 2) ? list[2] : "0", out var result);
				if (addr.Length == 0 && text2.StartsWith("addr:"))
				{
					addr = text2.Substring(5);
				}
				if (IsUnsetIdentifier(text2) && addr.Length == 0)
				{
					num3++;
					continue;
				}
				if (text2.StartsWith("addr:") || IsUnsetIdentifier(text2))
				{
					text2 = "addr:" + addr;
				}
				bool flag;
				lock (Lock)
				{
					flag = Bans.ContainsKey(text2) || (addr.Length > 0 && Bans.Values.Any((BanRecord b) => b.Address == addr));
				}
				if (flag)
				{
					num2++;
					continue;
				}
				lock (Lock)
				{
					Bans[text2] = new BanRecord
					{
						Identifier = text2,
						Name = name,
						PlatformId = result,
						When = ((list.Count > 3 && list[3].Length > 0) ? list[3] : Now()),
						Address = ((addr.Length > 0) ? addr : null)
					};
				}
				num++;
			}
			if (num > 0)
			{
				SaveBans();
			}
			AddEvent("banimport", null, "host", $"{num} added, {num2} already banned, {num3} unreadable");
			return (added: num, skipped: num2, bad: num3);
		}

		private static List<string> SplitCsv(string line)
		{
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			for (int i = 0; i < line.Length; i++)
			{
				char c = line[i];
				if (flag)
				{
					if (c == '"')
					{
						if (i + 1 < line.Length && line[i + 1] == '"')
						{
							stringBuilder.Append('"');
							i++;
						}
						else
						{
							flag = false;
						}
					}
					else
					{
						stringBuilder.Append(c);
					}
					continue;
				}
				switch (c)
				{
				case '"':
					flag = true;
					break;
				case ',':
					list.Add(stringBuilder.ToString());
					stringBuilder.Clear();
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			list.Add(stringBuilder.ToString());
			return list;
		}

		internal static void SeedDefaultBans()
		{
			int num = 0;
			(string, string)[] defaultBans = DefaultBans;
			for (int i = 0; i < defaultBans.Length; i++)
			{
				var (text, name) = defaultBans[i];
				if (!IsBannedAddress(text))
				{
					lock (Lock)
					{
						Bans["addr:" + text] = new BanRecord
						{
							Identifier = "addr:" + text,
							Name = name,
							When = Now(),
							Address = text
						};
					}
					num++;
				}
			}
			if (num > 0)
			{
				SaveBans();
				AddEvent("ban", null, "host", $"{num} known bad address(es) added from the built-in list");
			}
		}

		internal static void BanRemove(string id)
		{
			BanRecord value = null;
			lock (Lock)
			{
				if (Bans.TryGetValue(id, out value))
				{
					Bans.Remove(id);
				}
			}
			if (value != null)
			{
				SaveBans();
				AddEvent("unban", id, value.Name);
			}
		}

		internal static bool IsBanned(string id)
		{
			lock (Lock)
			{
				return id != null && Bans.ContainsKey(id);
			}
		}

		internal static string LastAddressFor(string id)
		{
			lock (Lock)
			{
				string value;
				return AddrById.TryGetValue(id ?? "", out value) ? value : null;
			}
		}

		internal static AuthInfo AuthSeen(int connId, string address, string claimedId, string version)
		{
			AuthInfo authInfo = new AuthInfo
			{
				ConnId = connId,
				Address = address,
				ClaimedId = claimedId,
				Version = version,
				When = Now()
			};
			lock (Lock)
			{
				AuthByConn[connId] = authInfo;
				if (!string.IsNullOrEmpty(address) && !string.IsNullOrEmpty(claimedId))
				{
					AddrById[claimedId] = address;
				}
			}
			return authInfo;
		}

		internal static AuthInfo AuthFor(int connId)
		{
			lock (Lock)
			{
				AuthInfo value;
				return AuthByConn.TryGetValue(connId, out value) ? value : null;
			}
		}

		internal static void LockSign(uint netId, string key, string text)
		{
			lock (Lock)
			{
				SignLocks[netId] = new SignLock
				{
					NetId = netId,
					Key = key,
					Text = (text ?? ""),
					When = Now()
				};
			}
		}

		internal static bool UnlockSign(uint netId)
		{
			lock (Lock)
			{
				return SignLocks.Remove(netId);
			}
		}

		internal static string LockedText(uint netId)
		{
			lock (Lock)
			{
				SignLock value;
				return SignLocks.TryGetValue(netId, out value) ? value.Text : null;
			}
		}

		internal static string SignLocksJson()
		{
			lock (Lock)
			{
				return "[" + string.Join(",", from l in SignLocks.Values
					orderby l.When
					select $"{{\"net\":{l.NetId},\"key\":{J(l.Key)},\"text\":{J(l.Text)},\"when\":{J(l.When)}}}") + "]";
			}
		}

		internal static string BansJson()
		{
			lock (Lock)
			{
				return "[" + string.Join(",", Bans.Values.Select((BanRecord b) => $"{{\"id\":{J(b.Identifier)},\"name\":{J(b.Name)},\"platformId\":\"{b.PlatformId}\",\"when\":{J(b.When)},\"addr\":{J(b.Address)}}}")) + "]";
			}
		}
	}
	internal static class Patches
	{
		[HarmonyPatch(typeof(PlayerNetworking), "UserCode_CmdSendTextChatMessage__String")]
		internal static class ChatCmdPatch
		{
			private static void Postfix(PlayerNetworking __instance, string __0)
			{
				try
				{
					LogChat("chat/cmd", __instance.identifier, Display(__instance), __0);
				}
				catch
				{
				}
			}
		}

		[HarmonyPatch(typeof(PlayerNetworking), "RpcTextChatMessage")]
		internal static class ChatRpcPatch
		{
			private static void Postfix(PlayerNetworking __instance, string __0)
			{
				try
				{
					LogChat("chat/rpc", __instance.identifier, Display(__instance), __0);
				}
				catch
				{
				}
			}
		}

		[HarmonyPatch(typeof(PlayerTexter), "ReceieveMessage")]
		internal static class ChatReceivePatch
		{
			private static void Postfix(PlayerTexter __instance, string __0)
			{
				try
				{
					PlayerCharacter playerCharacter = __instance.playerCharacter;
					PlayerNetworking val = ((playerCharacter != null) ? playerCharacter.playerNetworking : null);
					LogChat("chat/receive", (val != null) ? val.identifier : null, ((Object)(object)val != (Object)null) ? Display(val) : "?", __0);
				}
				catch
				{
				}
			}
		}

		[HarmonyPatch(typeof(PlayerTexter), "DisplayMessage")]
		internal static class ChatDisplayPatch
		{
			private static void Postfix(PlayerTexter __instance, string __0)
			{
				try
				{
					PlayerCharacter playerCharacter = __instance.playerCharacter;
					PlayerNetworking val = ((playerCharacter != null) ? playerCharacter.playerNetworking : null);
					LogChat("chat/display", (val != null) ? val.identifier : null, ((Object)(object)val != (Object)null) ? Display(val) : "?", __0);
				}
				catch
				{
				}
			}
		}

		[HarmonyPatch(typeof(PeckEffectTextInput), "CmdSendNewText")]
		internal static class SignCmdPatch
		{
			private static void Postfix(PeckEffectTextInput __instance, string __0, string __1)
			{
				try
				{
					LogSign("sign/cmd", SignKey(__instance), __0, __1, SignNetId(__instance));
				}
				catch
				{
				}
			}
		}

		[HarmonyPatch(typeof(PeckEffectTextInput), "OnChangeNetworkedText")]
		internal static class SignSyncPatch
		{
			private static void Postfix(PeckEffectTextInput __instance, string __1)
			{
				try
				{
					MarkFired("sign/sync");
					PeckEffectTextInput inst = __instance;