Decompiled source of AI LIMIT Together v0.2.5

BepInEx/plugins/AILimitCoop/AILimitCoop.dll

Decompiled 5 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using AILimitCoop.Dev;
using AILimitCoop.Game;
using AILimitCoop.Net;
using AILimitCoop.UI;
using AILimitCoop.Util;
using ArchiveDef;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
using BehaviorDesigner.Runtime.Tasks.Unity.Math;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using BepInEx.Unity.IL2CPP.UnityEngine;
using GameDef;
using HarmonyLib;
using Il2CppInterop.Common.XrefScans;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Reflection;
using LevelEdit;
using LiteNetLib;
using LiteNetLib.Layers;
using LiteNetLib.Utils;
using Microsoft.CodeAnalysis;
using PlayerShowSateDefine;
using RootMotion.Dynamics;
using SceneSettingDef;
using SenseGame;
using SenseGame.Tasks;
using Steamworks;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.AddressableAssets;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Samples;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.ResourceManagement.AsyncOperations;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("AILimitCoop contributors")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.4.0")]
[assembly: AssemblyInformationalVersion("0.2.4")]
[assembly: AssemblyProduct("AI LIMIT Co-op")]
[assembly: AssemblyTitle("AILimitCoop")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.2.4.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 AILimitCoop
{
	public enum PartyHudCorner
	{
		TopRight,
		TopLeft,
		BottomRight,
		BottomLeft
	}
	public enum TransportKind
	{
		UDP,
		Steam
	}
	public enum HitDetectionMode
	{
		Auto,
		ClientSide,
		HostForward
	}
	public static class CoopConfig
	{
		public static ConfigEntry<string> PlayerName;

		public static ConfigEntry<TransportKind> Transport;

		public static ConfigEntry<int> Port;

		public static ConfigEntry<string> JoinAddress;

		public static ConfigEntry<int> MaxPlayers;

		public static ConfigEntry<KeyCode> ToggleMenuKey;

		public static ConfigEntry<KeyCode> HostKey;

		public static ConfigEntry<KeyCode> JoinKey;

		public static ConfigEntry<KeyCode> LeaveKey;

		public static ConfigEntry<float> PlayerSendRate;

		public static ConfigEntry<float> MonsterSendRate;

		public static ConfigEntry<float> NetworkDiagnosticsInterval;

		public static ConfigEntry<float> InterpolationDelay;

		public static ConfigEntry<float> GhostAnimSmoothing;

		public static ConfigEntry<float> GhostAnimCrossFade;

		public static ConfigEntry<bool> SyncMonsters;

		public static ConfigEntry<float> EnemyHpPerExtraPlayer;

		public static ConfigEntry<bool> GhostTargeting;

		public static ConfigEntry<HitDetectionMode> HitDetection;

		public static ConfigEntry<bool> GhostEquipment;

		public static ConfigEntry<bool> GhostCollision;

		public static ConfigEntry<bool> ForceRunInBackground;

		public static ConfigEntry<bool> EnableOverlay;

		public static ConfigEntry<bool> ShowHud;

		public static ConfigEntry<float> HudScale;

		public static ConfigEntry<float> HudOpacity;

		public static ConfigEntry<PartyHudCorner> HudCorner;

		public static ConfigEntry<bool> AllyHealthBars;

		public static ConfigEntry<float> AllyHealthBarDistance;

		public static ConfigEntry<bool> AllyHealthBarNames;

		public static ConfigEntry<bool> ShowChat;

		public static ConfigEntry<KeyCode> ChatKey;

		public static ConfigEntry<bool> ShowMenuOnStart;

		public const string DefaultPlayerName = "Player";

		public static ConfigEntry<bool> Verbose;

		public static ConfigEntry<bool> DevCommands;

		public static ConfigEntry<string> PatchGroups;

		public static ConfigEntry<int> SimulatedLatencyMs;

		public static ConfigEntry<int> SimulatedJitterMs;

		public static ConfigEntry<int> SimulatedPacketLossPercent;

		public static int EffectivePort
		{
			get
			{
				if (InstanceProfile.Port <= 0)
				{
					return Port.Value;
				}
				return InstanceProfile.Port;
			}
		}

		public static string EffectivePlayerName
		{
			get
			{
				if (!string.IsNullOrEmpty(InstanceProfile.PlayerName))
				{
					return InstanceProfile.PlayerName;
				}
				string text = PlayerName?.Value;
				if (!string.IsNullOrWhiteSpace(text) && text != "Player")
				{
					return text;
				}
				string localName = SteamPresence.LocalName;
				if (!string.IsNullOrWhiteSpace(localName))
				{
					return localName;
				}
				return "Player";
			}
		}

		public static void Bind(ConfigFile cfg)
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Expected O, but got Unknown
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Expected O, but got Unknown
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Expected O, but got Unknown
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Expected O, but got Unknown
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Expected O, but got Unknown
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Expected O, but got Unknown
			//IL_0378: Unknown result type (might be due to invalid IL or missing references)
			//IL_0382: Expected O, but got Unknown
			//IL_03b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ba: Expected O, but got Unknown
			//IL_041e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0428: Expected O, but got Unknown
			//IL_0510: Unknown result type (might be due to invalid IL or missing references)
			//IL_051a: Expected O, but got Unknown
			//IL_0540: Unknown result type (might be due to invalid IL or missing references)
			//IL_054a: Expected O, but got Unknown
			//IL_056d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0577: Expected O, but got Unknown
			PlayerName = cfg.Bind<string>("General", "PlayerName", DefaultName(), "Name shown to other players.");
			Transport = cfg.Bind<TransportKind>("General", "Transport", TransportKind.UDP, "UDP: connect by IP address (LAN, VPN such as ZeroTier/Radmin/Hamachi, or a forwarded port).\nSteam: experimental peer-to-peer over Steam; requires the host and clients to be Steam friends.");
			Port = cfg.Bind<int>("General", "Port", 7777, "UDP port to host on (and default port when joining).");
			JoinAddress = cfg.Bind<string>("General", "JoinAddress", "127.0.0.1:7777", "Address used by the Join key / menu. UDP: ip:port. Steam: the host's 64-bit Steam id.");
			MaxPlayers = cfg.Bind<int>("General", "MaxPlayers", 8, new ConfigDescription("Maximum players in a hosted session, including the host. More than two is experimental; up to 16 allowed for stress testing.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 16), Array.Empty<object>()));
			ToggleMenuKey = cfg.Bind<KeyCode>("Keys", "ToggleMenu", (KeyCode)286, "Show/hide the co-op menu.");
			HostKey = cfg.Bind<KeyCode>("Keys", "Host", (KeyCode)287, "Start hosting a session.");
			JoinKey = cfg.Bind<KeyCode>("Keys", "Join", (KeyCode)288, "Join the session at JoinAddress.");
			LeaveKey = cfg.Bind<KeyCode>("Keys", "Leave", (KeyCode)289, "Leave / stop the current session.");
			PlayerSendRate = cfg.Bind<float>("Network", "PlayerSendRate", 30f, new ConfigDescription("How many times per second your character state is sent.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), Array.Empty<object>()));
			MonsterSendRate = cfg.Bind<float>("Network", "MonsterSendRate", 15f, new ConfigDescription("Host only: how many times per second monster states are sent.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 30f), Array.Empty<object>()));
			NetworkDiagnosticsInterval = cfg.Bind<float>("Network", "DiagnosticsInterval", 30f, new ConfigDescription("Seconds between networking summaries in LogOutput.log. 0 disables periodic summaries; counters and rate-limited send failures remain active.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>()));
			InterpolationDelay = cfg.Bind<float>("Network", "InterpolationDelay", 0.1f, new ConfigDescription("Seconds of buffering applied to remote characters. Higher is smoother but adds visual latency.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>()));
			GhostAnimSmoothing = cfg.Bind<float>("Network", "GhostAnimSmoothing", 0.08f, "Seconds of easing applied to another player's animation blending. Updates arrive at the send rate, so writing them straight through makes movement look stepped. 0 disables.");
			GhostAnimCrossFade = cfg.Bind<float>("Network", "GhostAnimCrossFade", 0.06f, "Seconds to fade when another player's animation state changes, instead of cutting. 0 disables.");
			SyncMonsters = cfg.Bind<bool>("Gameplay", "SyncMonsters", true, "Share monsters in the host's level. Nearby players simulate enemies with ownership locked during combat. Disable for 'ghost only' mode where everyone fights their own monsters.");
			EnemyHpPerExtraPlayer = cfg.Bind<float>("Gameplay", "EnemyHpPerExtraPlayer", 0.5f, new ConfigDescription("Host setting, shared when players join. Extra enemy and boss HP per additional connected player: 0.5 = +50%, 0 disables. Includes spectators and players in other areas. Combat locks the maximum until rest/reset. Change before hosting a new session.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			GhostTargeting = cfg.Bind<bool>("Gameplay", "GhostTargeting", true, "The enemy simulator evaluates living players using damage, proximity, threat decay and safe target changes.");
			HitDetection = cfg.Bind<HitDetectionMode>("Gameplay", "HitDetection", HitDetectionMode.Auto, "How monster attacks against remote players are resolved. Leave on Auto unless you know what you are doing.");
			GhostEquipment = cfg.Bind<bool>("Gameplay", "GhostEquipment", true, "Show other players' armor and weapons on their ghosts (disable if ghosts fail to spawn).");
			GhostCollision = cfg.Bind<bool>("Gameplay", "GhostCollision", false, "Keep the character controller on ghosts so players physically block each other.");
			ForceRunInBackground = cfg.Bind<bool>("General", "ForceRunInBackground", true, "Keep simulating while the game window is not focused. Needed so your character keeps syncing when you\nalt-tab, and required to run two instances on one PC.");
			EnableOverlay = cfg.Bind<bool>("UI", "EnableOverlay", true, "Draw the in-game menu and HUD. Turn off if the overlay causes problems; hotkeys and the config file still work.");
			ShowHud = cfg.Bind<bool>("UI", "ShowHud", true, "Show the party cards while the co-op menu is closed.");
			HudScale = cfg.Bind<float>("UI", "HudScale", 1f, new ConfigDescription("Party HUD size, relative to automatic resolution scaling.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.75f, 1.5f), Array.Empty<object>()));
			HudOpacity = cfg.Bind<float>("UI", "HudOpacity", 0.9f, new ConfigDescription("Party HUD panel opacity. Text and health remain readable.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.35f, 1f), Array.Empty<object>()));
			HudCorner = cfg.Bind<PartyHudCorner>("UI", "HudCorner", PartyHudCorner.TopRight, "Screen corner used by the party HUD.");
			AllyHealthBars = cfg.Bind<bool>("UI", "AllyHealthBars", true, "Show a small health bar above allies in your area. Hidden while the co-op menu is open and whenever the game hides its own enemy health bars.");
			AllyHealthBarDistance = cfg.Bind<float>("UI", "AllyHealthBarDistance", 40f, new ConfigDescription("Metres from the camera beyond which allies show no bar. Bars shrink with distance and fade out near this limit.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 100f), Array.Empty<object>()));
			AllyHealthBarNames = cfg.Bind<bool>("UI", "AllyHealthBarNames", true, "Show each ally's name above their health bar. Names fade out at a shorter distance than the bar.");
			ShowChat = cfg.Bind<bool>("UI", "ShowChat", true, "Show party chat and allow typing messages. Messages fade after a few seconds while chat is closed.");
			ChatKey = cfg.Bind<KeyCode>("Keys", "Chat", (KeyCode)13, "Open party chat while in a session. Enter sends and Escape closes; other co-op hotkeys pause while you type.");
			ShowMenuOnStart = cfg.Bind<bool>("UI", "ShowMenuOnStart", true, "Show the co-op menu when the game starts.");
			Verbose = cfg.Bind<bool>("Debug", "Verbose", false, "Log per-hit and per-spawn diagnostics to BepInEx/LogOutput.log.");
			DevCommands = cfg.Bind<bool>("Debug", "DevCommands", false, "Poll <GameDir>/BepInEx/coop_cmd.txt for developer commands (host, join, leave, dump, ...). Development aid only.");
			PatchGroups = cfg.Bind<string>("Debug", "PatchGroups", "Lifecycle,Ghost,Monster,Hit,Overlay", "Which groups of game hooks to install (comma separated). Troubleshooting aid: remove a group to disable that feature.\nLifecycle = level/game start tracking, Ghost = remote player bodies, Monster = shared monster simulation,\nHit = damage relay and targeting, Overlay = in-game menu/HUD drawing.");
			SimulatedLatencyMs = cfg.Bind<int>("Debug", "SimulatedLatencyMs", 0, new ConfigDescription("Testing aid: delay every packet this many milliseconds each way. 0 is off.\nA typical distant player sits around 30-80. Leave at 0 for real play.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 500), Array.Empty<object>()));
			SimulatedJitterMs = cfg.Bind<int>("Debug", "SimulatedJitterMs", 0, new ConfigDescription("Testing aid: vary the simulated delay by up to this much, so packets arrive unevenly.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 200), Array.Empty<object>()));
			SimulatedPacketLossPercent = cfg.Bind<int>("Debug", "SimulatedPacketLossPercent", 0, new ConfigDescription("Testing aid: drop this percentage of outgoing packets. 0 is off.\n1-3 resembles a poor connection and is the case worth testing.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 50), Array.Empty<object>()));
		}

		private static string DefaultName()
		{
			return "Player";
		}
	}
	public sealed class KeyEdge
	{
		private readonly Dictionary<KeyCode, bool> _held = new Dictionary<KeyCode, bool>();

		private static readonly int _pid = Process.GetCurrentProcess().Id;

		[DllImport("user32.dll")]
		private static extern short GetAsyncKeyState(int vKey);

		public static bool MouseButtonHeld()
		{
			if ((GetAsyncKeyState(1) & 0x8000) == 0 && (GetAsyncKeyState(2) & 0x8000) == 0)
			{
				return (GetAsyncKeyState(4) & 0x8000) != 0;
			}
			return true;
		}

		[DllImport("user32.dll")]
		private static extern IntPtr GetForegroundWindow();

		[DllImport("user32.dll")]
		private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

		public static bool GameHasFocus()
		{
			try
			{
				IntPtr foregroundWindow = GetForegroundWindow();
				if (foregroundWindow == IntPtr.Zero)
				{
					return false;
				}
				GetWindowThreadProcessId(foregroundWindow, out var processId);
				return processId == (uint)_pid;
			}
			catch
			{
				return true;
			}
		}

		public static int ToVirtualKey(KeyCode key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Expected I4, but got Unknown
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Invalid comparison between Unknown and I4
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Invalid comparison between Unknown and I4
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Invalid comparison between Unknown and I4
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Invalid comparison between Unknown and I4
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Invalid comparison between Unknown and I4
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Invalid comparison between Unknown and I4
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Invalid comparison between Unknown and I4
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Invalid comparison between Unknown and I4
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Invalid comparison between Unknown and I4
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Invalid comparison between Unknown and I4
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Expected I4, but got Unknown
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Invalid comparison between Unknown and I4
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Invalid comparison between Unknown and I4
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Expected I4, but got Unknown
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Expected I4, but got Unknown
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Invalid comparison between Unknown and I4
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Invalid comparison between Unknown and I4
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Invalid comparison between Unknown and I4
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Expected I4, but got Unknown
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Invalid comparison between Unknown and I4
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Invalid comparison between Unknown and I4
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Invalid comparison between Unknown and I4
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Invalid comparison between Unknown and I4
			int num = (int)key;
			if ((int)key >= 282 && (int)key <= 296)
			{
				return 112 + (num - 282);
			}
			if ((int)key >= 97 && (int)key <= 122)
			{
				return 65 + (num - 97);
			}
			if ((int)key >= 48 && (int)key <= 57)
			{
				return 48 + (num - 48);
			}
			if ((int)key >= 256 && (int)key <= 265)
			{
				return 96 + (num - 256);
			}
			if ((int)key <= 32)
			{
				if ((int)key <= 13)
				{
					if ((int)key == 8)
					{
						return 8;
					}
					if ((int)key == 9)
					{
						return 9;
					}
					if ((int)key == 13)
					{
						return 13;
					}
				}
				else
				{
					if ((int)key == 19)
					{
						return 19;
					}
					if ((int)key == 27)
					{
						return 27;
					}
					if ((int)key == 32)
					{
						return 32;
					}
				}
			}
			else if ((int)key <= 61)
			{
				switch (key - 39)
				{
				default:
					if ((int)key != 59)
					{
						if ((int)key != 61)
						{
							break;
						}
						return 187;
					}
					return 186;
				case 6:
					return 189;
				case 0:
					return 222;
				case 5:
					return 188;
				case 7:
					return 190;
				case 8:
					return 191;
				case 1:
				case 2:
				case 3:
				case 4:
					break;
				}
			}
			else if ((int)key <= 127)
			{
				switch (key - 91)
				{
				default:
					if ((int)key != 127)
					{
						break;
					}
					return 46;
				case 5:
					return 192;
				case 0:
					return 219;
				case 2:
					return 221;
				case 1:
					return 220;
				case 3:
				case 4:
					break;
				}
			}
			else
			{
				switch (key - 273)
				{
				case 7:
					return 33;
				case 8:
					return 34;
				case 6:
					return 35;
				case 5:
					return 36;
				case 3:
					return 37;
				case 0:
					return 38;
				case 2:
					return 39;
				case 1:
					return 40;
				case 4:
					return 45;
				}
				switch (key - 300)
				{
				case 1:
					return 20;
				case 0:
					return 144;
				case 2:
					return 145;
				case 4:
					return 160;
				case 3:
					return 161;
				case 6:
					return 162;
				case 5:
					return 163;
				case 8:
					return 164;
				case 7:
					return 165;
				}
			}
			return 0;
		}

		public bool Pressed(KeyCode key)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			try
			{
				int num = ToVirtualKey(key);
				if (num != 0)
				{
					flag = (GetAsyncKeyState(num) & 0x8000) != 0;
				}
			}
			catch
			{
			}
			bool value;
			bool flag2 = _held.TryGetValue(key, out value) && value;
			_held[key] = flag;
			if (flag)
			{
				return !flag2;
			}
			return false;
		}

		public bool Held(KeyCode key)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			bool value;
			return _held.TryGetValue(key, out value) && value;
		}
	}
	public sealed class CoopRuntime
	{
		public readonly Session Session;

		public readonly RemotePlayerManager Ghosts;

		public readonly MonsterSyncHost MonsterHost;

		public readonly MonsterSyncClient MonsterClient;

		public readonly MonsterAuthority Authority;

		public readonly EnemyHealthScaling HealthScaling;

		public readonly LocalPlayerSampler Sampler = new LocalPlayerSampler();

		public readonly MonsterIndex MonsterIndex = new MonsterIndex();

		public readonly CoopUI UI;

		public readonly MenuInputFocus MenuFocus;

		public readonly BossRewardSync Rewards;

		public readonly PartyLife Life;

		public readonly MonsterThreat Threat;

		public readonly SharedWorld World;

		public readonly DevCommands Dev;

		private readonly KeyEdge _keys = new KeyEdge();

		private int _levelId = -1;

		private bool _loading;

		private float _nextStateSend;

		private float _nextEquipCheck;

		private PlayerEquipMsg _lastEquip;

		private bool _equipSent;

		private float _lastErrorAt;

		private int _errorBurst;

		private RemotePlayer _fakeGhost;

		private Peer _fakePeer;

		private float _nextFakeSend;

		private readonly PlayerStateMsg _fakeState = new PlayerStateMsg();

		public bool MenuVisible;

		private int _frames;

		private readonly SessionPerformance _performance = new SessionPerformance();

		private int _hostAttempts;

		private float _nextHostAttempt;

		private bool _firstGui = true;

		private int _guiCalls;

		private float _nextAutoContinue;

		private int _autoContinueAttempts;

		private bool _autoStarted;

		private float _autoStartAt = -1f;

		private float _autoRetryAt;

		private int _autoRetries;

		private bool _steamLaunchJoinChecked;

		private Action _frameAction;

		private float _frameActionUntil;

		private Action _delayedAction;

		private float _delayedAt;

		public static CoopRuntime Instance { get; private set; }

		public int LevelId => _levelId;

		public bool Loading => _loading;

		public string LastError { get; private set; } = "";

		public bool GameActive { get; private set; }

		public bool OverlayAvailable => OverlayHost.IsAlive;

		public bool FakeGhostActive => _fakeGhost != null;

		public static void Create()
		{
			Instance = new CoopRuntime();
			HitRelay.Runtime = Instance;
		}

		private CoopRuntime()
		{
			Log.Trace("runtime ctor: begin");
			Session = new Session(new SteamBanList(Path.Combine(Paths.ConfigPath, "com.ailimit.coop.bans" + InstanceProfile.FileSuffix + ".json")));
			Session.Diagnostics.SummaryInterval = CoopConfig.NetworkDiagnosticsInterval.Value;
			Ghosts = new RemotePlayerManager(Session);
			Authority = new MonsterAuthority(Session);
			HealthScaling = new EnemyHealthScaling(Session, Authority);
			MonsterHost = new MonsterSyncHost(Session, Authority, HealthScaling);
			MonsterClient = new MonsterSyncClient(Session, Authority, HealthScaling, MonsterIndex);
			Log.Trace("runtime ctor: sync objects");
			UI = new CoopUI(this);
			MenuFocus = new MenuInputFocus(this);
			Rewards = new BossRewardSync(this);
			Life = new PartyLife(this);
			Threat = new MonsterThreat(this);
			World = new SharedWorld(this);
			MonsterClient.ActivationTarget = Threat.RecoveryTarget;
			Dev = new DevCommands(this);
			Log.Trace("runtime ctor: ui/dev");
			MenuVisible = CoopConfig.ShowMenuOnStart.Value;
			Session.OnMonsterHit += HitRelay.ApplyMonsterHit;
			Session.OnGhostHit += HitRelay.ApplyGhostHit;
			Session.OnPeerJoined += delegate(Peer p)
			{
				_equipSent = false;
				UI.Notify(p.Name + " joined", $"{p} joined");
			};
			Session.OnPeerLeft += delegate(Peer p)
			{
				UI.Notify(p.Name + " left", $"{p} left");
			};
			Session.OnChat += delegate(Peer p, string text)
			{
				UI.ReceiveChat(p, text);
			};
			Session.OnSessionStarted += delegate
			{
				_equipSent = false;
				HitRelay.ClientSideHitsObserved = false;
				UI.Notify(Session.IsHost ? "Hosting session" : "Joining session...");
			};
			Session.OnSessionEnded += delegate(string reason)
			{
				HitRelay.ClearCaches();
				UI.ClearChat();
				UI.Notify("Session ended: " + reason);
			};
		}

		private void EnsureHost(float now)
		{
			if (!OverlayHost.IsAlive && CoopConfig.EnableOverlay.Value && !(now < _nextHostAttempt) && _hostAttempts < 3)
			{
				_hostAttempts++;
				_nextHostAttempt = now + 5f;
				Log.Trace("host: creating");
				if (OverlayHost.TryCreate())
				{
					Log.Trace("host: ready");
				}
				else
				{
					Log.Trace("host: failed");
				}
			}
		}

		public void Update()
		{
			bool flag = _frames < 3;
			if (flag)
			{
				Log.Trace($"Update {_frames} begin");
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			_frames++;
			_performance.Begin(this, realtimeSinceStartup);
			try
			{
				if (_frames == 1)
				{
					Log.Info("[Coop] first Update");
				}
				if (_frames > 1)
				{
					EnsureHost(realtimeSinceStartup);
				}
				GhostRegistry.Prune(Time.frameCount, realtimeSinceStartup);
				if (flag)
				{
					Log.Trace("Update: keys");
				}
				if (KeyEdge.GameHasFocus())
				{
					HandleKeys();
				}
				MenuFocus.Tick();
				SteamPresence.Tick(realtimeSinceStartup, MenuVisible);
				if (flag)
				{
					Log.Trace("Update: dev poll");
				}
				Dev.Poll(realtimeSinceStartup);
				if (flag)
				{
					Log.Trace("Update: session poll");
				}
				if (GameActive)
				{
					TrackLevel();
				}
				_performance.Mark(CoopWork.Input);
				Session.Poll();
				_performance.Mark(CoopWork.Network);
				CoopMenuPause.Tick(this);
				Life.Tick(realtimeSinceStartup);
				ChestInteractionSafety.Tick(GameActive && !Loading && Session.IsConnected);
				_performance.Mark(CoopWork.Life);
				if (!GameActive)
				{
					_levelId = -1;
					_loading = true;
					AutoContinueTick(realtimeSinceStartup);
					SendState(realtimeSinceStartup);
					if (flag)
					{
						Log.Trace("Update end (inactive)");
					}
					return;
				}
				TrackLevel();
				AutoStart(realtimeSinceStartup);
				PumpFrameAction(realtimeSinceStartup);
				UpdateFakeGhost(realtimeSinceStartup);
				if (Session.IsActive || _fakeGhost != null)
				{
					Sampler.Bind(GameState.LocalPlayer);
					SendState(realtimeSinceStartup);
					_performance.Mark(CoopWork.LocalState);
					World.Tick(realtimeSinceStartup);
					Ghosts.Update(realtimeSinceStartup, _levelId, _loading);
					_performance.Mark(CoopWork.Ghosts);
					MonsterHost.Tick(realtimeSinceStartup, _levelId, _loading);
					_performance.Mark(CoopWork.MonsterSend);
					MonsterClient.Update(realtimeSinceStartup, _levelId);
					_performance.Mark(CoopWork.MonsterReceive);
					Threat.Tick(realtimeSinceStartup);
					_performance.Mark(CoopWork.Threat);
					Rewards.Tick();
					_performance.Mark(CoopWork.Rewards);
				}
			}
			catch (Exception ex)
			{
				ReleaseMenuOnError();
				ReportError("Update", ex);
			}
			finally
			{
				_performance.End(this, realtimeSinceStartup);
			}
		}

		public void OnGUI()
		{
			if (!CoopConfig.EnableOverlay.Value)
			{
				return;
			}
			bool flag = _guiCalls < 3;
			_guiCalls++;
			if (flag)
			{
				Log.Trace($"OnGUI {_guiCalls} begin");
			}
			long stamp = _performance.BeginMenu();
			try
			{
				if (_firstGui)
				{
					_firstGui = false;
					Log.Info("[Coop] first OnGUI");
				}
				bool menuWasVisible = MenuVisible || UI.ChatOpen;
				UI.Draw(flag);
				MenuFocus.AfterGui(menuWasVisible);
			}
			catch (Exception ex)
			{
				ReleaseMenuOnError();
				ReportError("OnGUI", ex);
			}
			finally
			{
				_performance.EndMenu(stamp);
			}
			if (flag)
			{
				Log.Trace($"OnGUI {_guiCalls} end");
			}
		}

		private void ReportError(string where, Exception ex)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			LastError = $"{where}: {ex.GetType().Name}: {ex.Message}";
			if (realtimeSinceStartup - _lastErrorAt > 5f)
			{
				_errorBurst = 0;
			}
			_lastErrorAt = realtimeSinceStartup;
			if (++_errorBurst <= 3)
			{
				Log.Error("[Coop] " + where, ex);
			}
		}

		private void HandleKeys()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			HandleMenuKey();
			HandleChatKey();
			if (!UI.ChatOpen)
			{
				TrySteamLaunchJoin();
				if (_keys.Pressed(CoopConfig.HostKey.Value))
				{
					HostSession();
				}
				if (_keys.Pressed(CoopConfig.JoinKey.Value))
				{
					JoinSession(CoopConfig.JoinAddress.Value);
				}
				if (_keys.Pressed(CoopConfig.LeaveKey.Value))
				{
					LeaveSession();
				}
				if (Life.IsSpectating && _keys.Pressed((KeyCode)290))
				{
					Life.CycleTarget();
				}
			}
		}

		private void HandleMenuKey()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if (_keys.Pressed(CoopConfig.ToggleMenuKey.Value) && !UI.ChatOpen)
			{
				MenuVisible = !MenuVisible;
			}
		}

		private void HandleChatKey()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			KeyCode value = CoopConfig.ChatKey.Value;
			bool num = _keys.Pressed(value);
			UI.TickChat(_keys.Held(value));
			if (num && !UI.ChatOpen && !MenuVisible)
			{
				UI.OpenChat();
			}
		}

		public void BeforeInputUpdate()
		{
			try
			{
				if (KeyEdge.GameHasFocus())
				{
					HandleMenuKey();
					HandleChatKey();
				}
				MenuFocus.Tick();
			}
			catch (Exception ex)
			{
				ReleaseMenuOnError();
				ReportError("Menu input", ex);
			}
		}

		private void ReleaseMenuOnError()
		{
			MenuVisible = false;
			try
			{
				UI.CloseChat(clearDraft: false);
			}
			catch
			{
			}
			try
			{
				MenuFocus.Release();
			}
			catch (Exception ex)
			{
				Log.Warn("[Menu input] restore: " + ex.Message);
			}
		}

		private void TrackLevel()
		{
			int currentLevelId = GameState.CurrentLevelId;
			bool isLoadingLevel = GameState.IsLoadingLevel;
			if (currentLevelId != _levelId)
			{
				Log.Info($"[Game] level changed {_levelId} -> {currentLevelId} ({GameState.CurrentSceneName})");
				HealthScaling.RestoreAll();
				_levelId = currentLevelId;
				MonsterClient.OnLevelChanged(currentLevelId);
				Authority.OnLevelChanged();
				MonsterHost.OnLevelChanged();
				MonsterIndex.Invalidate();
				HitRelay.ClearCaches();
			}
			if (isLoadingLevel != _loading)
			{
				_loading = isLoadingLevel;
				Log.Debug($"[Game] loading = {isLoadingLevel}");
			}
		}

		private void SendState(float now)
		{
			if (!Session.IsConnected)
			{
				return;
			}
			if (now >= _nextStateSend)
			{
				_nextStateSend = now + 1f / UnityMath.Max(1f, CoopConfig.PlayerSendRate.Value);
				PlayerFlags playerFlags = PlayerFlags.None;
				if (_loading || (Object)(object)Sampler.Player == (Object)null)
				{
					playerFlags |= PlayerFlags.Loading;
				}
				if (HitRelay.ClientSideHitsObserved)
				{
					playerFlags |= PlayerFlags.ClientSideHits;
				}
				if (Life.IsSpectating)
				{
					playerFlags |= PlayerFlags.Dead | PlayerFlags.Spectating;
				}
				PlayerStateMsg state = Sampler.Sample(_levelId, playerFlags);
				Session.SendPlayerState(state);
			}
			if (now >= _nextEquipCheck && (Object)(object)Sampler.Player != (Object)null)
			{
				_nextEquipCheck = now + 1f;
				PlayerEquipMsg playerEquipMsg = Sampler.SampleEquip(CoopConfig.EffectivePlayerName);
				if (!_equipSent || !playerEquipMsg.SameAs(in _lastEquip))
				{
					Session.SendPlayerEquip(playerEquipMsg);
					_lastEquip = playerEquipMsg;
					_equipSent = true;
					Log.Debug($"[Coop] sent equipment: weapon {playerEquipMsg.WeaponDefineId} head {playerEquipMsg.HeadId} body {playerEquipMsg.BodyId} acc {playerEquipMsg.AccessoriesId}");
				}
			}
		}

		private void AutoContinueTick(float now)
		{
			if (InstanceProfile.AutoContinue && !GameActive && !(now < _nextAutoContinue))
			{
				_nextAutoContinue = now + 2f;
				if (_autoContinueAttempts < 300)
				{
					_autoContinueAttempts++;
					PressThroughTitle(_autoContinueAttempts <= 3 || _autoContinueAttempts % 15 == 0);
				}
			}
		}

		public bool PressThroughTitle(bool log)
		{
			try
			{
				TitleView val = Il2CppUtil.FindObjectOfType<TitleView>();
				if ((Object)(object)val == (Object)null)
				{
					if (log)
					{
						Log.Debug("[Auto] title screen not ready yet");
					}
					return false;
				}
				try
				{
					InputManager instance = InputManager.Instance;
					if (instance != null)
					{
						instance.UISubmitInput((CallbackContext)null);
					}
				}
				catch (Exception ex)
				{
					if (log)
					{
						Log.Debug("[Auto] UISubmitInput: " + ex.Message);
					}
				}
				try
				{
					val.InvokeButton();
				}
				catch (Exception ex2)
				{
					if (log)
					{
						Log.Debug("[Auto] InvokeButton: " + ex2.Message);
					}
				}
				try
				{
					val.Continue();
				}
				catch (Exception ex3)
				{
					if (log)
					{
						Log.Debug("[Auto] Continue: " + ex3.Message);
					}
				}
				if (log)
				{
					Log.Info($"[Auto] title press + Continue() (attempt {_autoContinueAttempts})");
				}
				return true;
			}
			catch (Exception ex4)
			{
				if (log)
				{
					Log.Debug("[Auto] continue failed: " + ex4.Message);
				}
				return false;
			}
		}

		private void AutoStart(float now)
		{
			bool autoHost = InstanceProfile.AutoHost;
			bool flag = !string.IsNullOrEmpty(InstanceProfile.AutoJoin);
			if (_autoStarted)
			{
				if (flag && !Session.IsActive && Session.AllowAutoJoinRetry && _autoRetries < 30 && now >= _autoRetryAt)
				{
					_autoRetryAt = now + 5f;
					_autoRetries++;
					Log.Msg($"[Auto] retrying join {InstanceProfile.AutoJoin} ({_autoRetries})");
					JoinSession(InstanceProfile.AutoJoin);
				}
			}
			else if (!autoHost && !flag)
			{
				_autoStarted = true;
			}
			else if (_loading || _levelId < 0 || (Object)(object)GameState.LocalPlayer == (Object)null)
			{
				_autoStartAt = -1f;
			}
			else if (_autoStartAt < 0f)
			{
				_autoStartAt = now + 3f;
			}
			else if (!(now < _autoStartAt))
			{
				_autoStarted = true;
				if (autoHost)
				{
					Log.Msg("[Auto] hosting");
					HostSession();
				}
				else
				{
					Log.Msg("[Auto] joining " + InstanceProfile.AutoJoin);
					JoinSession(InstanceProfile.AutoJoin);
				}
			}
		}

		private void TrySteamLaunchJoin()
		{
			if (!_steamLaunchJoinChecked && !Session.IsActive && _levelId >= 0 && !_loading && !((Object)(object)GameState.LocalPlayer == (Object)null))
			{
				_steamLaunchJoinChecked = true;
				ulong num = SteamPresence.PendingLaunchJoin();
				if (num != 0L && num != SteamPresence.LocalSteamId)
				{
					Log.Msg($"[Steam] joining {num}, who invited us before the game started");
					UI.Notify("Joining your friend's session...");
					JoinSteamFriend(num);
				}
			}
		}

		private INetTransport CreateTransport()
		{
			if (CoopConfig.Transport.Value == TransportKind.Steam)
			{
				if (SteamP2PTransport.IsAvailable)
				{
					return new SteamP2PTransport();
				}
				Log.Warn("[Coop] Steam is not initialised; falling back to UDP.");
				UI.Notify("Steam unavailable, using UDP");
			}
			return new UdpTransport();
		}

		public void HostSession()
		{
			LogPrivacy.RegisterName(SteamPresence.LocalName);
			Session.LocalName = CoopConfig.EffectivePlayerName;
			Session.LocalSteamId = SteamP2PTransport.LocalSteamIdOrZero;
			Session.MaxPlayers = CoopConfig.MaxPlayers.Value;
			Session.ConfiguredEnemyHpPerExtraPlayer = CoopConfig.EnemyHpPerExtraPlayer.Value;
			INetTransport netTransport = CreateTransport();
			if (!Session.Host(netTransport, CoopConfig.EffectivePort))
			{
				UI.Notify("Could not start hosting: " + Session.StatusText);
				netTransport.Dispose();
			}
			else if (CoopConfig.Transport.Value == TransportKind.Steam)
			{
				SteamPresence.Advertise();
			}
		}

		public void JoinSteamFriend(ulong hostSteamId)
		{
			if (hostSteamId != 0L)
			{
				if (CoopConfig.Transport.Value != TransportKind.Steam)
				{
					CoopConfig.Transport.Value = TransportKind.Steam;
					Log.Info("[Coop] switched to the Steam transport to join a friend");
				}
				JoinSession(hostSteamId.ToString());
			}
		}

		public void InviteOverSteam()
		{
			if (!Session.IsHost)
			{
				UI.Notify("Host a session first");
				return;
			}
			if (!SteamPresence.IsAvailable)
			{
				UI.Notify("Steam is not available");
				return;
			}
			SteamPresence.Advertise();
			if (SteamPresence.OpenInviteOverlay())
			{
				UI.Notify("Steam invite dialog opened");
			}
			else
			{
				UI.Notify("Could not open the Steam overlay");
			}
		}

		public void JoinSession(string address)
		{
			LogPrivacy.RegisterName(SteamPresence.LocalName);
			Session.LocalName = CoopConfig.EffectivePlayerName;
			Session.LocalSteamId = SteamP2PTransport.LocalSteamIdOrZero;
			INetTransport netTransport = CreateTransport();
			if (!Session.Join(netTransport, address, CoopConfig.EffectivePort))
			{
				UI.Notify("Could not connect: " + Session.StatusText);
				netTransport.Dispose();
			}
		}

		public void LeaveSession()
		{
			SteamPresence.StopAdvertising();
			_autoStarted = true;
			_autoRetries = int.MaxValue;
			if (Session.IsActive)
			{
				Session.Leave();
			}
			else
			{
				UI.Notify("Not in a session");
			}
		}

		public void BeforeMonsterReset()
		{
			Threat.Clear();
			MonsterClient.ReleaseAll();
			HealthScaling.RestoreAll();
			MonsterIndex.Invalidate();
			HitRelay.ClearCaches();
			MonsterHost.OnLevelChanged();
		}

		public void OnRemoteRest(int levelId)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			if (!Session.IsHost || !Session.IsConnected)
			{
				return;
			}
			if (GameState.CurrentLevelId == levelId && GameActive && !Loading)
			{
				LevelRoot currentLevelRoot = GameState.CurrentLevelRoot;
				if (currentLevelRoot != null)
				{
					currentLevelRoot.ResetMonster();
				}
				OnLocalSceneReset(isDead: false);
			}
			else
			{
				Session.SendSceneReset(new SceneResetMsg
				{
					LevelId = levelId,
					IsDead = false
				});
			}
		}

		public void OnLocalSceneReset(bool isDead)
		{
			Threat.Clear();
			MonsterIndex.Invalidate();
			HitRelay.ClearCaches();
			MonsterHost.OnLevelChanged();
			if (Session.IsHost && Session.IsConnected)
			{
				MonsterClient.ReleaseAll();
				Authority.Clear();
				Log.Msg("[Game] telling clients to reset their monsters (host " + (isDead ? "died" : "rested") + ")");
				Session.SendSceneReset(new SceneResetMsg
				{
					LevelId = _levelId,
					IsDead = isDead
				});
			}
		}

		public void OnGameLoadNewScene()
		{
			World.Clear();
			HealthScaling.RestoreAll();
			Log.Info("[Game] ProcedureManager.OnLoadNewScene");
			MonsterIndex.Invalidate();
			HitRelay.ClearCaches();
		}

		public void OnGameStart()
		{
			Log.Info("[Game] ProcedureManager.StartGameInitAllLogic -> game active");
			GameActive = true;
			_levelId = -1;
		}

		public void OnGameClose()
		{
			World.Clear();
			HealthScaling.RestoreAll();
			Log.Info("[Game] ProcedureManager.CloseAllLogic -> game inactive");
			GameActive = false;
			Life.OnGameClose();
			Threat.Clear();
			Rewards.OnGameClose();
			_levelId = -1;
			_loading = true;
			MonsterIndex.Invalidate();
			HitRelay.ClearCaches();
			MonsterHost.OnLevelChanged();
			MonsterClient.ReleaseAll();
			Authority.Clear();
			Ghosts.DespawnAll();
			Sampler.Bind(null);
			if (_fakeGhost != null)
			{
				ToggleFakeGhost();
			}
		}

		public void RunEachFrame(float seconds, Action action)
		{
			_frameAction = action;
			_frameActionUntil = Time.realtimeSinceStartup + seconds;
		}

		public void RunAfter(float seconds, Action action)
		{
			_delayedAction = action;
			_delayedAt = Time.realtimeSinceStartup + seconds;
		}

		private void PumpFrameAction(float now)
		{
			if (_delayedAction != null && now >= _delayedAt)
			{
				Action delayedAction = _delayedAction;
				_delayedAction = null;
				try
				{
					delayedAction();
				}
				catch (Exception ex)
				{
					Log.Warn("[Dev] delayed action: " + ex.Message);
				}
			}
			if (_frameAction == null)
			{
				return;
			}
			if (now > _frameActionUntil)
			{
				_frameAction = null;
				return;
			}
			try
			{
				_frameAction();
			}
			catch (Exception ex2)
			{
				Log.Warn("[Dev] frame action: " + ex2.Message);
				_frameAction = null;
			}
		}

		public void ToggleFakeGhost()
		{
			if (_fakeGhost != null)
			{
				try
				{
					_fakeGhost.Despawn();
				}
				catch (Exception ex)
				{
					Log.Error("[Fake] despawn", ex);
				}
				_fakeGhost = null;
				_fakePeer = null;
				Log.Msg("[Fake] fake ghost removed");
			}
			else
			{
				_fakePeer = new Peer
				{
					Id = 250,
					Name = "FakeGhost",
					HandshakeDone = true
				};
				_fakeGhost = new RemotePlayer(_fakePeer);
				Log.Msg("[Fake] fake ghost enabled (mirrors the local player 2m to the side)");
			}
		}

		private void UpdateFakeGhost(float now)
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			if (_fakeGhost == null)
			{
				return;
			}
			Player localPlayer = GameState.LocalPlayer;
			Sampler.Bind(localPlayer);
			if ((Object)(object)localPlayer != (Object)null && now >= _nextFakeSend)
			{
				_nextFakeSend = now + 0.05f;
				PlayerStateMsg o = Sampler.Sample(_levelId, PlayerFlags.None);
				_fakeState.CopyFrom(o);
				Transform val = GameState.SafeRoot(localPlayer);
				Vector3 val2 = (((Object)(object)val != (Object)null) ? val.right : Vector3.right);
				_fakeState.X += val2.x * 2f;
				_fakeState.Z += val2.z * 2f;
				_fakeState.PeerId = 250;
				_fakeGhost.OnState(_fakeState, now);
				if (!_fakeGhost.IsSpawned && (Object)(object)Sampler.Player != (Object)null)
				{
					_fakeGhost.OnEquip(Sampler.SampleEquip("FakeGhost"));
				}
			}
			_fakeGhost.Update(now, _levelId, _loading, Ghosts.HolderTransform);
		}
	}
	public sealed class MonsterIndex
	{
		private readonly Dictionary<int, Monster> _map = new Dictionary<int, Monster>();

		private float _builtAt = float.NegativeInfinity;

		private int _levelId = -1;

		private IntPtr _root;

		private const float RefreshSeconds = 2f;

		private const float MissingRetrySeconds = 0.5f;

		public int Count => _map.Count;

		public long RebuildCount { get; private set; }

		public long MissCount { get; private set; }

		public void Invalidate()
		{
			_map.Clear();
			_builtAt = float.NegativeInfinity;
		}

		public Monster Find(int instanceId)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			int currentLevelId = GameState.CurrentLevelId;
			LevelRoot currentLevelRoot = GameState.CurrentLevelRoot;
			IntPtr intPtr = ((currentLevelRoot != null) ? ((Il2CppObjectBase)currentLevelRoot).Pointer : IntPtr.Zero);
			if (currentLevelId != _levelId || intPtr != _root)
			{
				Invalidate();
				_levelId = currentLevelId;
				_root = intPtr;
			}
			Monster value;
			bool flag = _map.TryGetValue(instanceId, out value) && Il2CppUtil.Alive((Object)(object)value);
			float num = realtimeSinceStartup - _builtAt;
			if (num < 0f || num >= (flag ? 2f : 0.5f))
			{
				_builtAt = realtimeSinceStartup;
				RebuildCount++;
				_map.Clear();
				foreach (Monster item in GameState.CollectMonsters(currentLevelRoot))
				{
					if (Il2CppUtil.Alive((Object)(object)item))
					{
						int num2 = GameState.MonsterInstanceId(item);
						if (num2 >= 0)
						{
							_map[num2] = item;
						}
					}
				}
				flag = _map.TryGetValue(instanceId, out value) && Il2CppUtil.Alive((Object)(object)value);
			}
			if (!flag)
			{
				MissCount++;
			}
			if (!flag)
			{
				return null;
			}
			return value;
		}
	}
	public static class InstanceProfile
	{
		public static string Name = "";

		public static string PlayerName;

		public static bool ReadOnlySaves;

		public static int Port = -1;

		public static bool AutoContinue;

		public static bool AutoHost;

		public static string AutoJoin;

		public static string FileSuffix
		{
			get
			{
				if (!string.IsNullOrEmpty(Name))
				{
					return "." + Name;
				}
				return "";
			}
		}

		public static bool IsSecondary => !string.IsNullOrEmpty(Name);

		public static void Init()
		{
			Name = Clean(Get("AILIMITCOOP_PROFILE"));
			PlayerName = Get("AILIMITCOOP_NAME");
			ReadOnlySaves = Flag("AILIMITCOOP_READONLY_SAVE");
			AutoContinue = Flag("AILIMITCOOP_AUTOCONTINUE");
			AutoHost = Flag("AILIMITCOOP_AUTOHOST");
			AutoJoin = Get("AILIMITCOOP_AUTOJOIN");
			string text = Get("AILIMITCOOP_PORT");
			if (!string.IsNullOrEmpty(text) && int.TryParse(text, out var result))
			{
				Port = result;
			}
		}

		public static string Describe()
		{
			if (IsSecondary || ReadOnlySaves || AutoContinue || AutoHost || !string.IsNullOrEmpty(AutoJoin))
			{
				return $"profile='{Name}' name='{PlayerName}' readOnlySaves={ReadOnlySaves} autoContinue={AutoContinue} autoHost={AutoHost} autoJoin='{AutoJoin}' port={((Port > 0) ? Port.ToString() : "config")}";
			}
			return "";
		}

		private static string Get(string key)
		{
			try
			{
				return Environment.GetEnvironmentVariable(key)?.Trim();
			}
			catch
			{
				return null;
			}
		}

		private static bool Flag(string key)
		{
			string text = Get(key);
			if (!string.IsNullOrEmpty(text) && text != "0")
			{
				return !text.Equals("false", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static string Clean(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			char[] array = s.ToCharArray();
			for (int i = 0; i < array.Length; i++)
			{
				if (!char.IsLetterOrDigit(array[i]) && array[i] != '_' && array[i] != '-')
				{
					array[i] = '_';
				}
			}
			return new string(array);
		}
	}
	[BepInPlugin("com.ailimit.coop", "AI LIMIT Co-op", "0.2.4")]
	public class Plugin : BasePlugin
	{
		public const string Guid = "com.ailimit.coop";

		public const string Name = "AI LIMIT Co-op";

		public const string Version = "0.2.4";

		private static WillRenderCanvases _frameAction;

		private static Action _beforeInput;

		public override void Load()
		{
			//IL_026e: Unknown result type (might be due to invalid IL or missing references)
			//IL_028b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
			InstanceProfile.Init();
			Log.Init(((BasePlugin)this).Log, Path.Combine(Paths.BepInExRootPath, "coop_trace" + InstanceProfile.FileSuffix + ".txt"));
			ManagedExceptionCounter.Install();
			((BasePlugin)this).Config.SaveOnConfigSet = false;
			CoopConfig.Bind(((BasePlugin)this).Config);
			((BasePlugin)this).Config.SaveOnConfigSet = !InstanceProfile.IsSecondary;
			if (((BasePlugin)this).Config.SaveOnConfigSet)
			{
				try
				{
					((BasePlugin)this).Config.Save();
				}
				catch (IOException ex)
				{
					Log.Warn("Config is busy; using loaded settings: " + ex.Message);
				}
			}
			Log.Verbose = CoopConfig.Verbose.Value;
			CoopConfig.Verbose.SettingChanged += delegate
			{
				Log.Verbose = CoopConfig.Verbose.Value;
			};
			Log.Msg($"{"AI LIMIT Co-op"} v{"0.2.4"} loading (transport {CoopConfig.Transport.Value}, port {CoopConfig.EffectivePort})");
			Log.Info($"[Net] Protocol {5}, monster message budget {1100} bytes; send acceptance is local only.");
			Log.Info("[Party life] Death spectating enabled; rest or boss rewards revive, party wipe resumes native respawn.");
			Log.Info("[MonsterSync] Proximity simulation ownership with combat locks and independent multiplayer threat.");
			string text = InstanceProfile.Describe();
			if (text.Length > 0)
			{
				Log.Msg("Instance profile: " + text);
			}
			Log.Trace("config bound");
			if (CoopConfig.ForceRunInBackground.Value || InstanceProfile.IsSecondary)
			{
				try
				{
					Application.runInBackground = true;
					Log.Trace("runInBackground forced");
				}
				catch (Exception ex2)
				{
					Log.Warn("Could not set runInBackground: " + ex2.Message);
				}
			}
			CoopRuntime.Create();
			Log.Trace("runtime created");
			try
			{
				Hooks.Apply();
				Log.Trace("hooks applied");
			}
			catch (Exception ex3)
			{
				Log.Error("Failed to apply Harmony patches", ex3);
			}
			RegisterFrameDriver();
			RegisterEarlyInput();
			Log.Msg($"{"AI LIMIT Co-op"} loaded. Keys: {CoopConfig.ToggleMenuKey.Value} menu, {CoopConfig.HostKey.Value} host, {CoopConfig.JoinKey.Value} join, {CoopConfig.LeaveKey.Value} leave.");
		}

		private static void RegisterEarlyInput()
		{
			try
			{
				_beforeInput = DelegateSupport.ConvertDelegate<Action>((Delegate)(Action)delegate
				{
					CoopRuntime.Instance?.BeforeInputUpdate();
				});
				InputSystem.onBeforeUpdate += _beforeInput;
				MenuInputFocus.EarlyInputRegistered = true;
				Log.Info("[Menu input] " + MenuInputFocus.Readiness);
			}
			catch (Exception ex)
			{
				Log.Error("Could not register menu input capture", ex);
			}
		}

		private static void RegisterFrameDriver()
		{
			try
			{
				_frameAction = DelegateSupport.ConvertDelegate<WillRenderCanvases>((Delegate)new Action(Hooks.FrameHooks.Tick));
				Canvas.willRenderCanvases += _frameAction;
				Log.Trace("frame driver registered (Canvas.willRenderCanvases)");
			}
			catch (Exception ex)
			{
				Log.Error("Failed to register the per-frame callback; the mod cannot run", ex);
			}
		}
	}
}
namespace AILimitCoop.Util
{
	internal enum CoopWork
	{
		Input,
		Network,
		Life,
		LocalState,
		Ghosts,
		MonsterSend,
		MonsterReceive,
		Threat,
		Rewards,
		Other,
		Menu,
		FrameGap,
		Count
	}
	internal sealed class FrameTimingWindow
	{
		private readonly double[] _sum = new double[12];

		private readonly double[] _max = new double[12];

		private readonly int[] _count = new int[12];

		public int Count(CoopWork stage)
		{
			return _count[(int)stage];
		}

		public double Mean(CoopWork stage)
		{
			if (Count(stage) != 0)
			{
				return _sum[(int)stage] / (double)Count(stage);
			}
			return 0.0;
		}

		public double Max(CoopWork stage)
		{
			return _max[(int)stage];
		}

		public void Record(CoopWork stage, double milliseconds)
		{
			if (double.IsFinite(milliseconds) && !(milliseconds < 0.0) && stage >= CoopWork.Input && stage < CoopWork.Count)
			{
				_count[(int)stage]++;
				_sum[(int)stage] += milliseconds;
				_max[(int)stage] = Math.Max(_max[(int)stage], milliseconds);
			}
		}

		public void Clear()
		{
			Array.Clear(_sum, 0, _sum.Length);
			Array.Clear(_max, 0, _max.Length);
			Array.Clear(_count, 0, _count.Length);
		}

		public string Describe()
		{
			StringBuilder stringBuilder = new StringBuilder("stage ms mean/max (calls):");
			for (int i = 0; i < _count.Length; i++)
			{
				if (_count[i] != 0)
				{
					stringBuilder.Append(' ').Append((CoopWork)i).Append('=')
						.Append(Mean((CoopWork)i).ToString("F2", CultureInfo.InvariantCulture))
						.Append('/')
						.Append(_max[i].ToString("F2", CultureInfo.InvariantCulture))
						.Append('(')
						.Append(_count[i])
						.Append(')');
				}
			}
			return stringBuilder.ToString();
		}
	}
	public static class Il2CppUtil
	{
		public static Object StringKey(string s)
		{
			return (Object)(object)String.op_Implicit(s);
		}

		public static bool Alive(Object o)
		{
			try
			{
				if (o == null || ((Il2CppObjectBase)o).Pointer == IntPtr.Zero)
				{
					return false;
				}
				return o.m_CachedPtr != IntPtr.Zero || o != (Object)null;
			}
			catch
			{
				return false;
			}
		}

		public static Vector3 MakeVec3(float x, float y, float z)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3
			{
				x = x,
				y = y,
				z = z
			};
		}

		public static Rect MakeRect(float x, float y, float width, float height)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			return new Rect
			{
				m_XMin = x,
				m_YMin = y,
				m_Width = width,
				m_Height = height
			};
		}

		public static Color MakeColor(float r, float g, float b, float a)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			return new Color
			{
				r = r,
				g = g,
				b = b,
				a = a
			};
		}

		public static Quaternion Yaw(float degrees)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			double num = (double)degrees * (Math.PI / 360.0);
			return new Quaternion
			{
				y = (float)Math.Sin(num),
				w = (float)Math.Cos(num)
			};
		}

		public static float SqrDistance(Vector3 a, Vector3 b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: 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_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			float num = a.x - b.x;
			float num2 = a.y - b.y;
			float num3 = a.z - b.z;
			return num * num + num2 * num2 + num3 * num3;
		}

		public static T FindObjectOfType<T>() where T : Object
		{
			try
			{
				Object val = Object.FindObjectOfType(Il2CppType.Of<T>());
				return (val == (Object)null) ? default(T) : ((Il2CppObjectBase)val).TryCast<T>();
			}
			catch (Exception ex)
			{
				Log.Debug("FindObjectOfType<" + typeof(T).Name + "> failed: " + ex.Message);
				return default(T);
			}
		}

		public static IEnumerable<T> Each<T>(List<T> list)
		{
			if (list == null)
			{
				yield break;
			}
			int count;
			try
			{
				count = list.Count;
			}
			catch
			{
				yield break;
			}
			for (int i = 0; i < count; i++)
			{
				T val;
				try
				{
					val = list[i];
				}
				catch
				{
					break;
				}
				yield return val;
			}
		}

		public static IEnumerable<T> Each<T>(Il2CppArrayBase<T> array)
		{
			if (array != null)
			{
				int count = array.Length;
				for (int i = 0; i < count; i++)
				{
					yield return array[i];
				}
			}
		}

		public static Il2CppStructArray<byte> ToIl2CppBytes(byte[] data, int length)
		{
			Il2CppStructArray<byte> val = new Il2CppStructArray<byte>((long)length);
			for (int i = 0; i < length; i++)
			{
				((Il2CppArrayBase<byte>)(object)val)[i] = data[i];
			}
			return val;
		}

		public static int StableHash(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return 0;
			}
			uint num = 2166136261u;
			foreach (char c in s)
			{
				num ^= c;
				num *= 16777619;
			}
			return (int)num;
		}

		public static string DescribeHierarchy(GameObject root, int maxDepth = 2, int maxChildren = 40)
		{
			StringBuilder stringBuilder = new StringBuilder();
			Describe(root.transform, 0, maxDepth, maxChildren, stringBuilder);
			return stringBuilder.ToString();
		}

		private static void Describe(Transform t, int depth, int maxDepth, int maxChildren, StringBuilder sb)
		{
			sb.Append(' ', depth * 2).Append(((Object)t).name).Append(((Component)t).gameObject.activeSelf ? "" : " (inactive)");
			try
			{
				Il2CppArrayBase<Component> components = ((Component)t).GetComponents<Component>();
				List<string> list = new List<string>();
				foreach (Component item in Each<Component>(components))
				{
					if ((Object)(object)item == (Object)null)
					{
						continue;
					}
					string text = ((MemberInfo)((Object)item).GetIl2CppType()).Name;
					if (!(text == "Transform"))
					{
						Behaviour val = ((Il2CppObjectBase)item).TryCast<Behaviour>();
						if ((Object)(object)val != (Object)null && !val.enabled)
						{
							text += "(off)";
						}
						list.Add(text);
					}
				}
				if (list.Count > 0)
				{
					sb.Append("  [").Append(string.Join(", ", list)).Append(']');
				}
			}
			catch (Exception ex)
			{
				sb.Append("  <components failed: ").Append(ex.Message).Append('>');
			}
			sb.AppendLine();
			if (depth < maxDepth)
			{
				int childCount = t.childCount;
				for (int i = 0; i < childCount && i < maxChildren; i++)
				{
					Describe(t.GetChild(i), depth + 1, maxDepth, maxChildren, sb);
				}
				if (childCount > maxChildren)
				{
					sb.Append(' ', (depth + 1) * 2).Append("... ").Append(childCount - maxChildren)
						.AppendLine(" more");
				}
			}
		}

		public static string Vec(Vector3 v)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			return $"({v.x:F2}, {v.y:F2}, {v.z:F2})";
		}
	}
	public static class Log
	{
		private static ManualLogSource _source;

		public static bool Verbose;

		private static string _tracePath;

		public static void Init(ManualLogSource source, string tracePath = null)
		{
			_source = source;
			_tracePath = tracePath;
			if (_tracePath != null)
			{
				try
				{
					File.WriteAllText(_tracePath, $"{DateTime.Now:HH:mm:ss.fff} trace start\n");
				}
				catch
				{
					_tracePath = null;
				}
			}
		}

		public static void Trace(string msg)
		{
			if (_tracePath == null)
			{
				return;
			}
			try
			{
				File.AppendAllText(_tracePath, $"{DateTime.Now:HH:mm:ss.fff} {LogPrivacy.Redact(msg)}\n");
			}
			catch
			{
			}
		}

		public static void Info(string msg)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogInfo((object)LogPrivacy.Redact(msg));
			}
		}

		public static void Msg(string msg)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogMessage((object)LogPrivacy.Redact(msg));
			}
		}

		public static void Warn(string msg)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogWarning((object)LogPrivacy.Redact(msg));
			}
		}

		public static void Error(string msg)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogError((object)LogPrivacy.Redact(msg));
			}
		}

		public static void Error(string context, Exception ex)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogError((object)LogPrivacy.Redact($"{context}: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}"));
			}
		}

		public static void Debug(string msg)
		{
			if (Verbose)
			{
				ManualLogSource source = _source;
				if (source != null)
				{
					source.LogInfo((object)("[dbg] " + LogPrivacy.Redact(msg)));
				}
			}
		}
	}
	public static class LogPrivacy
	{
		private const int MaxLabelsPerKind = 256;

		private const int MaxNameLength = 256;

		private const RegexOptions Options = RegexOptions.Compiled | RegexOptions.CultureInvariant;

		private static readonly Regex SteamIds;

		private static readonly Regex Ipv4;

		private static readonly Regex Ipv6;

		private static readonly Regex AccountFolder;

		private static readonly Regex Placeholder;

		private static readonly HashSet<string> LogWords;

		private static readonly object Gate;

		private static readonly Dictionary<string, string> SteamLabels;

		private static readonly Dictionary<string, string> IpLabels;

		private static readonly Dictionary<string, string> NameLabels;

		private static Regex _names;

		private static Regex _userName;

		private static Regex _machineName;

		static LogPrivacy()
		{
			SteamIds = new Regex("(?<!\\d)7656119\\d{10}(?!\\d)", RegexOptions.Compiled | RegexOptions.CultureInvariant);
			Ipv4 = new Regex("(?<![\\w.])(?<!Version=)(?:\\d{1,3}\\.){3}\\d{1,3}(?!\\w|\\.\\d)", RegexOptions.Compiled | RegexOptions.CultureInvariant);
			Ipv6 = new Regex("(?<![\\w:.])(?:[0-9A-Fa-f]{0,4}:){2,7}[0-9A-Fa-f]{0,4}(?![\\w:])", RegexOptions.Compiled | RegexOptions.CultureInvariant);
			AccountFolder = new Regex("((?:[A-Za-z]:[\\\\/]+Users|(?:^|[\\\\/])home)[\\\\/]+)(?:[^\\\\/\\r\\n\"'<>|:*?]{1,64}?(?=[\\\\/])|[^\\s\\\\/\"'<>|:*?;,()\\[\\]]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant);
			Placeholder = new Regex("^(?:Player\\d*|steam-user-\\d+|ip-\\d+|name-\\d+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant);
			LogWords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
			{
				"host", "client", "peer", "player", "ghost", "session", "steam", "udp", "user", "name",
				"level", "monster", "monstersync", "boss", "game", "coop", "party", "life", "menu", "input",
				"chat", "local", "unknown", "test", "hit", "threat", "rewards", "overlay", "hooks", "interaction",
				"perf", "net", "send", "types", "auto", "fake", "dev", "dbg", "trace", "moderation",
				"combat", "safety", "joined", "left", "username", "computername"
			};
			Gate = new object();
			SteamLabels = new Dictionary<string, string>(StringComparer.Ordinal);
			IpLabels = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			NameLabels = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			string userName = null;
			string machineName = null;
			try
			{
				userName = Environment.UserName;
			}
			catch
			{
			}
			try
			{
				machineName = Environment.MachineName;
			}
			catch
			{
			}
			UseEnvironment(userName, machineName);
		}

		public static void UseEnvironment(string userName, string machineName)
		{
			_userName = (IsHideable(userName) ? WholeWords(new string[1] { userName.Trim() }) : null);
			_machineName = (IsHideable(machineName) ? WholeWords(new string[1] { machineName.Trim() }) : null);
		}

		public static void RegisterName(string name)
		{
			if (!IsHideable(name))
			{
				return;
			}
			name = name.Trim();
			lock (Gate)
			{
				if (!NameLabels.ContainsKey(name) && NameLabels.Count < 256)
				{
					NameLabels[name] = "name-" + (NameLabels.Count + 1);
					_names = WholeWords(NameLabels.Keys);
				}
			}
		}

		public static void Reset()
		{
			lock (Gate)
			{
				SteamLabels.Clear();
				IpLabels.Clear();
				NameLabels.Clear();
				_names = null;
			}
		}

		public static string Redact(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return text;
			}
			text = AccountFolder.Replace(text, "$1%USERNAME%");
			text = SteamIds.Replace(text, (Match m) => Label(SteamLabels, m.Value, "steam-user"));
			text = Ipv4.Replace(text, (Match m) => (!IsHiddenIpv4(m.Value)) ? m.Value : Label(IpLabels, m.Value, "ip"));
			text = Ipv6.Replace(text, (Match m) => (!LooksLikeIpv6(m.Value)) ? m.Value : Label(IpLabels, m.Value, "ip"));
			Regex names = _names;
			Regex userName = _userName;
			Regex machineName = _machineName;
			if (names != null)
			{
				text = names.Replace(text, delegate(Match m)
				{
					lock (Gate)
					{
						string value;
						return NameLabels.TryGetValue(m.Value, out value) ? value : "name";
					}
				});
			}
			if (machineName != null)
			{
				text = machineName.Replace(text, "%COMPUTERNAME%");
			}
			if (userName != null)
			{
				text = userName.Replace(text, "%USERNAME%");
			}
			return text;
		}

		private static string Label(Dictionary<string, string> labels, string value, string kind)
		{
			lock (Gate)
			{
				if (labels.TryGetValue(value, out var value2))
				{
					return value2;
				}
				if (labels.Count >= 256)
				{
					return kind;
				}
				return labels[value] = kind + "-" + (labels.Count + 1);
			}
		}

		private static bool IsHiddenIpv4(string text)
		{
			string[] array = text.Split('.');
			int[] array2 = new int[4];
			for (int i = 0; i < 4; i++)
			{
				if (!int.TryParse(array[i], out array2[i]) || array2[i] > 255)
				{
					return false;
				}
			}
			if (array2[0] != 127)
			{
				return (array2[0] | array2[1] | array2[2] | array2[3]) != 0;
			}
			return false;
		}

		private static bool LooksLikeIpv6(string text)
		{
			if (text == "::" || text == "::1" || !text.Any(Uri.IsHexDigit))
			{
				return false;
			}
			if (!text.Contains("::"))
			{
				return text.Count((char c) => c == ':') >= 5;
			}
			return true;
		}

		private static bool IsHideable(string word)
		{
			word = word?.Trim();
			if (!string.IsNullOrEmpty(word) && word.Length >= 3 && word.Length <= 256 && word.Any(char.IsLetter) && !LogWords.Contains(word))
			{
				return !Placeholder.IsMatch(word);
			}
			return false;
		}

		private static Regex WholeWords(IEnumerable<string> words)
		{
			return new Regex("(?<![\\p{L}\\p{N}_])(?:" + string.Join("|", words.OrderByDescending((string w) => w.Length).Select(Regex.Escape)) + ")(?![\\p{L}\\p{N}_])", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
		}
	}
	internal static class ManagedExceptionCounter
	{
		private static long _count;

		private static int _installed;

		public static long Count => Interlocked.Read(in _count);

		public static void Install()
		{
			if (Interlocked.Exchange(ref _installed, 1) == 0)
			{
				AppDomain.CurrentDomain.FirstChanceException += delegate
				{
					Interlocked.Increment(ref _count);
				};
			}
		}
	}
	internal sealed class SessionPerformance
	{
		private readonly FrameTimingWindow _window = new FrameTimingWindow();

		private readonly int[] _gc = new int[3];

		private bool _active;

		private long _stamp;

		private long _lastFrame;

		private long _allocated;

		private long _scans;

		private long _misses;

		private long _scanFaults;

		private long _executeSkips;

		private long _exceptions;

		private float _windowStart;

		private static double Milliseconds(long ticks)
		{
			return (double)ticks * (1000.0 / (double)Stopwatch.Frequency);
		}

		public void Begin(CoopRuntime rt, float now)
		{
			bool flag = rt.GameActive && rt.Session.IsConnected;
			if (flag != _active)
			{
				_active = flag;
				_window.Clear();
				_lastFrame = 0L;
				ResetBaseline(rt, now);
			}
			if (_active)
			{
				_stamp = Stopwatch.GetTimestamp();
				if (_lastFrame != 0L)
				{
					_window.Record(CoopWork.FrameGap, Milliseconds(_stamp - _lastFrame));
				}
				_lastFrame = _stamp;
			}
		}

		public void Mark(CoopWork stage)
		{
			if (_active)
			{
				long timestamp = Stopwatch.GetTimestamp();
				_window.Record(stage, Milliseconds(timestamp - _stamp));
				_stamp = timestamp;
			}
		}

		public long BeginMenu()
		{
			if (!_active)
			{
				return 0L;
			}
			return Stopwatch.GetTimestamp();
		}

		public void EndMenu(long stamp)
		{
			if (stamp != 0L && _active)
			{
				_window.Record(CoopWork.Menu, Milliseconds(Stopwatch.GetTimestamp() - stamp));
			}
		}

		private void ResetBaseline(CoopRuntime rt, float now)
		{
			_windowStart = now;
			_allocated = GC.GetAllocatedBytesForCurrentThread();
			for (int i = 0; i < _gc.Length; i++)
			{
				_gc[i] = GC.CollectionCount(i);
			}
			_scans = rt.MonsterIndex.RebuildCount;
			_misses = rt.MonsterIndex.MissCount;
			_scanFaults = Hooks.ExecuteScanFaults;
			_executeSkips = Hooks.GhostExecuteSkips;
			_exceptions = ManagedExceptionCounter.Count;
		}

		public void End(CoopRuntime rt, float now)
		{
			if (!_active)
			{
				return;
			}
			Mark(CoopWork.Other);
			if (now - _windowStart < 30f)
			{
				return;
			}
			try
			{
				double num = now - _windowStart;
				double value = (double)Math.Max(0L, GC.GetAllocatedBytesForCurrentThread() - _allocated) / 1048576.0 / num;
				string value2 = "unavailable";
				try
				{
					using Process process = Process.GetCurrentProcess();
					value2 = ((double)process.WorkingSet64 / 1048576.0).ToString("F0", CultureInfo.InvariantCulture);
				}
				catch
				{
				}
				Log.Info($"[Perf] windowSec={num:F1} level={rt.LevelId} loading={rt.Loading} menu={rt.MenuVisible} managedMiB={(double)GC.GetTotalMemory(forceFullCollection: false) / 1048576.0:F1} residentMiB={value2} mainThreadManagedAllocMiBps={value:F2} gc0/1/2={GC.CollectionCount(0) - _gc[0]}/{GC.CollectionCount(1) - _gc[1]}/{GC.CollectionCount(2) - _gc[2]} ghosts={rt.Ghosts.Count} retiredGhostPointers={GhostRegistry.ZombieCount} ghostExecuteSkips={Hooks.GhostExecuteSkips - _executeSkips} executeScanFaults={Hooks.ExecuteScanFaults - _scanFaults} managedExceptions={ManagedExceptionCounter.Count - _exceptions} replicas={rt.MonsterClient.ControlledCount} indexActors={rt.MonsterIndex.Count} indexScans={rt.MonsterIndex.RebuildCount - _scans} missingLookups={rt.MonsterIndex.MissCount - _misses} safetyTotalsLateMachines/Enemy/Camera={GhostMachineSafety.LateMachines}/{NativeCombatSafety.InvalidEnemyUpdates}/{NativeCombatSafety.InvalidCameraTargets}");
				Log.Info("[Perf] " + _window.Describe());
			}
			catch (Exception ex)
			{
				Log.Debug("[Perf] summary unavailable: " + ex.Message);
			}
			finally
			{
				_window.Clear();
				ResetBaseline(rt, now);
			}
		}
	}
	public static class UnityMath
	{
		public static float Max(float a, float b)
		{
			if (!(a > b))
			{
				return b;
			}
			return a;
		}

		public static float Min(float a, float b)
		{
			if (!(a < b))
			{
				return b;
			}
			return a;
		}

		public static float Abs(float value)
		{
			return Math.Abs(value);
		}

		public static float Sqrt(float value)
		{
			return (float)Math.Sqrt(value);
		}

		public static float Floor(float value)
		{
			return (float)Math.Floor(value);
		}

		public static float Exp(float power)
		{
			return (float)Math.Exp(power);
		}

		public static float Clamp(float value, float min, float max)
		{
			if (value < min)
			{
				value = min;
			}
			else if (value > max)
			{
				value = max;
			}
			return value;
		}

		public static float Clamp01(float value)
		{
			if (!(value < 0f))
			{
				if (!(value > 1f))
				{
					return value;
				}
				return 1f;
			}
			return 0f;
		}

		public static float Lerp(float a, float b, float t)
		{
			return a + (b - a) * Clamp01(t);
		}

		public static float MoveTowards(float current, float target, float maxDelta)
		{
			if (!(Math.Abs(target - current) <= maxDelta))
			{
				return current + ((target - current >= 0f) ? 1f : (-1f)) * maxDelta;
			}
			return target;
		}

		public static float Repeat(float t, float length)
		{
			return Clamp(t - Floor(t / length) * length, 0f, length);
		}

		public static float LerpAngle(float a, float b, float t)
		{
			float num = Repeat(b - a, 360f);
			if (num > 180f)
			{
				num -= 360f;
			}
			return a + num * Clamp01(t);
		}
	}
}
namespace AILimitCoop.UI
{
	public sealed class ChatLine
	{
		public string Name = "Player";

		public string Text = "";

		public uint Accent = 12297593u;

		public double At;

		public float Opacity = 1f;

		internal float WrapWidth = -1f;

		internal float NameWidth;

		internal string First;

		internal string Second;
	}
	public sealed class ChatLog
	{
		public const int Capacity = 40;

		public const int MaxLength = 96;

		public const int ClosedLines = 5;

		public const int OpenLines = 8;

		public const double VisibleSeconds = 12.0;

		public const double FadeSeconds = 2.0;

		private readonly List<ChatLine> _lines = new List<ChatLine>(40);

		public int Count => _lines.Count;

		public void Add(string name, uint accent, string text, double now)
		{
			text = CleanMessage(text);
			if (text.Length != 0)
			{
				ChatLine chatLine;
				if (_lines.Count == 40)
				{
					chatLine = _lines[0];
					_lines.RemoveAt(0);
				}
				else
				{
					chatLine = new ChatLine();
				}
				chatLine.Name = PartyPresentation.Clean(name, 24);
				chatLine.Text = text;
				chatLine.Accent = accent;
				chatLine.At = now;
				chatLine.Opacity = 1f;
				chatLine.WrapWidth = -1f;
				chatLine.First = (chatLine.Second = null);
				_lines.Add(chatLine);
			}
		}

		public void Clear()
		{
			_lines.Clear();
		}

		public void Visible(List<ChatLine> into, double now, bool open)
		{
			into.Clear();
			for (int i = Math.Max(0, _lines.Count - (open ? 8 : 5)); i < _lines.Count; i++)
			{
				ChatLine chatLine = _lines[i];
				chatLine.Opacity = (open ? 1f : Fade(now - chatLine.At));
				if (chatLine.Opacity > 0f)
				{
					into.Add(chatLine);
				}
			}
		}

		public static float Fade(double age)
		{
			if (!(age >= 12.0))
			{
				if (!(age <= 10.0))
				{
					return (float)((12.0 - age) / 2.0);
				}
				return 1f;
			}
			return 0f;
		}

		public static string CleanMessage(string text, int maxElements = 96)
		{
			if (string.IsNullOrWhiteSpace(text))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(Math.Min(text.Length, maxElements * 2));
			TextElementEnumerator textElementEnumerator = StringInfo.GetTextElementEnumerator(text.Trim());
			int num = 0;
			while (num < maxElements && textElementEnumerator.MoveNext())
			{
				string textElement = textElementEnumerator.GetTextElement();
				if (char.IsControl(textElement, 0))
				{
					if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != ' ')
					{
						stringBuilder.Append(' ');
						num++;
					}
				}
				else
				{
					stringBuilder.Append(textElement);
					num++;
				}
			}
			return stringBuilder.ToString().Trim();
		}
	}
	public sealed class ChatKeyGate
	{
		public bool Armed { get; private set; }

		public void Opened()
		{
			Armed = false;
		}

		public void Closed()
		{
			Armed = false;
		}

		public void Observe(bool keyHeld)
		{
			if (!keyHeld)
			{
				Armed = true;
			}
		}
	}
	public sealed class ChatSendLimiter
	{
		public const double Interval = 0.35;

		private double _last = double.NegativeInfinity;

		public bool TryAcquire(double now)
		{
			if (now - _last < 0.35)
			{
				return false;
			}
			_last = now;
			return true;
		}
	}
	public sealed class CoopUI
	{
		private sealed class PeerView
		{
			public Peer Peer;

			public readonly PartyMemberView View = new PartyMemberView();

			public int DetailKind = -1;

			public int DetailDistance = -1;

			public int DetailPing = -1;

			public float HealthHp = float.NaN;

			public float HealthMax = float.NaN;
		}

		private struct AllyBarPlacement
		{
			public float Distance;

			public float X;

			public float Y;

			public float Width;

			public float Opacity;

			public float NameOpacity;

			public float Health;

			public float Trail;

			public string Name;
		}

		private const float DefaultCharacterHeight = 1.7f;

		private const float AllyBarClearance = 0.2f;

		private const float ChatLeft = 28f;

		private const float ChatBottomMargin = 180f;

		private const string ChatControlName = "AILimitCoopChat";

		private static readonly Color White = Il2CppUtil.MakeColor(1f, 1f, 1f, 1f);

		private readonly CoopRuntime _rt;

		private readonly List<(string text, float until)> _notices = new List<(string, float)>();

		private readonly List<PartyMemberView> _members = new List<PartyMemberView>(16);

		private readonly List<PartyMemberView> _allies = new List<PartyMemberView>(15);

		private readonly Dictionary<byte, PeerView> _peerViews = new Dictionary<byte, PeerView>();

		private readonly List<byte> _departed = new List<byte>();

		private readonly PartyMemberView _local = new PartyMemberView();

		private float _localHealthHp = float.NaN;

		private float _localHealthMax = float.NaN;

		private readonly List<AllyBarPlacement> _allyBars = new List<AllyBarPlacement>(15);

		private readonly ChatLog _chat = new ChatLog();

		private readonly List<ChatLine> _chatLines = new List<ChatLine>(8);

		private readonly ChatKeyGate _chatGate = new ChatKeyGate();

		private readonly ChatSendLimiter _chatLimiter = new ChatSendLimiter();

		private UnityPartyCanvas _canvas;

		private GUIStyle _buttonStyle;

		private GUIStyle _fieldStyle;

		private GUIStyle _chatFieldStyle;

		private string _address;

		private string _chatDraft = "";

		private float _nextViewRefresh;

		private float _lastViewRefresh;

		private float _nextNameRefresh;

		private string _localName = "Player";

		private bool _details;

		private int _partyPage;

		private bool _managePlayers;

		private bool _showBans;

		private int _moderationPage;

		private readonly List<PartyModerationRow> _moderationRows = new List<PartyModerationRow>();

		private readonly List<Peer> _moderationPeers = new List<Peer>();

		private readonly List<ulong> _moderationIds = new List<ulong>();

		private bool _fieldUnavailable;

		private bool _chatFieldUnavailable;

		private float _styleRetryAt;

		private bool _menuPainted;

		private float _measuredHeight;

		private float _nextHeightSample;

		private bool _allyBarErrorLogged;

		private bool _chatErrorLogged;

		private Vector2 _mouse;

		private (bool Active, bool Host, bool Connected, bool Steam, int Rtt) _connectionKey = (Active: false, Host: false, Connected: false, Steam: false, Rtt: int.MinValue);

		private string _connectionText = "OFFLINE";

		private int _menuKeyValue = int.MinValue;

		private string _menuKeyText = "";

		private string _spectatorFor;

		private string _spectatorText = "";

		public bool HasStyles => _canvas != null;

		public bool ChatOpen { get; private set; }

		private static float BaseScale => UnityMath.Clamp((float)Screen.height / 1080f, 0.8f, 3f);

		public bool CanChat
		{
			get
			{
				if (CoopConfig.ShowChat.Value && CoopConfig.EnableOverlay.Value && HasStyles && OverlayHost.IsAlive && _rt.Session.IsConnected && _rt.GameActive && !_rt.Loading && !_rt.MenuVisible)
				{
					return !NativeMenuOpen();
				}
				return false;
			}
		}

		private static float ChatScale => BaseScale * CoopConfig.HudScale.Value;

		public CoopUI(CoopRuntime rt)
		{
			_rt = rt;
		}

		public void Notify(string text)
		{
			Notify(text, text);
		}

		public void Notify(string text, string logText)
		{
			Log.Msg("[UI] " + logText);
			_notices.Add((PartyPresentation.Clean(text, 180), Time.realtimeSinceStartup + 6f));
			if (_notices.Count > 3)
			{
				_notices.RemoveAt(0);
			}
		}

		private bool EnsureStyles()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Expected O, but got Unknown
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			if (_canvas != null)
			{
				return true;
			}
			if (Time.realtimeSinceStartup < _styleRetryAt)
			{
				return false;
			}
			try
			{
				Color textColor = UnityPartyCanvas.ColorOf(15262937u);
				_buttonStyle = new GUIStyle();
				_buttonStyle.richText = false;
				_fieldStyle = new GUIStyle();
				_fieldStyle.fontSize = 13;
				_fieldStyle.alignment = (TextAnchor)3;
				_fieldStyle.clipping = (TextClipping)1;
				_fieldStyle.richText = false;
				_fieldStyle.padding = new RectOffset(10, 10, 0, 0);
				_fieldStyle.normal.textColor = textColor;
				_fieldStyle.focused.textColor = textColor;
				_chatFieldStyle = new GUIStyle();
				_chatFieldStyle.fontSize = 12;
				_chatFieldStyle.alignment = (TextAnchor)3;
				_chatFieldStyle.clipping = (TextClipping)1;
				_chatFieldStyle.richText = false;
				_chatFieldStyle.normal.textColor = textColor;
				_chatFieldStyle.focused.textColor = textColor;
				_chatFieldStyle.hover.textColor = textColor;
				_chatFieldStyle.active.textColor = textColor;
				_canvas = new UnityPartyCanvas();
				return true;
			}
			catch (Exception ex)
			{
				_styleRetryAt = Time.realtimeSinceStartup + 10f;
				Log.Warn("[UI] styles: " + ex.Message);
				return false;
			}
		}

		public void Draw(bool trace = false)
		{
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: 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_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: 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_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if (trace)
			{
				Log.Trace("UI: party styles");
			}
			if (!EnsureStyles())
			{
				return;
			}
			RefreshParty();
			Matrix4x4 matrix = GUI.matrix;
			Color color = GUI.color;
			Color contentColor = GUI.contentColor;
			Color backgroundColor = GUI.backgroundColor;
			bool enabled = GUI.enabled;
			try
			{
				GUI.color = (GUI.contentColor = (GUI.backgroundColor = White));
				GUI.enabled = true;
				_canvas.BeginPass();
				bool repaint = _canvas.Repaint;
				if (!_rt.MenuVisible)
				{
					_menuPainted = false;
				}
				if (repaint)
				{
					DrawAllyBars();
					if (CoopConfig.ShowHud.Value && !_rt.MenuVisible)
					{
						DrawHud();
					}
					if (!_rt.MenuVisible)
					{
						DrawChatFeed();
					}
				}
				if (_rt.MenuVisible)
				{
					bool num = !_menuPainted && repaint;
					if (num)
					{
						Log.Info("[UI] party menu first repaint starting");
					}
					DrawMenu();
					if (num)
					{
						_menuPainted = true;
						Log.Info("[UI] party menu first repaint complete");
					}
				}
				if (ChatOpen)
				{
					DrawChatInput();
				}
				if (repaint)
				{
					if (_rt.Life.IsSpectating && !_rt.MenuVisible)
					{
						DrawSpectator();
					}
					DrawNotices();
				}
			}
			finally
			{
				GUI.matrix = matrix;
				GUI.color = color;
				GUI.contentColor = contentColor;
				GUI.backgroundColor = backgroundColor;
				GUI.enabled = enabled;
			}
		}

		private void RefreshParty()
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ca: Unknown result type (might be due to invalid IL or missing references)
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup < _nextViewRefresh)
			{
				return;
			}
			float dt = UnityMath.Clamp(realtimeSinceStartup - _lastViewRefresh, 0f, 0.3f);
			_lastViewRefresh = realtimeSinceStartup;
			_nextViewRefresh = realtimeSinceStartup + 0.1f;
			Session session = _rt.Session;
			_members.Clear();
			_allies.Clear();
			Player val = ((_rt.GameActive && !GameState.IsLoadingLevel) ? GameState.LocalPlayer : null);
			bool flag = (Object)(object)val != (Object)null;
			Vector3 a = (Vector3)(flag ? GameState.SafePosition(val) : default(Vector3));
			if (realtimeSinceStartup >= _nextNameRefresh)
			{
				_nextNameRefresh = realtimeSinceStartup + 2f;
				_localName = CoopConfig.EffectivePlayerName;
			}
			_local.Name = _localName;
			_local.Role = (session.IsHost ? "YOU / HOST" : "YOU");
			_local.Accent = PartyTheme.Accent(session.IsActive ? session.LocalPeerId : 0);
			_local.Status = (flag ? "READY" : "NOT IN WORLD");
			_local.StatusColor = (flag ? 13225156u : 11184543u);
			_local.Detail = (session.IsHost ? "Leading the party" : (session.IsConnected ? "Connected to host" : "Your character"));
			SetHealth(_local, flag ? GameState.SafeHp(val) : 0f, flag ? GameState.SafeHpMax(val) : 0f, flag, dt, ref _localHealthHp, ref _localHealthMax);
			if (_local.HealthKnown && _local.Health <= 0f)
			{
				_local.Status = "DEAD";
				_local.StatusColor = 13994619u;
			}
			if (_rt.Life.IsSpectating)
			{
				_local.Status = "SPECTATING";
			}
			_members.Add(_local);
			_departed.Clear();
			foreach (KeyValuePair<byte, PeerView> peerView in _peerViews)
			{
				if (session.GetPeer(peerView.Key) != peerView.Value.Peer)
				{
					_departed.Add(peerView.Key);
				}
			}
			foreach (byte item in _departed)
			{
				_peerViews.Remove(item);
			}
			IReadOnlyList<Peer> peers = session.Peers;
			for (int i = 0; i < peers.Count; i++)
			{
				Peer peer = peers[i];
				if (!_peerViews.TryGetValue(peer.Id, out var value))
				{
					value = new PeerView
					{
						Peer = peer
					};
					_peerViews[peer.Id] = value;
				}
				PartyMemberView view = value.View;
				PlayerStateMsg lastState = peer.LastState;
				bool flag2 = peer.LastStateReceivedAt >= 0f && session.Now - peer.LastStateReceivedAt > 3f;
				bool flag3 = lastState == null || (lastState.Flags & PlayerFlags.Loading) != 0;
				bool flag4 = flag && lastState != null && lastState.LevelId == _rt.LevelId;
				bool flag5 = lastState != null && (lastState.Flags & PlayerFlags.Dead) != 0;
				view.Name = peer.Name;
				view.Role = ((peer.Id == 0) ? "HOST" : "ALLY");
				view.Accent = PartyTheme.Accent(peer.Id);
				view.StatusColor = 13225156u;
				if (flag2)
				{
					view.Status = "CONNECTION SLOW";
					view.StatusColor = 12954232u;
				}
				else if (flag3)
				{
					view.Status = "LOADING";
					view.StatusColor = 11184543u;
				}
				else if (flag5)
				{
					view.Status = (((lastState.Flags & PlayerFlags.Spectating) != PlayerFlags.None) ? "SPECTATING" : "DEAD");
					view.StatusColor = 13994619u;
				}
				else if ((lastState.Flags & PlayerFlags.InCombat) != PlayerFlags.None)
				{
					view.Status = "IN COMBAT";
					view.StatusColor = 12954232u;
				}
				else
				{
					view.Status = "EXPLORING";
				}
				int num = ((!flag3) ? ((!flag) ? 1 : ((!flag4) ? 2 : 3)) : 0);
				int num2 = -1;
				if (flag4 && !flag3 && !flag2)
				{
					float num3 = UnityMath.Sqrt(Il2CppUtil.SqrDistance(a, Il2CppUtil.MakeVec3(lastState.X, lastState.Y, lastState.Z)));
					if (float.IsFinite(num3))
					{
						num = 4;
						num2 = (int)UnityMath.Clamp(num3, 0f, 9999f);
					}
				}
				int num4 = (session.IsHost ? peer.RttMs : ((peer.Id == 0) ? session.HostRttMs : (-1)));
				if (num4 <= 0)
				{
					num4 = -1;
				}
				if (num != value.DetailKind || num2 != value.DetailDistance || num4 != value.DetailPing)
				{
					value.DetailKind = num;
					value.DetailDistance = num2;
					value.DetailPing = num4;
					string text = num switch
					{
						0 => "Waiting for player", 
						1 => "In the world", 
						2 => "In another area", 
						4 => num2 + " m away", 
						_ => "In your area", 
					};
					view.Detail = ((num4 > 0) ? (text + "  /  " + num4 + " ms") : text);
				}
				SetHealth(view, lastState?.Hp ?? 0f, lastState?.HpMax ?? 0f, !flag3 && !flag2, dt, ref value.HealthHp, ref value.HealthMax);
				if (view.HealthKnown && view.Health <= 0.25f && !flag5 && !flag2)
				{
					view.Status = "LOW HEALTH";
					view.StatusColor = 13994619u;
				}
				_allies.Add(view);
				_members.Add(view);
			}
		}

		private static void SetHealth(PartyMemberView view, float hp, float max, bool known, float dt, ref float textHp, ref float textMax)
		{
			bool healthKnown = view.HealthKnown;
			view.HealthKnown = known && float.IsFinite(hp) && float.IsFinite(max) && max > 0f;
			view.Health = (view.HealthKnown ? PartyPresentation.Fraction(hp, max) : 0f);
			view.Trail = ((!healthKnown || !view.HealthKnown) ? view.Health : UnityMath.MoveTowards(view.Trail, view.Health, dt * 0.55f));
			view.Trail = UnityMath.Max(view.Trail, view.Health);
			float num = (view.HealthKnown ? Math.Clamp(hp, 0f, max) : float.NaN);
			float num2 = (view.HealthKnown ? max : float.NaN);
			if (!num.Equals(textHp) || !num2.Equals(textMax))
			{
				textHp = num;
				textMax = num2;
				view.HealthText = (view.HealthKnown ? (num.ToString("N0") + " / " + max.ToString("N0")) : "--");
			}
		}

		private string ConnectionText()
		{
			Session session = _rt.Session;
			(bool, bool, bool, bool, int) tuple = (session.IsActive, session.IsHost, session.IsConnected, session.Transport is SteamP2PTransport, (!session.IsHost) ? session.HostRttMs : 0);
			(bool, bool, bool, bool, int) tuple2 = tuple;
			(bool, bool, bool, bool, int) connectionKey = _connectionKey;
			if (tuple2.Item1 == connectionKey.Item1 && tuple2.Item2 == connectionKey.Item2 && tuple2.Item3 == connectionKey.Item3 && tuple2.Item4 == connectionKey.Item4 && tuple2.Item5 == connectionKey.Item5)
			{
				return _connectionText;
			}
			_connectionKey = tuple;
			string text = (tuple.Item4 ? "STEAM" : "DIRECT");
			_connectionText = ((!session.IsActive) ? "OFFLINE" : ((!session.IsHost && !session.IsConnected) ? "CONNECTING" : (session.IsHost ? ("HOSTING  /  " + text) : (text + "  /  " + ((session.HostRttMs > 0) ? (session.HostRttMs + " ms") : "CONNECTED")))));
			return _connectionText;
		}

		private string MenuKeyText()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected I4, but got Unknown
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			int num = (int)CoopConfig.ToggleMenuKey.Value;
			if (num == _menuKeyValue)
			{
				return _menuKeyText;
			}
			_menuKeyValue = num;
			_menuKeyText = ((object)CoopConfig.ToggleMenuKey.Value/*cast due to .constrained prefix*/).ToString();
			return _menuKeyText;
		}

		private static void Scale(float scale)
		{
			//IL_0002: 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)
			GUI.matrix = new Matrix4x4
			{
				m00 = scale,
				m11 = scale,
				m22 = 1f,
				m33 = 1f
			};
		}

		private void DrawHud()
		{
			if (_rt.Session.IsActive)
			{
				float num = PartyPresentation.HudHeight(_allies.Count);
				float num2 = PartyPresentation.HudScale(Screen.width, Screen.height, CoopConfig.HudScale.Value, _allies.Count);
				Scale(num2);
				float num3 = (float)Screen.width / num2;
				float num4 = (float)Screen.height / num2;
				float num5 = 20f;
				PartyHudCorner value = CoopConfig.HudCorner.Value;
				bool flag = value == PartyHudCorner.TopRight || value == PartyHudCorner.BottomRight;
				bool flag2 = value == PartyHudCorner.BottomRight || value == PartyHudCorner.BottomLeft;
				PartyPresentation.Hud(_canvas, flag ? (num3 - 292f - num5) : num5, flag2 ? (num4 - num - num5) : num5, _allies, ConnectionText(), MenuKeyText(), CoopConfig.HudOpacity.Value);
			}
		}

		private void DrawMenu()
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			float b = UnityMath.Min(BaseScale, UnityMath.Min(((float)Screen.width - 32f) / 860f, ((float)Screen.height - 32f) / 682f));
			b = UnityMath.Max(0.25f, b);
			Scale(b);
			_mouse = Event.current.mousePosition;
			float num = (float)Screen.width / b;
			float num2 = (float)Screen.height / b;
			float num3 = (num - 860f) / 2f;
			float num4 = (num2 - 682f) / 2f;
			_canvas.Fill(0f, 0f, num, num2, 395016u, 0.58f);
			string footer = (_details ? $"Build {"0.2.4"}  /  Hooks {Hooks.Applied}/{Hooks.Applied + Hooks.Failed}  /  Enemies {_rt.MonsterClient.ControlledCount}  /  {_rt.Session.StatusText}" : (((object)CoopConfig.ToggleMenuKey.Value/*cast due to .constrained prefix*/).ToString() + "  party menu    /    " + ((object)CoopConfig.LeaveKey.Value/*cast due to .constrained prefix*/).ToString() + "  leave session"));
			PartyPresentation.MenuFrame(_canvas, num3, num4, ConnectionText(), _rt.Session.IsActive, footer, _rt.LastError, _rt.GameActive && !GameState.IsLoadingLevel);
			if (Button(num3 + 758f, num4 + 24f, 78f, 42f, "CLOSE"))
			{
				_rt.MenuVisible = false;
			}
			DrawConnections(num3 + 24f, num4 + 110f);
			if (_rt.Session.IsClient)
			{
				_managePlayers = false;
			}
			if (_managePlayers)
			{
				DrawModeration(num3 + 304f, num4 + 110f);
			}
			else
			{
				DrawPartyRoster(num3, num4);
			}
			DrawHudSettings(num3 + 24f, num4 + 575f);
			if (Button(num3 + 622f, num4 + 575f, 76f, 30f, "PLAYERS", primary: false, !_rt.Session.IsClient))
			{
				_managePlayers = !_managePlayers;
			}
			if (Button(num3 + 708f, num4 + 575f, 128f, 30f, _details ? "HIDE DETAILS" : "DETAILS"))
			{
				_details = !_details;
			}
		}

		private void DrawAllyBars()
		{
			try
			{
				if (CollectAllyBars(out var scale) == null)
				{
					Scale(scale);
					float viewWidth = (float)Screen.width / scale;
					for (int i = 0; i < _allyBars.Count; i++)
					{
						AllyBarPlacement allyBarPlacement = _allyBars[i];
						PartyPresentation.AllyBar(_canvas, allyBarPlacement.X, allyBarPlacement.Y, allyBarPlacement.Width, allyBarPlacement.Health, allyBarPlacement.Trail, allyBarPlacement.Opacity);
						PartyPresentation.AllyName(_canvas, allyBarPlacement.X, allyBarPlacement.Y, viewWidth, allyBarPlacement.Name, allyBarPlacement.NameOpacity);
					}
				}
			}
			catch (Exception ex)
			{
				if (!_allyBarErrorLogged)
				{
					_allyBarErrorLogged = true;
					Log.Warn("[UI] ally health bars unavailable: " + ex.Message);
				}
			}
		}

		private string CollectAllyBars(out float scale)
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: 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_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: 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_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			_allyBars.Clear();
			scale = 1f;
			if (!CoopConfig.AllyHealthBars.Value)
			{
				return "disabled";
			}
			if (_rt.MenuVisible)
			{
				return "co-op menu open";
			}
			if (!_rt.Session.IsActive || !_rt.GameActive || _rt.Loading)
			{
				return "not in a session world";
			}
			if (_rt.Ghosts.Count == 0)
			{
				return "no allies";
			}
			if (!GameShowsHealthBars())
			{
				return "game hid its health bars";
			}
			Camera val = GameCamera();
			if ((Object)(object)val == (Object)null)
			{
				return "no camera";
			}
			scale = BaseScale * CoopConfig.HudScale.Value;
			Vector3 position = ((Component)val).transform.position;
			float num = CharacterHeight() + 0.2f;
			float value = CoopConfig.AllyHealthBarDistance.Value;
			bool value2 = CoopConfig.AllyHealthBarNames.Value;
			float screenWidth = Screen.width;
			float screenHeight = Screen.height;
			IReadOnlyList<RemotePlayer> all = _rt.Ghosts.All;
			for (int i = 0; i < all.Count; i++)
			{
				Remo

BepInEx/plugins/AILimitCoop/LiteNetLib.dll

Decompiled 5 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using LiteNetLib.Layers;
using LiteNetLib.Utils;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyMetadata("IsTrimmable", "True")]
[assembly: AssemblyCompany("Ruslan Pyrch")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright 2024 Ruslan Pyrch")]
[assembly: AssemblyDescription("Lite reliable UDP library for .NET, Mono, and .NET Core")]
[assembly: AssemblyFileVersion("1.3.1")]
[assembly: AssemblyInformationalVersion("1.0.0+f57b9eafe72b924681a371d671af911478b6ab88")]
[assembly: AssemblyProduct("LiteNetLib")]
[assembly: AssemblyTitle("LiteNetLib")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/RevenantX/LiteNetLib")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.1.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class IsUnmanagedAttribute : Attribute
	{
	}
}
namespace LiteNetLib
{
	internal abstract class BaseChannel
	{
		protected readonly NetPeer Peer;

		protected readonly Queue<NetPacket> OutgoingQueue = new Queue<NetPacket>(64);

		private int _isAddedToPeerChannelSendQueue;

		public int PacketsInQueue => OutgoingQueue.Count;

		protected BaseChannel(NetPeer peer)
		{
			Peer = peer;
		}

		public void AddToQueue(NetPacket packet)
		{
			lock (OutgoingQueue)
			{
				OutgoingQueue.Enqueue(packet);
			}
			AddToPeerChannelSendQueue();
		}

		protected void AddToPeerChannelSendQueue()
		{
			if (Interlocked.CompareExchange(ref _isAddedToPeerChannelSendQueue, 1, 0) == 0)
			{
				Peer.AddToReliableChannelSendQueue(this);
			}
		}

		public bool SendAndCheckQueue()
		{
			bool num = SendNextPackets();
			if (!num)
			{
				Interlocked.Exchange(ref _isAddedToPeerChannelSendQueue, 0);
			}
			return num;
		}

		protected abstract bool SendNextPackets();

		public abstract bool ProcessPacket(NetPacket packet);
	}
	internal enum ConnectionRequestResult
	{
		None,
		Accept,
		Reject,
		RejectForce
	}
	public class ConnectionRequest
	{
		private readonly NetManager _listener;

		private int _used;

		internal NetConnectRequestPacket InternalPacket;

		public readonly IPEndPoint RemoteEndPoint;

		public NetDataReader Data => InternalPacket.Data;

		internal ConnectionRequestResult Result { get; private set; }

		internal void UpdateRequest(NetConnectRequestPacket connectRequest)
		{
			if (connectRequest.ConnectionTime >= InternalPacket.ConnectionTime && (connectRequest.ConnectionTime != InternalPacket.ConnectionTime || connectRequest.ConnectionNumber != InternalPacket.ConnectionNumber))
			{
				InternalPacket = connectRequest;
			}
		}

		private bool TryActivate()
		{
			return Interlocked.CompareExchange(ref _used, 1, 0) == 0;
		}

		internal ConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, NetManager listener)
		{
			InternalPacket = requestPacket;
			RemoteEndPoint = remoteEndPoint;
			_listener = listener;
		}

		public NetPeer AcceptIfKey(string key)
		{
			if (!TryActivate())
			{
				return null;
			}
			try
			{
				if (Data.GetString() == key)
				{
					Result = ConnectionRequestResult.Accept;
				}
			}
			catch
			{
				NetDebug.WriteError("[AC] Invalid incoming data");
			}
			if (Result == ConnectionRequestResult.Accept)
			{
				return _listener.OnConnectionSolved(this, null, 0, 0);
			}
			Result = ConnectionRequestResult.Reject;
			_listener.OnConnectionSolved(this, null, 0, 0);
			return null;
		}

		public NetPeer Accept()
		{
			if (!TryActivate())
			{
				return null;
			}
			Result = ConnectionRequestResult.Accept;
			return _listener.OnConnectionSolved(this, null, 0, 0);
		}

		public void Reject(byte[] rejectData, int start, int length, bool force)
		{
			if (TryActivate())
			{
				Result = (force ? ConnectionRequestResult.RejectForce : ConnectionRequestResult.Reject);
				_listener.OnConnectionSolved(this, rejectData, start, length);
			}
		}

		public void Reject(byte[] rejectData, int start, int length)
		{
			Reject(rejectData, start, length, force: false);
		}

		public void RejectForce(byte[] rejectData, int start, int length)
		{
			Reject(rejectData, start, length, force: true);
		}

		public void RejectForce()
		{
			Reject(null, 0, 0, force: true);
		}

		public void RejectForce(byte[] rejectData)
		{
			Reject(rejectData, 0, rejectData.Length, force: true);
		}

		public void RejectForce(NetDataWriter rejectData)
		{
			Reject(rejectData.Data, 0, rejectData.Length, force: true);
		}

		public void Reject()
		{
			Reject(null, 0, 0, force: false);
		}

		public void Reject(byte[] rejectData)
		{
			Reject(rejectData, 0, rejectData.Length, force: false);
		}

		public void Reject(NetDataWriter rejectData)
		{
			Reject(rejectData.Data, 0, rejectData.Length, force: false);
		}
	}
	public enum UnconnectedMessageType
	{
		BasicMessage,
		Broadcast
	}
	public enum DisconnectReason
	{
		ConnectionFailed,
		Timeout,
		HostUnreachable,
		NetworkUnreachable,
		RemoteConnectionClose,
		DisconnectPeerCalled,
		ConnectionRejected,
		InvalidProtocol,
		UnknownHost,
		Reconnect,
		PeerToPeerConnection,
		PeerNotFound
	}
	public struct DisconnectInfo
	{
		public DisconnectReason Reason;

		public SocketError SocketErrorCode;

		public NetPacketReader AdditionalData;
	}
	public interface INetEventListener
	{
		void OnPeerConnected(NetPeer peer);

		void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo);

		void OnNetworkError(IPEndPoint endPoint, SocketError socketError);

		void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod);

		void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType);

		void OnNetworkLatencyUpdate(NetPeer peer, int latency);

		void OnConnectionRequest(ConnectionRequest request);
	}
	public interface IDeliveryEventListener
	{
		void OnMessageDelivered(NetPeer peer, object userData);
	}
	public interface INtpEventListener
	{
		void OnNtpResponse(NtpPacket packet);
	}
	public interface IPeerAddressChangedListener
	{
		void OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress);
	}
	public class EventBasedNetListener : INetEventListener, IDeliveryEventListener, INtpEventListener, IPeerAddressChangedListener
	{
		public delegate void OnPeerConnected(NetPeer peer);

		public delegate void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo);

		public delegate void OnNetworkError(IPEndPoint endPoint, SocketError socketError);

		public delegate void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod);

		public delegate void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType);

		public delegate void OnNetworkLatencyUpdate(NetPeer peer, int latency);

		public delegate void OnConnectionRequest(ConnectionRequest request);

		public delegate void OnDeliveryEvent(NetPeer peer, object userData);

		public delegate void OnNtpResponseEvent(NtpPacket packet);

		public delegate void OnPeerAddressChangedEvent(NetPeer peer, IPEndPoint previousAddress);

		public event OnPeerConnected PeerConnectedEvent;

		public event OnPeerDisconnected PeerDisconnectedEvent;

		public event OnNetworkError NetworkErrorEvent;

		public event OnNetworkReceive NetworkReceiveEvent;

		public event OnNetworkReceiveUnconnected NetworkReceiveUnconnectedEvent;

		public event OnNetworkLatencyUpdate NetworkLatencyUpdateEvent;

		public event OnConnectionRequest ConnectionRequestEvent;

		public event OnDeliveryEvent DeliveryEvent;

		public event OnNtpResponseEvent NtpResponseEvent;

		public event OnPeerAddressChangedEvent PeerAddressChangedEvent;

		public void ClearPeerConnectedEvent()
		{
			this.PeerConnectedEvent = null;
		}

		public void ClearPeerDisconnectedEvent()
		{
			this.PeerDisconnectedEvent = null;
		}

		public void ClearNetworkErrorEvent()
		{
			this.NetworkErrorEvent = null;
		}

		public void ClearNetworkReceiveEvent()
		{
			this.NetworkReceiveEvent = null;
		}

		public void ClearNetworkReceiveUnconnectedEvent()
		{
			this.NetworkReceiveUnconnectedEvent = null;
		}

		public void ClearNetworkLatencyUpdateEvent()
		{
			this.NetworkLatencyUpdateEvent = null;
		}

		public void ClearConnectionRequestEvent()
		{
			this.ConnectionRequestEvent = null;
		}

		public void ClearDeliveryEvent()
		{
			this.DeliveryEvent = null;
		}

		public void ClearNtpResponseEvent()
		{
			this.NtpResponseEvent = null;
		}

		public void ClearPeerAddressChangedEvent()
		{
			this.PeerAddressChangedEvent = null;
		}

		void INetEventListener.OnPeerConnected(NetPeer peer)
		{
			if (this.PeerConnectedEvent != null)
			{
				this.PeerConnectedEvent(peer);
			}
		}

		void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
		{
			if (this.PeerDisconnectedEvent != null)
			{
				this.PeerDisconnectedEvent(peer, disconnectInfo);
			}
		}

		void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode)
		{
			if (this.NetworkErrorEvent != null)
			{
				this.NetworkErrorEvent(endPoint, socketErrorCode);
			}
		}

		void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod)
		{
			if (this.NetworkReceiveEvent != null)
			{
				this.NetworkReceiveEvent(peer, reader, channelNumber, deliveryMethod);
			}
		}

		void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType)
		{
			if (this.NetworkReceiveUnconnectedEvent != null)
			{
				this.NetworkReceiveUnconnectedEvent(remoteEndPoint, reader, messageType);
			}
		}

		void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency)
		{
			if (this.NetworkLatencyUpdateEvent != null)
			{
				this.NetworkLatencyUpdateEvent(peer, latency);
			}
		}

		void INetEventListener.OnConnectionRequest(ConnectionRequest request)
		{
			if (this.ConnectionRequestEvent != null)
			{
				this.ConnectionRequestEvent(request);
			}
		}

		void IDeliveryEventListener.OnMessageDelivered(NetPeer peer, object userData)
		{
			if (this.DeliveryEvent != null)
			{
				this.DeliveryEvent(peer, userData);
			}
		}

		void INtpEventListener.OnNtpResponse(NtpPacket packet)
		{
			if (this.NtpResponseEvent != null)
			{
				this.NtpResponseEvent(packet);
			}
		}

		void IPeerAddressChangedListener.OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress)
		{
			if (this.PeerAddressChangedEvent != null)
			{
				this.PeerAddressChangedEvent(peer, previousAddress);
			}
		}
	}
	internal sealed class NetConnectRequestPacket
	{
		public const int HeaderSize = 18;

		public readonly long ConnectionTime;

		public byte ConnectionNumber;

		public readonly byte[] TargetAddress;

		public readonly NetDataReader Data;

		public readonly int PeerId;

		private NetConnectRequestPacket(long connectionTime, byte connectionNumber, int localId, byte[] targetAddress, NetDataReader data)
		{
			ConnectionTime = connectionTime;
			ConnectionNumber = connectionNumber;
			TargetAddress = targetAddress;
			Data = data;
			PeerId = localId;
		}

		public static int GetProtocolId(NetPacket packet)
		{
			return BitConverter.ToInt32(packet.RawData, 1);
		}

		public static NetConnectRequestPacket FromData(NetPacket packet)
		{
			if (packet.ConnectionNumber >= 4)
			{
				return null;
			}
			long connectionTime = BitConverter.ToInt64(packet.RawData, 5);
			int localId = BitConverter.ToInt32(packet.RawData, 13);
			int num = packet.RawData[17];
			if (num != 16 && num != 28)
			{
				return null;
			}
			byte[] array = new byte[num];
			Buffer.BlockCopy(packet.RawData, 18, array, 0, num);
			NetDataReader netDataReader = new NetDataReader(null, 0, 0);
			if (packet.Size > 18 + num)
			{
				netDataReader.SetSource(packet.RawData, 18 + num, packet.Size);
			}
			return new NetConnectRequestPacket(connectionTime, packet.ConnectionNumber, localId, array, netDataReader);
		}

		public static NetPacket Make(NetDataWriter connectData, SocketAddress addressBytes, long connectTime, int localId)
		{
			NetPacket netPacket = new NetPacket(PacketProperty.ConnectRequest, connectData.Length + addressBytes.Size);
			FastBitConverter.GetBytes(netPacket.RawData, 1, 13);
			FastBitConverter.GetBytes(netPacket.RawData, 5, connectTime);
			FastBitConverter.GetBytes(netPacket.RawData, 13, localId);
			netPacket.RawData[17] = (byte)addressBytes.Size;
			for (int i = 0; i < addressBytes.Size; i++)
			{
				netPacket.RawData[18 + i] = addressBytes[i];
			}
			Buffer.BlockCopy(connectData.Data, 0, netPacket.RawData, 18 + addressBytes.Size, connectData.Length);
			return netPacket;
		}
	}
	internal sealed class NetConnectAcceptPacket
	{
		public const int Size = 15;

		public readonly long ConnectionTime;

		public readonly byte ConnectionNumber;

		public readonly int PeerId;

		public readonly bool PeerNetworkChanged;

		private NetConnectAcceptPacket(long connectionTime, byte connectionNumber, int peerId, bool peerNetworkChanged)
		{
			ConnectionTime = connectionTime;
			ConnectionNumber = connectionNumber;
			PeerId = peerId;
			PeerNetworkChanged = peerNetworkChanged;
		}

		public static NetConnectAcceptPacket FromData(NetPacket packet)
		{
			if (packet.Size != 15)
			{
				return null;
			}
			long connectionTime = BitConverter.ToInt64(packet.RawData, 1);
			byte b = packet.RawData[9];
			if (b >= 4)
			{
				return null;
			}
			byte b2 = packet.RawData[10];
			if (b2 > 1)
			{
				return null;
			}
			int num = BitConverter.ToInt32(packet.RawData, 11);
			if (num < 0)
			{
				return null;
			}
			return new NetConnectAcceptPacket(connectionTime, b, num, b2 == 1);
		}

		public static NetPacket Make(long connectTime, byte connectNum, int localPeerId)
		{
			NetPacket netPacket = new NetPacket(PacketProperty.ConnectAccept, 0);
			FastBitConverter.GetBytes(netPacket.RawData, 1, connectTime);
			netPacket.RawData[9] = connectNum;
			FastBitConverter.GetBytes(netPacket.RawData, 11, localPeerId);
			return netPacket;
		}

		public static NetPacket MakeNetworkChanged(NetPeer peer)
		{
			NetPacket netPacket = new NetPacket(PacketProperty.PeerNotFound, 14);
			FastBitConverter.GetBytes(netPacket.RawData, 1, peer.ConnectTime);
			netPacket.RawData[9] = peer.ConnectionNum;
			netPacket.RawData[10] = 1;
			FastBitConverter.GetBytes(netPacket.RawData, 11, peer.RemoteId);
			return netPacket;
		}
	}
	internal static class NativeSocket
	{
		private static class WinSock
		{
			private const string LibName = "ws2_32.dll";

			[DllImport("ws2_32.dll", SetLastError = true)]
			public static extern int recvfrom(IntPtr socketHandle, [In][Out] byte[] pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [Out] byte[] socketAddress, [In][Out] ref int socketAddressSize);

			[DllImport("ws2_32.dll", SetLastError = true)]
			internal unsafe static extern int sendto(IntPtr socketHandle, byte* pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [In] byte[] socketAddress, [In] int socketAddressSize);
		}

		private static class UnixSock
		{
			private const string LibName = "libc";

			[DllImport("libc", SetLastError = true)]
			public static extern int recvfrom(IntPtr socketHandle, [In][Out] byte[] pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [Out] byte[] socketAddress, [In][Out] ref int socketAddressSize);

			[DllImport("libc", SetLastError = true)]
			internal unsafe static extern int sendto(IntPtr socketHandle, byte* pinnedBuffer, [In] int len, [In] SocketFlags socketFlags, [In] byte[] socketAddress, [In] int socketAddressSize);
		}

		public static readonly bool IsSupported;

		public static readonly bool UnixMode;

		public const int IPv4AddrSize = 16;

		public const int IPv6AddrSize = 28;

		public const int AF_INET = 2;

		public const int AF_INET6 = 10;

		private static readonly Dictionary<int, SocketError> NativeErrorToSocketError;

		static NativeSocket()
		{
			IsSupported = false;
			UnixMode = false;
			NativeErrorToSocketError = new Dictionary<int, SocketError>
			{
				{
					13,
					SocketError.AccessDenied
				},
				{
					98,
					SocketError.AddressAlreadyInUse
				},
				{
					99,
					SocketError.AddressNotAvailable
				},
				{
					97,
					SocketError.AddressFamilyNotSupported
				},
				{
					11,
					SocketError.WouldBlock
				},
				{
					114,
					SocketError.AlreadyInProgress
				},
				{
					9,
					SocketError.OperationAborted
				},
				{
					125,
					SocketError.OperationAborted
				},
				{
					103,
					SocketError.ConnectionAborted
				},
				{
					111,
					SocketError.ConnectionRefused
				},
				{
					104,
					SocketError.ConnectionReset
				},
				{
					89,
					SocketError.DestinationAddressRequired
				},
				{
					14,
					SocketError.Fault
				},
				{
					112,
					SocketError.HostDown
				},
				{
					6,
					SocketError.HostNotFound
				},
				{
					113,
					SocketError.HostUnreachable
				},
				{
					115,
					SocketError.InProgress
				},
				{
					4,
					SocketError.Interrupted
				},
				{
					22,
					SocketError.InvalidArgument
				},
				{
					106,
					SocketError.IsConnected
				},
				{
					24,
					SocketError.TooManyOpenSockets
				},
				{
					90,
					SocketError.MessageSize
				},
				{
					100,
					SocketError.NetworkDown
				},
				{
					102,
					SocketError.NetworkReset
				},
				{
					101,
					SocketError.NetworkUnreachable
				},
				{
					23,
					SocketError.TooManyOpenSockets
				},
				{
					105,
					SocketError.NoBufferSpaceAvailable
				},
				{
					61,
					SocketError.NoData
				},
				{
					2,
					SocketError.AddressNotAvailable
				},
				{
					92,
					SocketError.ProtocolOption
				},
				{
					107,
					SocketError.NotConnected
				},
				{
					88,
					SocketError.NotSocket
				},
				{
					3440,
					SocketError.OperationNotSupported
				},
				{
					1,
					SocketError.AccessDenied
				},
				{
					32,
					SocketError.Shutdown
				},
				{
					96,
					SocketError.ProtocolFamilyNotSupported
				},
				{
					93,
					SocketError.ProtocolNotSupported
				},
				{
					91,
					SocketError.ProtocolType
				},
				{
					94,
					SocketError.SocketNotSupported
				},
				{
					108,
					SocketError.Disconnecting
				},
				{
					110,
					SocketError.TimedOut
				},
				{
					0,
					SocketError.Success
				}
			};
			if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
			{
				IsSupported = true;
				UnixMode = true;
			}
			else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
			{
				IsSupported = true;
			}
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public static int RecvFrom(IntPtr socketHandle, byte[] pinnedBuffer, int len, byte[] socketAddress, ref int socketAddressSize)
		{
			if (!UnixMode)
			{
				return WinSock.recvfrom(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, ref socketAddressSize);
			}
			return UnixSock.recvfrom(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, ref socketAddressSize);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public unsafe static int SendTo(IntPtr socketHandle, byte* pinnedBuffer, int len, byte[] socketAddress, int socketAddressSize)
		{
			if (!UnixMode)
			{
				return WinSock.sendto(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, socketAddressSize);
			}
			return UnixSock.sendto(socketHandle, pinnedBuffer, len, SocketFlags.None, socketAddress, socketAddressSize);
		}

		public static SocketError GetSocketError()
		{
			int lastWin32Error = Marshal.GetLastWin32Error();
			if (UnixMode)
			{
				if (!NativeErrorToSocketError.TryGetValue(lastWin32Error, out var value))
				{
					return SocketError.SocketError;
				}
				return value;
			}
			return (SocketError)lastWin32Error;
		}

		public static SocketException GetSocketException()
		{
			int lastWin32Error = Marshal.GetLastWin32Error();
			if (UnixMode)
			{
				if (!NativeErrorToSocketError.TryGetValue(lastWin32Error, out var value))
				{
					return new SocketException(-1);
				}
				return new SocketException((int)value);
			}
			return new SocketException(lastWin32Error);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public static short GetNativeAddressFamily(IPEndPoint remoteEndPoint)
		{
			if (!UnixMode)
			{
				return (short)remoteEndPoint.AddressFamily;
			}
			return (short)((remoteEndPoint.AddressFamily == AddressFamily.InterNetwork) ? 2 : 10);
		}
	}
	public enum NatAddressType
	{
		Internal,
		External
	}
	public interface INatPunchListener
	{
		void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token);

		void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token);
	}
	public class EventBasedNatPunchListener : INatPunchListener
	{
		public delegate void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token);

		public delegate void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token);

		public event OnNatIntroductionRequest NatIntroductionRequest;

		public event OnNatIntroductionSuccess NatIntroductionSuccess;

		void INatPunchListener.OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token)
		{
			if (this.NatIntroductionRequest != null)
			{
				this.NatIntroductionRequest(localEndPoint, remoteEndPoint, token);
			}
		}

		void INatPunchListener.OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token)
		{
			if (this.NatIntroductionSuccess != null)
			{
				this.NatIntroductionSuccess(targetEndPoint, type, token);
			}
		}
	}
	public sealed class NatPunchModule
	{
		private struct RequestEventData
		{
			public IPEndPoint LocalEndPoint;

			public IPEndPoint RemoteEndPoint;

			public string Token;
		}

		private struct SuccessEventData
		{
			public IPEndPoint TargetEndPoint;

			public NatAddressType Type;

			public string Token;
		}

		private class NatIntroduceRequestPacket
		{
			public IPEndPoint Internal
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}

			public string Token
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}
		}

		private class NatIntroduceResponsePacket
		{
			public IPEndPoint Internal
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}

			public IPEndPoint External
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}

			public string Token
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}
		}

		private class NatPunchPacket
		{
			public string Token
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}

			public bool IsExternal
			{
				[Preserve]
				get;
				[Preserve]
				set;
			}
		}

		private readonly NetManager _socket;

		private readonly ConcurrentQueue<RequestEventData> _requestEvents = new ConcurrentQueue<RequestEventData>();

		private readonly ConcurrentQueue<SuccessEventData> _successEvents = new ConcurrentQueue<SuccessEventData>();

		private readonly NetDataReader _cacheReader = new NetDataReader();

		private readonly NetDataWriter _cacheWriter = new NetDataWriter();

		private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor(256);

		private INatPunchListener _natPunchListener;

		public const int MaxTokenLength = 256;

		public bool UnsyncedEvents;

		internal NatPunchModule(NetManager socket)
		{
			_socket = socket;
			_netPacketProcessor.SubscribeReusable<NatIntroduceResponsePacket>(OnNatIntroductionResponse);
			_netPacketProcessor.SubscribeReusable<NatIntroduceRequestPacket, IPEndPoint>(OnNatIntroductionRequest);
			_netPacketProcessor.SubscribeReusable<NatPunchPacket, IPEndPoint>(OnNatPunch);
		}

		internal void ProcessMessage(IPEndPoint senderEndPoint, NetPacket packet)
		{
			lock (_cacheReader)
			{
				_cacheReader.SetSource(packet.RawData, 1, packet.Size);
				_netPacketProcessor.ReadAllPackets(_cacheReader, senderEndPoint);
			}
		}

		public void Init(INatPunchListener listener)
		{
			_natPunchListener = listener;
		}

		private void Send<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(T packet, IPEndPoint target) where T : class, new()
		{
			_cacheWriter.Reset();
			_cacheWriter.Put((byte)16);
			_netPacketProcessor.Write(_cacheWriter, packet);
			_socket.SendRaw(_cacheWriter.Data, 0, _cacheWriter.Length, target);
		}

		public void NatIntroduce(IPEndPoint hostInternal, IPEndPoint hostExternal, IPEndPoint clientInternal, IPEndPoint clientExternal, string additionalInfo)
		{
			NatIntroduceResponsePacket natIntroduceResponsePacket = new NatIntroduceResponsePacket
			{
				Token = additionalInfo
			};
			natIntroduceResponsePacket.Internal = hostInternal;
			natIntroduceResponsePacket.External = hostExternal;
			Send(natIntroduceResponsePacket, clientExternal);
			natIntroduceResponsePacket.Internal = clientInternal;
			natIntroduceResponsePacket.External = clientExternal;
			Send(natIntroduceResponsePacket, hostExternal);
		}

		public void PollEvents()
		{
			if (!UnsyncedEvents && _natPunchListener != null && (!_successEvents.IsEmpty || !_requestEvents.IsEmpty))
			{
				SuccessEventData result;
				while (_successEvents.TryDequeue(out result))
				{
					_natPunchListener.OnNatIntroductionSuccess(result.TargetEndPoint, result.Type, result.Token);
				}
				RequestEventData result2;
				while (_requestEvents.TryDequeue(out result2))
				{
					_natPunchListener.OnNatIntroductionRequest(result2.LocalEndPoint, result2.RemoteEndPoint, result2.Token);
				}
			}
		}

		public void SendNatIntroduceRequest(string host, int port, string additionalInfo)
		{
			SendNatIntroduceRequest(NetUtils.MakeEndPoint(host, port), additionalInfo);
		}

		public void SendNatIntroduceRequest(IPEndPoint masterServerEndPoint, string additionalInfo)
		{
			string localIp = NetUtils.GetLocalIp(LocalAddrType.IPv4);
			if (string.IsNullOrEmpty(localIp) || masterServerEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
			{
				localIp = NetUtils.GetLocalIp(LocalAddrType.IPv6);
			}
			Send(new NatIntroduceRequestPacket
			{
				Internal = NetUtils.MakeEndPoint(localIp, _socket.LocalPort),
				Token = additionalInfo
			}, masterServerEndPoint);
		}

		private void OnNatIntroductionRequest(NatIntroduceRequestPacket req, IPEndPoint senderEndPoint)
		{
			if (UnsyncedEvents)
			{
				_natPunchListener.OnNatIntroductionRequest(req.Internal, senderEndPoint, req.Token);
				return;
			}
			_requestEvents.Enqueue(new RequestEventData
			{
				LocalEndPoint = req.Internal,
				RemoteEndPoint = senderEndPoint,
				Token = req.Token
			});
		}

		private void OnNatIntroductionResponse(NatIntroduceResponsePacket req)
		{
			NatPunchPacket natPunchPacket = new NatPunchPacket
			{
				Token = req.Token
			};
			Send(natPunchPacket, req.Internal);
			_socket.Ttl = 2;
			_socket.SendRaw(new byte[1] { 17 }, 0, 1, req.External);
			_socket.Ttl = 255;
			natPunchPacket.IsExternal = true;
			Send(natPunchPacket, req.External);
		}

		private void OnNatPunch(NatPunchPacket req, IPEndPoint senderEndPoint)
		{
			if (UnsyncedEvents)
			{
				_natPunchListener.OnNatIntroductionSuccess(senderEndPoint, req.IsExternal ? NatAddressType.External : NatAddressType.Internal, req.Token);
				return;
			}
			_successEvents.Enqueue(new SuccessEventData
			{
				TargetEndPoint = senderEndPoint,
				Type = (req.IsExternal ? NatAddressType.External : NatAddressType.Internal),
				Token = req.Token
			});
		}
	}
	public enum DeliveryMethod : byte
	{
		Unreliable = 4,
		ReliableUnordered = 0,
		Sequenced = 1,
		ReliableOrdered = 2,
		ReliableSequenced = 3
	}
	public static class NetConstants
	{
		public const int DefaultWindowSize = 64;

		public const int SocketBufferSize = 1048576;

		public const int SocketTTL = 255;

		public const int HeaderSize = 1;

		public const int ChanneledHeaderSize = 4;

		public const int FragmentHeaderSize = 6;

		public const int FragmentedHeaderTotalSize = 10;

		public const ushort MaxSequence = 32768;

		public const ushort HalfMaxSequence = 16384;

		internal const int ProtocolId = 13;

		internal const int MaxUdpHeaderSize = 68;

		internal const int ChannelTypeCount = 4;

		internal static readonly int[] PossibleMtu = new int[6] { 1024, 1164, 1392, 1404, 1424, 1432 };

		public static readonly int InitialMtu = PossibleMtu[0];

		public static readonly int MaxPacketSize = PossibleMtu[PossibleMtu.Length - 1];

		public static readonly int MaxUnreliableDataSize = MaxPacketSize - 1;

		public const byte MaxConnectionNumber = 4;
	}
	public class InvalidPacketException : ArgumentException
	{
		public InvalidPacketException(string message)
			: base(message)
		{
		}
	}
	public class TooBigPacketException : InvalidPacketException
	{
		public TooBigPacketException(string message)
			: base(message)
		{
		}
	}
	public enum NetLogLevel
	{
		Warning,
		Error,
		Trace,
		Info
	}
	public interface INetLogger
	{
		void WriteNet(NetLogLevel level, string str, params object[] args);
	}
	public static class NetDebug
	{
		public static INetLogger Logger = null;

		private static readonly object DebugLogLock = new object();

		private static void WriteLogic(NetLogLevel logLevel, string str, params object[] args)
		{
			lock (DebugLogLock)
			{
				if (Logger == null)
				{
					Console.WriteLine(str, args);
				}
				else
				{
					Logger.WriteNet(logLevel, str, args);
				}
			}
		}

		[Conditional("DEBUG_MESSAGES")]
		internal static void Write(string str)
		{
			WriteLogic(NetLogLevel.Trace, str);
		}

		[Conditional("DEBUG_MESSAGES")]
		internal static void Write(NetLogLevel level, string str)
		{
			WriteLogic(level, str);
		}

		[Conditional("DEBUG_MESSAGES")]
		[Conditional("DEBUG")]
		internal static void WriteForce(string str)
		{
			WriteLogic(NetLogLevel.Trace, str);
		}

		[Conditional("DEBUG_MESSAGES")]
		[Conditional("DEBUG")]
		internal static void WriteForce(NetLogLevel level, string str)
		{
			WriteLogic(level, str);
		}

		internal static void WriteError(string str)
		{
			WriteLogic(NetLogLevel.Error, str);
		}
	}
	public sealed class NetPacketReader : NetDataReader
	{
		private NetPacket _packet;

		private readonly NetManager _manager;

		private readonly NetEvent _evt;

		internal NetPacketReader(NetManager manager, NetEvent evt)
		{
			_manager = manager;
			_evt = evt;
		}

		internal void SetSource(NetPacket packet, int headerSize)
		{
			if (packet != null)
			{
				_packet = packet;
				SetSource(packet.RawData, headerSize, packet.Size);
			}
		}

		internal void RecycleInternal()
		{
			Clear();
			if (_packet != null)
			{
				_manager.PoolRecycle(_packet);
			}
			_packet = null;
			_manager.RecycleEvent(_evt);
		}

		public void Recycle()
		{
			if (!_manager.AutoRecycle)
			{
				RecycleInternal();
			}
		}
	}
	internal sealed class NetEvent
	{
		public enum EType
		{
			Connect,
			Disconnect,
			Receive,
			ReceiveUnconnected,
			Error,
			ConnectionLatencyUpdated,
			Broadcast,
			ConnectionRequest,
			MessageDelivered,
			PeerAddressChanged
		}

		public NetEvent Next;

		public EType Type;

		public NetPeer Peer;

		public IPEndPoint RemoteEndPoint;

		public object UserData;

		public int Latency;

		public SocketError ErrorCode;

		public DisconnectReason DisconnectReason;

		public ConnectionRequest ConnectionRequest;

		public DeliveryMethod DeliveryMethod;

		public byte ChannelNumber;

		public readonly NetPacketReader DataReader;

		public NetEvent(NetManager manager)
		{
			DataReader = new NetPacketReader(manager, this);
		}
	}
	public class NetManager : IEnumerable<NetPeer>, IEnumerable
	{
		public struct NetPeerEnumerator : IEnumerator<NetPeer>, IEnumerator, IDisposable
		{
			private readonly NetPeer _initialPeer;

			private NetPeer _p;

			public NetPeer Current => _p;

			object IEnumerator.Current => _p;

			public NetPeerEnumerator(NetPeer p)
			{
				_initialPeer = p;
				_p = null;
			}

			public void Dispose()
			{
			}

			public bool MoveNext()
			{
				_p = ((_p == null) ? _initialPeer : _p.NextPeer);
				return _p != null;
			}

			public void Reset()
			{
				throw new NotSupportedException();
			}
		}

		private struct IncomingData
		{
			public NetPacket Data;

			public IPEndPoint EndPoint;

			public DateTime TimeWhenGet;
		}

		private struct Slot
		{
			internal int HashCode;

			internal int Next;

			internal NetPeer Value;
		}

		private readonly List<IncomingData> _pingSimulationList = new List<IncomingData>();

		private readonly Random _randomGenerator = new Random();

		private const int MinLatencyThreshold = 5;

		private Thread _logicThread;

		private bool _manualMode;

		private readonly AutoResetEvent _updateTriggerEvent = new AutoResetEvent(initialState: true);

		private NetEvent _pendingEventHead;

		private NetEvent _pendingEventTail;

		private NetEvent _netEventPoolHead;

		private readonly INetEventListener _netEventListener;

		private readonly IDeliveryEventListener _deliveryEventListener;

		private readonly INtpEventListener _ntpEventListener;

		private readonly IPeerAddressChangedListener _peerAddressChangedListener;

		private readonly Dictionary<IPEndPoint, ConnectionRequest> _requestsDict = new Dictionary<IPEndPoint, ConnectionRequest>();

		private readonly ConcurrentDictionary<IPEndPoint, NtpRequest> _ntpRequests = new ConcurrentDictionary<IPEndPoint, NtpRequest>();

		private long _connectedPeersCount;

		private readonly List<NetPeer> _connectedPeerListCache = new List<NetPeer>();

		private readonly PacketLayerBase _extraPacketLayer;

		private int _lastPeerId;

		private ConcurrentQueue<int> _peerIds = new ConcurrentQueue<int>();

		private byte _channelsCount = 1;

		private readonly object _eventLock = new object();

		private bool _dropPacket;

		public bool UnconnectedMessagesEnabled;

		public bool NatPunchEnabled;

		public int UpdateTime = 15;

		public int PingInterval = 1000;

		public int DisconnectTimeout = 5000;

		public bool SimulatePacketLoss;

		public bool SimulateLatency;

		public int SimulationPacketLossChance = 10;

		public int SimulationMinLatency = 30;

		public int SimulationMaxLatency = 100;

		public bool UnsyncedEvents;

		public bool UnsyncedReceiveEvent;

		public bool UnsyncedDeliveryEvent;

		public bool BroadcastReceiveEnabled;

		public int ReconnectDelay = 500;

		public int MaxConnectAttempts = 10;

		public bool ReuseAddress;

		public bool DontRoute;

		public readonly NetStatistics Statistics = new NetStatistics();

		public bool EnableStatistics;

		public readonly NatPunchModule NatPunchModule;

		public bool AutoRecycle;

		public bool IPv6Enabled = true;

		public int MtuOverride;

		public bool MtuDiscovery;

		public bool UseNativeSockets;

		public bool DisconnectOnUnreachable;

		public bool AllowPeerAddressChange;

		private const int MaxPrimeArrayLength = 2147483587;

		private const int HashPrime = 101;

		private const int Lower31BitMask = int.MaxValue;

		private static readonly int[] Primes;

		private int[] _buckets;

		private Slot[] _slots;

		private int _count;

		private int _lastIndex;

		private int _freeList = -1;

		private NetPeer[] _peersArray = new NetPeer[32];

		private readonly ReaderWriterLockSlim _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

		private volatile NetPeer _headPeer;

		private NetPacket _poolHead;

		private int _poolCount;

		private readonly object _poolLock = new object();

		public int PacketPoolSize = 1000;

		private const int ReceivePollingTime = 500000;

		private Socket _udpSocketv4;

		private Socket _udpSocketv6;

		private Thread _receiveThread;

		private IPEndPoint _bufferEndPointv4;

		private IPEndPoint _bufferEndPointv6;

		private const int SioUdpConnreset = -1744830452;

		private static readonly IPAddress MulticastAddressV6;

		public static readonly bool IPv6Support;

		internal bool NotConnected;

		public bool IsRunning { get; private set; }

		public int LocalPort { get; private set; }

		public NetPeer FirstPeer => _headPeer;

		public byte ChannelsCount
		{
			get
			{
				return _channelsCount;
			}
			set
			{
				if (value < 1 || value > 64)
				{
					throw new ArgumentException("Channels count must be between 1 and 64");
				}
				_channelsCount = value;
			}
		}

		public List<NetPeer> ConnectedPeerList
		{
			get
			{
				GetPeersNonAlloc(_connectedPeerListCache, ConnectionState.Connected);
				return _connectedPeerListCache;
			}
		}

		public int ConnectedPeersCount => (int)Interlocked.Read(ref _connectedPeersCount);

		public int ExtraPacketSizeForLayer => _extraPacketLayer?.ExtraPacketSizeForLayer ?? 0;

		public int PoolCount => _poolCount;

		public short Ttl
		{
			get
			{
				return _udpSocketv4.Ttl;
			}
			internal set
			{
				_udpSocketv4.Ttl = value;
			}
		}

		public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null)
		{
			_netEventListener = listener;
			_deliveryEventListener = listener as IDeliveryEventListener;
			_ntpEventListener = listener as INtpEventListener;
			_peerAddressChangedListener = listener as IPeerAddressChangedListener;
			NatPunchModule = new NatPunchModule(this);
			_extraPacketLayer = extraPacketLayer;
		}

		internal void ConnectionLatencyUpdated(NetPeer fromPeer, int latency)
		{
			CreateEvent(NetEvent.EType.ConnectionLatencyUpdated, fromPeer, null, SocketError.Success, latency, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
		}

		internal void MessageDelivered(NetPeer fromPeer, object userData)
		{
			if (_deliveryEventListener != null)
			{
				CreateEvent(NetEvent.EType.MessageDelivered, fromPeer, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0, null, userData);
			}
		}

		internal void DisconnectPeerForce(NetPeer peer, DisconnectReason reason, SocketError socketErrorCode, NetPacket eventData)
		{
			DisconnectPeer(peer, reason, socketErrorCode, force: true, null, 0, 0, eventData);
		}

		private void DisconnectPeer(NetPeer peer, DisconnectReason reason, SocketError socketErrorCode, bool force, byte[] data, int start, int count, NetPacket eventData)
		{
			switch (peer.Shutdown(data, start, count, force))
			{
			case ShutdownResult.None:
				return;
			case ShutdownResult.WasConnected:
				Interlocked.Decrement(ref _connectedPeersCount);
				break;
			}
			CreateEvent(NetEvent.EType.Disconnect, peer, null, socketErrorCode, 0, reason, null, DeliveryMethod.Unreliable, 0, eventData);
		}

		private void CreateEvent(NetEvent.EType type, NetPeer peer = null, IPEndPoint remoteEndPoint = null, SocketError errorCode = SocketError.Success, int latency = 0, DisconnectReason disconnectReason = DisconnectReason.ConnectionFailed, ConnectionRequest connectionRequest = null, DeliveryMethod deliveryMethod = DeliveryMethod.Unreliable, byte channelNumber = 0, NetPacket readerSource = null, object userData = null)
		{
			bool flag = UnsyncedEvents;
			switch (type)
			{
			case NetEvent.EType.Connect:
				Interlocked.Increment(ref _connectedPeersCount);
				break;
			case NetEvent.EType.MessageDelivered:
				flag = UnsyncedDeliveryEvent;
				break;
			}
			NetEvent netEvent;
			lock (_eventLock)
			{
				netEvent = _netEventPoolHead;
				if (netEvent == null)
				{
					netEvent = new NetEvent(this);
				}
				else
				{
					_netEventPoolHead = netEvent.Next;
				}
			}
			netEvent.Next = null;
			netEvent.Type = type;
			netEvent.DataReader.SetSource(readerSource, readerSource?.GetHeaderSize() ?? 0);
			netEvent.Peer = peer;
			netEvent.RemoteEndPoint = remoteEndPoint;
			netEvent.Latency = latency;
			netEvent.ErrorCode = errorCode;
			netEvent.DisconnectReason = disconnectReason;
			netEvent.ConnectionRequest = connectionRequest;
			netEvent.DeliveryMethod = deliveryMethod;
			netEvent.ChannelNumber = channelNumber;
			netEvent.UserData = userData;
			if (flag || _manualMode)
			{
				ProcessEvent(netEvent);
				return;
			}
			lock (_eventLock)
			{
				if (_pendingEventTail == null)
				{
					_pendingEventHead = netEvent;
				}
				else
				{
					_pendingEventTail.Next = netEvent;
				}
				_pendingEventTail = netEvent;
			}
		}

		private void ProcessEvent(NetEvent evt)
		{
			bool isNull = evt.DataReader.IsNull;
			switch (evt.Type)
			{
			case NetEvent.EType.Connect:
				_netEventListener.OnPeerConnected(evt.Peer);
				break;
			case NetEvent.EType.Disconnect:
			{
				DisconnectInfo disconnectInfo = default(DisconnectInfo);
				disconnectInfo.Reason = evt.DisconnectReason;
				disconnectInfo.AdditionalData = evt.DataReader;
				disconnectInfo.SocketErrorCode = evt.ErrorCode;
				DisconnectInfo disconnectInfo2 = disconnectInfo;
				_netEventListener.OnPeerDisconnected(evt.Peer, disconnectInfo2);
				break;
			}
			case NetEvent.EType.Receive:
				_netEventListener.OnNetworkReceive(evt.Peer, evt.DataReader, evt.ChannelNumber, evt.DeliveryMethod);
				break;
			case NetEvent.EType.ReceiveUnconnected:
				_netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.BasicMessage);
				break;
			case NetEvent.EType.Broadcast:
				_netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.Broadcast);
				break;
			case NetEvent.EType.Error:
				_netEventListener.OnNetworkError(evt.RemoteEndPoint, evt.ErrorCode);
				break;
			case NetEvent.EType.ConnectionLatencyUpdated:
				_netEventListener.OnNetworkLatencyUpdate(evt.Peer, evt.Latency);
				break;
			case NetEvent.EType.ConnectionRequest:
				_netEventListener.OnConnectionRequest(evt.ConnectionRequest);
				break;
			case NetEvent.EType.MessageDelivered:
				_deliveryEventListener.OnMessageDelivered(evt.Peer, evt.UserData);
				break;
			case NetEvent.EType.PeerAddressChanged:
			{
				_peersLock.EnterUpgradeableReadLock();
				IPEndPoint iPEndPoint = null;
				if (ContainsPeer(evt.Peer))
				{
					_peersLock.EnterWriteLock();
					RemovePeerFromSet(evt.Peer);
					iPEndPoint = new IPEndPoint(evt.Peer.Address, evt.Peer.Port);
					evt.Peer.FinishEndPointChange(evt.RemoteEndPoint);
					AddPeerToSet(evt.Peer);
					_peersLock.ExitWriteLock();
				}
				_peersLock.ExitUpgradeableReadLock();
				if (iPEndPoint != null && _peerAddressChangedListener != null)
				{
					_peerAddressChangedListener.OnPeerAddressChanged(evt.Peer, iPEndPoint);
				}
				break;
			}
			}
			if (isNull)
			{
				RecycleEvent(evt);
			}
			else if (AutoRecycle)
			{
				evt.DataReader.RecycleInternal();
			}
		}

		internal void RecycleEvent(NetEvent evt)
		{
			evt.Peer = null;
			evt.ErrorCode = SocketError.Success;
			evt.RemoteEndPoint = null;
			evt.ConnectionRequest = null;
			lock (_eventLock)
			{
				evt.Next = _netEventPoolHead;
				_netEventPoolHead = evt;
			}
		}

		private void UpdateLogic()
		{
			List<NetPeer> list = new List<NetPeer>();
			Stopwatch stopwatch = new Stopwatch();
			stopwatch.Start();
			while (IsRunning)
			{
				try
				{
					float num = (float)((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0);
					num = ((num <= 0f) ? 0.001f : num);
					stopwatch.Restart();
					for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
					{
						if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > (float)DisconnectTimeout)
						{
							list.Add(netPeer);
						}
						else
						{
							netPeer.Update(num);
						}
					}
					if (list.Count > 0)
					{
						_peersLock.EnterWriteLock();
						for (int i = 0; i < list.Count; i++)
						{
							RemovePeer(list[i], enableWriteLock: false);
						}
						_peersLock.ExitWriteLock();
						list.Clear();
					}
					ProcessNtpRequests(num);
					int num2 = UpdateTime - (int)stopwatch.ElapsedMilliseconds;
					if (num2 > 0)
					{
						_updateTriggerEvent.WaitOne(num2);
					}
				}
				catch (ThreadAbortException)
				{
					return;
				}
				catch (Exception ex2)
				{
					NetDebug.WriteError("[NM] LogicThread error: " + ex2);
				}
			}
			stopwatch.Stop();
		}

		[Conditional("DEBUG")]
		private void ProcessDelayedPackets()
		{
			if (!SimulateLatency)
			{
				return;
			}
			DateTime utcNow = DateTime.UtcNow;
			lock (_pingSimulationList)
			{
				for (int i = 0; i < _pingSimulationList.Count; i++)
				{
					IncomingData incomingData = _pingSimulationList[i];
					if (incomingData.TimeWhenGet <= utcNow)
					{
						HandleMessageReceived(incomingData.Data, incomingData.EndPoint);
						_pingSimulationList.RemoveAt(i);
						i--;
					}
				}
			}
		}

		private void ProcessNtpRequests(float elapsedMilliseconds)
		{
			List<IPEndPoint> list = null;
			foreach (KeyValuePair<IPEndPoint, NtpRequest> ntpRequest in _ntpRequests)
			{
				ntpRequest.Value.Send(_udpSocketv4, elapsedMilliseconds);
				if (ntpRequest.Value.NeedToKill)
				{
					if (list == null)
					{
						list = new List<IPEndPoint>();
					}
					list.Add(ntpRequest.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (IPEndPoint item in list)
			{
				_ntpRequests.TryRemove(item, out var _);
			}
		}

		public void ManualUpdate(float elapsedMilliseconds)
		{
			if (!_manualMode)
			{
				return;
			}
			for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
			{
				if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > (float)DisconnectTimeout)
				{
					RemovePeer(netPeer, enableWriteLock: false);
				}
				else
				{
					netPeer.Update(elapsedMilliseconds);
				}
			}
			ProcessNtpRequests(elapsedMilliseconds);
		}

		internal NetPeer OnConnectionSolved(ConnectionRequest request, byte[] rejectData, int start, int length)
		{
			NetPeer actualValue = null;
			if (request.Result == ConnectionRequestResult.RejectForce)
			{
				if (rejectData != null && length > 0)
				{
					NetPacket netPacket = PoolGetWithProperty(PacketProperty.Disconnect, length);
					netPacket.ConnectionNumber = request.InternalPacket.ConnectionNumber;
					FastBitConverter.GetBytes(netPacket.RawData, 1, request.InternalPacket.ConnectionTime);
					if (netPacket.Size >= NetConstants.PossibleMtu[0])
					{
						NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU!");
					}
					else
					{
						Buffer.BlockCopy(rejectData, start, netPacket.RawData, 9, length);
					}
					SendRawAndRecycle(netPacket, request.RemoteEndPoint);
				}
				lock (_requestsDict)
				{
					_requestsDict.Remove(request.RemoteEndPoint);
				}
			}
			else
			{
				lock (_requestsDict)
				{
					if (!TryGetPeer(request.RemoteEndPoint, out actualValue))
					{
						if (request.Result == ConnectionRequestResult.Reject)
						{
							actualValue = new NetPeer(this, request.RemoteEndPoint, GetNextPeerId());
							actualValue.Reject(request.InternalPacket, rejectData, start, length);
							AddPeer(actualValue);
						}
						else
						{
							actualValue = new NetPeer(this, request, GetNextPeerId());
							AddPeer(actualValue);
							CreateEvent(NetEvent.EType.Connect, actualValue, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
						}
					}
					_requestsDict.Remove(request.RemoteEndPoint);
				}
			}
			return actualValue;
		}

		private int GetNextPeerId()
		{
			if (!_peerIds.TryDequeue(out var result))
			{
				return _lastPeerId++;
			}
			return result;
		}

		private void ProcessConnectRequest(IPEndPoint remoteEndPoint, NetPeer netPeer, NetConnectRequestPacket connRequest)
		{
			if (netPeer != null)
			{
				ConnectRequestResult connectRequestResult = netPeer.ProcessConnectRequest(connRequest);
				switch (connectRequestResult)
				{
				default:
					return;
				case ConnectRequestResult.Reconnection:
					DisconnectPeerForce(netPeer, DisconnectReason.Reconnect, SocketError.Success, null);
					RemovePeer(netPeer, enableWriteLock: true);
					break;
				case ConnectRequestResult.NewConnection:
					RemovePeer(netPeer, enableWriteLock: true);
					break;
				case ConnectRequestResult.P2PLose:
					DisconnectPeerForce(netPeer, DisconnectReason.PeerToPeerConnection, SocketError.Success, null);
					RemovePeer(netPeer, enableWriteLock: true);
					break;
				}
				if (connectRequestResult != ConnectRequestResult.P2PLose)
				{
					connRequest.ConnectionNumber = (byte)((netPeer.ConnectionNum + 1) % 4);
				}
			}
			ConnectionRequest value;
			lock (_requestsDict)
			{
				if (_requestsDict.TryGetValue(remoteEndPoint, out value))
				{
					value.UpdateRequest(connRequest);
					return;
				}
				value = new ConnectionRequest(remoteEndPoint, connRequest, this);
				_requestsDict.Add(remoteEndPoint, value);
			}
			CreateEvent(NetEvent.EType.ConnectionRequest, null, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, value, DeliveryMethod.Unreliable, 0);
		}

		private void OnMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint)
		{
			if (packet.Size == 0)
			{
				PoolRecycle(packet);
				return;
			}
			_dropPacket = false;
			if (!_dropPacket)
			{
				HandleMessageReceived(packet, remoteEndPoint);
			}
		}

		[Conditional("DEBUG")]
		private void HandleSimulateLatency(NetPacket packet, IPEndPoint remoteEndPoint)
		{
			if (!SimulateLatency)
			{
				return;
			}
			int num = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency);
			if (num > 5)
			{
				lock (_pingSimulationList)
				{
					_pingSimulationList.Add(new IncomingData
					{
						Data = packet,
						EndPoint = remoteEndPoint,
						TimeWhenGet = DateTime.UtcNow.AddMilliseconds(num)
					});
				}
				_dropPacket = true;
			}
		}

		[Conditional("DEBUG")]
		private void HandleSimulatePacketLoss()
		{
			if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100.0 < (double)SimulationPacketLossChance)
			{
				_dropPacket = true;
			}
		}

		private void HandleMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint)
		{
			int size = packet.Size;
			if (EnableStatistics)
			{
				Statistics.IncrementPacketsReceived();
				Statistics.AddBytesReceived(size);
			}
			if (_ntpRequests.Count > 0 && _ntpRequests.TryGetValue(remoteEndPoint, out var _))
			{
				if (packet.Size >= 48)
				{
					byte[] array = new byte[packet.Size];
					Buffer.BlockCopy(packet.RawData, 0, array, 0, packet.Size);
					NtpPacket ntpPacket = NtpPacket.FromServerResponse(array, DateTime.UtcNow);
					try
					{
						ntpPacket.ValidateReply();
					}
					catch (InvalidOperationException)
					{
						ntpPacket = null;
					}
					if (ntpPacket != null)
					{
						_ntpRequests.TryRemove(remoteEndPoint, out var _);
						_ntpEventListener?.OnNtpResponse(ntpPacket);
					}
				}
				return;
			}
			if (_extraPacketLayer != null)
			{
				_extraPacketLayer.ProcessInboundPacket(ref remoteEndPoint, ref packet.RawData, ref packet.Size);
				if (packet.Size == 0)
				{
					return;
				}
			}
			if (!packet.Verify())
			{
				NetDebug.WriteError("[NM] DataReceived: bad!");
				PoolRecycle(packet);
				return;
			}
			switch (packet.Property)
			{
			case PacketProperty.ConnectRequest:
				if (NetConnectRequestPacket.GetProtocolId(packet) != 13)
				{
					SendRawAndRecycle(PoolGetWithProperty(PacketProperty.InvalidProtocol), remoteEndPoint);
					return;
				}
				break;
			case PacketProperty.Broadcast:
				if (BroadcastReceiveEnabled)
				{
					CreateEvent(NetEvent.EType.Broadcast, null, remoteEndPoint, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0, packet);
				}
				return;
			case PacketProperty.UnconnectedMessage:
				if (UnconnectedMessagesEnabled)
				{
					CreateEvent(NetEvent.EType.ReceiveUnconnected, null, remoteEndPoint, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0, packet);
				}
				return;
			case PacketProperty.NatMessage:
				if (NatPunchEnabled)
				{
					NatPunchModule.ProcessMessage(remoteEndPoint, packet);
				}
				return;
			}
			NetPeer actualValue = remoteEndPoint as NetPeer;
			bool flag = actualValue != null || TryGetPeer(remoteEndPoint, out actualValue);
			if (flag && EnableStatistics)
			{
				actualValue.Statistics.IncrementPacketsReceived();
				actualValue.Statistics.AddBytesReceived(size);
			}
			switch (packet.Property)
			{
			case PacketProperty.ConnectRequest:
			{
				NetConnectRequestPacket netConnectRequestPacket = NetConnectRequestPacket.FromData(packet);
				if (netConnectRequestPacket != null)
				{
					ProcessConnectRequest(remoteEndPoint, actualValue, netConnectRequestPacket);
				}
				break;
			}
			case PacketProperty.PeerNotFound:
				if (flag)
				{
					if (actualValue.ConnectionState == ConnectionState.Connected)
					{
						if (packet.Size == 1)
						{
							actualValue.ResetMtu();
							SendRaw(NetConnectAcceptPacket.MakeNetworkChanged(actualValue), remoteEndPoint);
						}
						else if (packet.Size == 2 && packet.RawData[1] == 1)
						{
							DisconnectPeerForce(actualValue, DisconnectReason.PeerNotFound, SocketError.Success, null);
						}
					}
				}
				else
				{
					if (packet.Size <= 1)
					{
						break;
					}
					bool flag2 = false;
					if (AllowPeerAddressChange)
					{
						NetConnectAcceptPacket netConnectAcceptPacket = NetConnectAcceptPacket.FromData(packet);
						if (netConnectAcceptPacket != null && netConnectAcceptPacket.PeerNetworkChanged && netConnectAcceptPacket.PeerId < _peersArray.Length)
						{
							_peersLock.EnterUpgradeableReadLock();
							NetPeer netPeer = _peersArray[netConnectAcceptPacket.PeerId];
							_peersLock.ExitUpgradeableReadLock();
							if (netPeer != null && netPeer.ConnectTime == netConnectAcceptPacket.ConnectionTime && netPeer.ConnectionNum == netConnectAcceptPacket.ConnectionNumber)
							{
								if (netPeer.ConnectionState == ConnectionState.Connected)
								{
									netPeer.InitiateEndPointChange();
									CreateEvent(NetEvent.EType.PeerAddressChanged, netPeer, remoteEndPoint, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
								}
								flag2 = true;
							}
						}
					}
					PoolRecycle(packet);
					if (!flag2)
					{
						NetPacket netPacket = PoolGetWithProperty(PacketProperty.PeerNotFound, 1);
						netPacket.RawData[1] = 1;
						SendRawAndRecycle(netPacket, remoteEndPoint);
					}
				}
				break;
			case PacketProperty.InvalidProtocol:
				if (flag && actualValue.ConnectionState == ConnectionState.Outgoing)
				{
					DisconnectPeerForce(actualValue, DisconnectReason.InvalidProtocol, SocketError.Success, null);
				}
				break;
			case PacketProperty.Disconnect:
				if (flag)
				{
					DisconnectResult disconnectResult = actualValue.ProcessDisconnect(packet);
					if (disconnectResult == DisconnectResult.None)
					{
						PoolRecycle(packet);
						break;
					}
					DisconnectPeerForce(actualValue, (disconnectResult == DisconnectResult.Disconnect) ? DisconnectReason.RemoteConnectionClose : DisconnectReason.ConnectionRejected, SocketError.Success, packet);
				}
				else
				{
					PoolRecycle(packet);
				}
				SendRawAndRecycle(PoolGetWithProperty(PacketProperty.ShutdownOk), remoteEndPoint);
				break;
			case PacketProperty.ConnectAccept:
				if (flag)
				{
					NetConnectAcceptPacket netConnectAcceptPacket2 = NetConnectAcceptPacket.FromData(packet);
					if (netConnectAcceptPacket2 != null && actualValue.ProcessConnectAccept(netConnectAcceptPacket2))
					{
						CreateEvent(NetEvent.EType.Connect, actualValue, null, SocketError.Success, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
					}
				}
				break;
			default:
				if (flag)
				{
					actualValue.ProcessPacket(packet);
				}
				else
				{
					SendRawAndRecycle(PoolGetWithProperty(PacketProperty.PeerNotFound), remoteEndPoint);
				}
				break;
			}
		}

		internal void CreateReceiveEvent(NetPacket packet, DeliveryMethod method, byte channelNumber, int headerSize, NetPeer fromPeer)
		{
			if (UnsyncedEvents || UnsyncedReceiveEvent || _manualMode)
			{
				NetEvent netEvent;
				lock (_eventLock)
				{
					netEvent = _netEventPoolHead;
					if (netEvent == null)
					{
						netEvent = new NetEvent(this);
					}
					else
					{
						_netEventPoolHead = netEvent.Next;
					}
				}
				netEvent.Next = null;
				netEvent.Type = NetEvent.EType.Receive;
				netEvent.DataReader.SetSource(packet, headerSize);
				netEvent.Peer = fromPeer;
				netEvent.DeliveryMethod = method;
				netEvent.ChannelNumber = channelNumber;
				ProcessEvent(netEvent);
				return;
			}
			lock (_eventLock)
			{
				NetEvent netEvent = _netEventPoolHead;
				if (netEvent == null)
				{
					netEvent = new NetEvent(this);
				}
				else
				{
					_netEventPoolHead = netEvent.Next;
				}
				netEvent.Next = null;
				netEvent.Type = NetEvent.EType.Receive;
				netEvent.DataReader.SetSource(packet, headerSize);
				netEvent.Peer = fromPeer;
				netEvent.DeliveryMethod = method;
				netEvent.ChannelNumber = channelNumber;
				if (_pendingEventTail == null)
				{
					_pendingEventHead = netEvent;
				}
				else
				{
					_pendingEventTail.Next = netEvent;
				}
				_pendingEventTail = netEvent;
			}
		}

		public void SendToAll(NetDataWriter writer, DeliveryMethod options)
		{
			SendToAll(writer.Data, 0, writer.Length, options);
		}

		public void SendToAll(byte[] data, DeliveryMethod options)
		{
			SendToAll(data, 0, data.Length, options);
		}

		public void SendToAll(byte[] data, int start, int length, DeliveryMethod options)
		{
			SendToAll(data, start, length, 0, options);
		}

		public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options)
		{
			SendToAll(writer.Data, 0, writer.Length, channelNumber, options);
		}

		public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options)
		{
			SendToAll(data, 0, data.Length, channelNumber, options);
		}

		public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options)
		{
			try
			{
				_peersLock.EnterReadLock();
				for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
				{
					netPeer.Send(data, start, length, channelNumber, options);
				}
			}
			finally
			{
				_peersLock.ExitReadLock();
			}
		}

		public void SendToAll(NetDataWriter writer, DeliveryMethod options, NetPeer excludePeer)
		{
			SendToAll(writer.Data, 0, writer.Length, 0, options, excludePeer);
		}

		public void SendToAll(byte[] data, DeliveryMethod options, NetPeer excludePeer)
		{
			SendToAll(data, 0, data.Length, 0, options, excludePeer);
		}

		public void SendToAll(byte[] data, int start, int length, DeliveryMethod options, NetPeer excludePeer)
		{
			SendToAll(data, start, length, 0, options, excludePeer);
		}

		public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
		{
			SendToAll(writer.Data, 0, writer.Length, channelNumber, options, excludePeer);
		}

		public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
		{
			SendToAll(data, 0, data.Length, channelNumber, options, excludePeer);
		}

		public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
		{
			try
			{
				_peersLock.EnterReadLock();
				for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
				{
					if (netPeer != excludePeer)
					{
						netPeer.Send(data, start, length, channelNumber, options);
					}
				}
			}
			finally
			{
				_peersLock.ExitReadLock();
			}
		}

		public void SendToAll(ReadOnlySpan<byte> data, DeliveryMethod options)
		{
			SendToAll(data, 0, options, null);
		}

		public void SendToAll(ReadOnlySpan<byte> data, DeliveryMethod options, NetPeer excludePeer)
		{
			SendToAll(data, 0, options, excludePeer);
		}

		public void SendToAll(ReadOnlySpan<byte> data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
		{
			try
			{
				_peersLock.EnterReadLock();
				for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
				{
					if (netPeer != excludePeer)
					{
						netPeer.Send(data, channelNumber, options);
					}
				}
			}
			finally
			{
				_peersLock.ExitReadLock();
			}
		}

		public bool SendUnconnectedMessage(ReadOnlySpan<byte> message, IPEndPoint remoteEndPoint)
		{
			int headerSize = NetPacket.GetHeaderSize(PacketProperty.UnconnectedMessage);
			NetPacket netPacket = PoolGetPacket(message.Length + headerSize);
			netPacket.Property = PacketProperty.UnconnectedMessage;
			message.CopyTo(new Span<byte>(netPacket.RawData, headerSize, message.Length));
			return SendRawAndRecycle(netPacket, remoteEndPoint) > 0;
		}

		public bool Start()
		{
			return Start(0);
		}

		public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port)
		{
			return Start(addressIPv4, addressIPv6, port, manualMode: false);
		}

		public bool Start(string addressIPv4, string addressIPv6, int port)
		{
			IPAddress addressIPv7 = NetUtils.ResolveAddress(addressIPv4);
			IPAddress addressIPv8 = NetUtils.ResolveAddress(addressIPv6);
			return Start(addressIPv7, addressIPv8, port);
		}

		public bool Start(int port)
		{
			return Start(IPAddress.Any, IPAddress.IPv6Any, port);
		}

		public bool StartInManualMode(IPAddress addressIPv4, IPAddress addressIPv6, int port)
		{
			return Start(addressIPv4, addressIPv6, port, manualMode: true);
		}

		public bool StartInManualMode(string addressIPv4, string addressIPv6, int port)
		{
			IPAddress addressIPv7 = NetUtils.ResolveAddress(addressIPv4);
			IPAddress addressIPv8 = NetUtils.ResolveAddress(addressIPv6);
			return StartInManualMode(addressIPv7, addressIPv8, port);
		}

		public bool StartInManualMode(int port)
		{
			return StartInManualMode(IPAddress.Any, IPAddress.IPv6Any, port);
		}

		public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint)
		{
			return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint);
		}

		public bool SendUnconnectedMessage(NetDataWriter writer, string address, int port)
		{
			IPEndPoint remoteEndPoint = NetUtils.MakeEndPoint(address, port);
			return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint);
		}

		public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint)
		{
			return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint);
		}

		public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
		{
			NetPacket packet = PoolGetWithData(PacketProperty.UnconnectedMessage, message, start, length);
			return SendRawAndRecycle(packet, remoteEndPoint) > 0;
		}

		public void TriggerUpdate()
		{
			_updateTriggerEvent.Set();
		}

		public void PollEvents(int maxProcessedEvents = 0)
		{
			if (_manualMode)
			{
				if (_udpSocketv4 != null)
				{
					ManualReceive(_udpSocketv4, _bufferEndPointv4, maxProcessedEvents);
				}
				if (_udpSocketv6 != null && _udpSocketv6 != _udpSocketv4)
				{
					ManualReceive(_udpSocketv6, _bufferEndPointv6, maxProcessedEvents);
				}
			}
			else
			{
				if (UnsyncedEvents)
				{
					return;
				}
				NetEvent netEvent;
				lock (_eventLock)
				{
					netEvent = _pendingEventHead;
					_pendingEventHead = null;
					_pendingEventTail = null;
				}
				int num = 0;
				while (netEvent != null)
				{
					NetEvent next = netEvent.Next;
					ProcessEvent(netEvent);
					netEvent = next;
					num++;
					if (num == maxProcessedEvents)
					{
						break;
					}
				}
			}
		}

		public NetPeer Connect(string address, int port, string key)
		{
			return Connect(address, port, NetDataWriter.FromString(key));
		}

		public NetPeer Connect(string address, int port, NetDataWriter connectionData)
		{
			IPEndPoint target;
			try
			{
				target = NetUtils.MakeEndPoint(address, port);
			}
			catch
			{
				CreateEvent(NetEvent.EType.Disconnect, null, null, SocketError.Success, 0, DisconnectReason.UnknownHost, null, DeliveryMethod.Unreliable, 0);
				return null;
			}
			return Connect(target, connectionData);
		}

		public NetPeer Connect(IPEndPoint target, string key)
		{
			return Connect(target, NetDataWriter.FromString(key));
		}

		public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData)
		{
			if (!IsRunning)
			{
				throw new InvalidOperationException("Client is not running");
			}
			lock (_requestsDict)
			{
				if (_requestsDict.ContainsKey(target))
				{
					return null;
				}
				byte connectNum = 0;
				if (TryGetPeer(target, out var actualValue))
				{
					ConnectionState connectionState = actualValue.ConnectionState;
					if (connectionState == ConnectionState.Outgoing || connectionState == ConnectionState.Connected)
					{
						return actualValue;
					}
					connectNum = (byte)((actualValue.ConnectionNum + 1) % 4);
					RemovePeer(actualValue, enableWriteLock: true);
				}
				actualValue = new NetPeer(this, target, GetNextPeerId(), connectNum, connectionData);
				AddPeer(actualValue);
				return actualValue;
			}
		}

		public void Stop()
		{
			Stop(sendDisconnectMessages: true);
		}

		public void Stop(bool sendDisconnectMessages)
		{
			if (IsRunning)
			{
				for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
				{
					netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages);
				}
				CloseSocket();
				_updateTriggerEvent.Set();
				if (!_manualMode)
				{
					_logicThread.Join();
					_logicThread = null;
				}
				ClearPeerSet();
				_peerIds = new ConcurrentQueue<int>();
				_lastPeerId = 0;
				_connectedPeersCount = 0L;
				_pendingEventHead = null;
				_pendingEventTail = null;
			}
		}

		[Conditional("DEBUG")]
		private void ClearPingSimulationList()
		{
			lock (_pingSimulationList)
			{
				_pingSimulationList.Clear();
			}
		}

		public int GetPeersCount(ConnectionState peerState)
		{
			int num = 0;
			_peersLock.EnterReadLock();
			for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
			{
				if ((netPeer.ConnectionState & peerState) != 0)
				{
					num++;
				}
			}
			_peersLock.ExitReadLock();
			return num;
		}

		public void GetPeersNonAlloc(List<NetPeer> peers, ConnectionState peerState)
		{
			peers.Clear();
			_peersLock.EnterReadLock();
			for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
			{
				if ((netPeer.ConnectionState & peerState) != 0)
				{
					peers.Add(netPeer);
				}
			}
			_peersLock.ExitReadLock();
		}

		public void DisconnectAll()
		{
			DisconnectAll(null, 0, 0);
		}

		public void DisconnectAll(byte[] data, int start, int count)
		{
			_peersLock.EnterReadLock();
			for (NetPeer netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
			{
				DisconnectPeer(netPeer, DisconnectReason.DisconnectPeerCalled, SocketError.Success, force: false, data, start, count, null);
			}
			_peersLock.ExitReadLock();
		}

		public void DisconnectPeerForce(NetPeer peer)
		{
			DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, SocketError.Success, null);
		}

		public void DisconnectPeer(NetPeer peer)
		{
			DisconnectPeer(peer, null, 0, 0);
		}

		public void DisconnectPeer(NetPeer peer, byte[] data)
		{
			DisconnectPeer(peer, data, 0, data.Length);
		}

		public void DisconnectPeer(NetPeer peer, NetDataWriter writer)
		{
			DisconnectPeer(peer, writer.Data, 0, writer.Length);
		}

		public void DisconnectPeer(NetPeer peer, byte[] data, int start, int count)
		{
			DisconnectPeer(peer, DisconnectReason.DisconnectPeerCalled, SocketError.Success, force: false, data, start, count, null);
		}

		public void CreateNtpRequest(IPEndPoint endPoint)
		{
			_ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint));
		}

		public void CreateNtpRequest(string ntpServerAddress, int port)
		{
			IPEndPoint iPEndPoint = NetUtils.MakeEndPoint(ntpServerAddress, port);
			_ntpRequests.TryAdd(iPEndPoint, new NtpRequest(iPEndPoint));
		}

		public void CreateNtpRequest(string ntpServerAddress)
		{
			IPEndPoint iPEndPoint = NetUtils.MakeEndPoint(ntpServerAddress, 123);
			_ntpRequests.TryAdd(iPEndPoint, new NtpRequest(iPEndPoint));
		}

		public NetPeerEnumerator GetEnumerator()
		{
			return new NetPeerEnumerator(_headPeer);
		}

		IEnumerator<NetPeer> IEnumerable<NetPeer>.GetEnumerator()
		{
			return new NetPeerEnumerator(_headPeer);
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return new NetPeerEnumerator(_headPeer);
		}

		private static int HashSetGetPrime(int min)
		{
			int[] primes = Primes;
			foreach (int num in primes)
			{
				if (num >= min)
				{
					return num;
				}
			}
			for (int j = min | 1; j < int.MaxValue; j += 2)
			{
				if (IsPrime(j) && (j - 1) % 101 != 0)
				{
					return j;
				}
			}
			return min;
			static bool IsPrime(int candidate)
			{
				if (((uint)candidate & (true ? 1u : 0u)) != 0)
				{
					int num2 = (int)Math.Sqrt(candidate);
					for (int k = 3; k <= num2; k += 2)
					{
						if (candidate % k == 0)
						{
							return false;
						}
					}
					return true;
				}
				return candidate == 2;
			}
		}

		private void ClearPeerSet()
		{
			_peersLock.EnterWriteLock();
			_headPeer = null;
			if (_lastIndex > 0)
			{
				Array.Clear(_slots, 0, _lastIndex);
				Array.Clear(_buckets, 0, _buckets.Length);
				_lastIndex = 0;
				_count = 0;
				_freeList = -1;
			}
			_peersArray = new NetPeer[32];
			_peersLock.ExitWriteLock();
		}

		private bool ContainsPeer(NetPeer item)
		{
			if (item == null)
			{
				NetDebug.WriteError($"Contains peer null: {item}");
				return false;
			}
			if (_buckets != null)
			{
				int num = item.GetHashCode() & 0x7FFFFFFF;
				for (int num2 = _buckets[num % _buckets.Length] - 1; num2 >= 0; num2 = _slots[num2].Next)
				{
					if (_slots[num2].HashCode == num && _slots[num2].Value.Equals(item))
					{
						return true;
					}
				}
			}
			return false;
		}

		public NetPeer GetPeerById(int id)
		{
			if (id < 0 || id >= _peersArray.Length)
			{
				return null;
			}
			return _peersArray[id];
		}

		public bool TryGetPeerById(int id, out NetPeer peer)
		{
			peer = GetPeerById(id);
			return peer != null;
		}

		private void AddPeer(NetPeer peer)
		{
			if (peer == null)
			{
				NetDebug.WriteError($"Add peer null: {peer}");
				return;
			}
			_peersLock.EnterWriteLock();
			if (_headPeer != null)
			{
				peer.NextPeer = _headPeer;
				_headPeer.PrevPeer = peer;
			}
			_headPeer = peer;
			AddPeerToSet(peer);
			if (peer.Id >= _peersArray.Length)
			{
				int num = _peersArray.Length * 2;
				while (peer.Id >= num)
				{
					num *= 2;
				}
				Array.Resize(ref _peersArray, num);
			}
			_peersArray[peer.Id] = peer;
			_peersLock.ExitWriteLock();
		}

		private void RemovePeer(NetPeer peer, bool enableWriteLock)
		{
			if (enableWriteLock)
			{
				_peersLock.EnterWriteLock();
			}
			if (!RemovePeerFromSet(peer))
			{
				if (enableWriteLock)
				{
					_peersLock.ExitWriteLock();
				}
				return;
			}
			if (peer == _headPeer)
			{
				_headPeer = peer.NextPeer;
			}
			if (peer.PrevPeer != null)
			{
				peer.PrevPeer.NextPeer = peer.NextPeer;
			}
			if (peer.NextPeer != null)
			{
				peer.NextPeer.PrevPeer = peer.PrevPeer;
			}
			peer.PrevPeer = null;
			_peersArray[peer.Id] = null;
			_peerIds.Enqueue(peer.Id);
			if (enableWriteLock)
			{
				_peersLock.ExitWriteLock();
			}
		}

		private bool RemovePeerFromSet(NetPeer peer)
		{
			if (_buckets == null || peer == null)
			{
				return false;
			}
			int num = peer.GetHashCode() & 0x7FFFFFFF;
			int num2 = num % _buckets.Length;
			int num3 = -1;
			for (int num4 = _buckets[num2] - 1; num4 >= 0; num4 = _slots[num4].Next)
			{
				if (_slots[num4].HashCode == num && _slots[num4].Value.Equals(peer))
				{
					if (num3 < 0)
					{
						_buckets[num2] = _slots[num4].Next + 1;
					}
					else
					{
						_slots[num3].Next = _slots[num4].Next;
					}
					_slots[num4].HashCode = -1;
					_slots[num4].Value = null;
					_slots[num4].Next = _freeList;
					_count--;
					if (_count == 0)
					{
						_lastIndex = 0;
						_freeList = -1;
					}
					else
					{
						_freeList = num4;
					}
					return true;
				}
				num3 = num4;
			}
			return false;
		}

		private bool TryGetPeer(IPEndPoint endPoint, out NetPeer actualValue)
		{
			if (_buckets != null)
			{
				int num = endPoint.GetHashCode() & 0x7FFFFFFF;
				_peersLock.EnterReadLock();
				for (int num2 = _buckets[num % _buckets.Length] - 1; num2 >= 0; num2 = _slots[num2].Next)
				{
					if (_slots[num2].HashCode == num && _slots[num2].Value.Equals(endPoint))
					{
						actualValue = _slots[num2].Value;
						_peersLock.ExitReadLock();
						return true;
					}
				}
				_peersLock.ExitReadLock();
			}
			actualValue = null;
			return false;
		}

		private bool TryGetPeer(SocketAddress saddr, out NetPeer actualValue)
		{
			if (_buckets != null)
			{
				int num = saddr.GetHashCode() & 0x7FFFFFFF;
				_peersLock.EnterReadLock();
				for (int num2 = _buckets[num % _buckets.Length] - 1; num2 >= 0; num2 = _slots[num2].Next)
				{
					if (_slots[num2].HashCode == num && _slots[num2].Value.Serialize().Equals(saddr))
					{
						actualValue = _slots[num2].Value;
						_peersLock.ExitReadLock();
						return true;
					}
				}
				_peersLock.ExitReadLock();
			}
			actualValue = null;
			return false;
		}

		private bool AddPeerToSet(NetPeer value)
		{
			if (_buckets == null)
			{
				int num = HashSetGetPrime(0);
				_buckets = new int[num];
				_slots = new Slot[num];
			}
			int num2 = value.GetHashCode() & 0x7FFFFFFF;
			int num3 = num2 % _buckets.Length;
			for (int num4 = _buckets[num2 % _buckets.Length] - 1; num4 >= 0; num4 = _slots[num4].Next)
			{
				if (_slots[num4].HashCode == num2 && _slots[num4].Value.Equals(value))
				{
					return false;
				}
			}
			int num5;
			if (_freeList >= 0)
			{
				num5 = _freeList;
				_freeList = _slots[num5].Next;
			}
			else
			{
				if (_lastIndex == _slots.Length)
				{
					int num6 = 2 * _count;
					num6 = (((uint)num6 > 2147483587u && 2147483587 > _count) ? 2147483587 : HashSetGetPrime(num6));
					Slot[] array = new Slot[num6];
					Array.Copy(_slots, 0, array, 0, _lastIndex);
					_buckets = new int[num6];
					for (int i = 0; i < _lastIndex; i++)
					{
						int num7 = array[i].HashCode % num6;
						array[i].Next = _buckets[num7] - 1;
						_buckets[num7] = i + 1;
					}
					_slots = array;
					num3 = num2 % _buckets.Length;
				}
				num5 = _lastIndex;
				_lastIndex++;
			}
			_slots[num5].HashCode = num2;
			_slots[num5].Value = value;
			_slots[num5].Next = _buckets[num3] - 1;
			_buckets[num3] = num5 + 1;
			_count++;
			return true;
		}

		private NetPacket PoolGetWithData(PacketProperty property, byte[] data, int start, int length)
		{
			int headerSize = NetPacket.GetHeaderSize(property);
			NetPacket netPacket = PoolGetPacket(length + headerSize);
			netPacket.Property = property;
			Buffer.BlockCopy(data, start, netPacket.RawData, headerSize, length);
			return netPacket;
		}

		private NetPacket PoolGetWithProperty(PacketProperty property, int size)
		{
			NetPacket netPacket = PoolGetPacket(size + NetPacket.GetHeaderSize(property));
			netPacket.Property = property;
			return netPacket;
		}

		private NetPacket PoolGetWithProperty(PacketProperty property)
		{
			NetPacket netPacket = PoolGetPacket(NetPacket.GetHeaderSize(property));
			netPacket.Property = property;
			return netPacket;
		}

		internal NetPacket PoolGetPacket(int size)
		{
			if (size > NetConstants.MaxPacketSize)
			{
				return new NetPacket(size);
			}
			NetPacket poolHead;
			lock (_poolLock)
			{
				poolHead = _poolHead;
				if (poolHead == null)
				{
					return new NetPacket(size);
				}
				_poolHead = _poolHead.Next;
				_poolCount--;
			}
			poolHead.Size = size;
			if (poolHead.RawData.Length < size)
			{
				poolHead.RawData = new byte[size];
			}
			return poolHead;
		}

		internal void PoolRecycle(NetPacket packet)
		{
			if (packet.RawData.Length > NetConstants.MaxPacketSize || _poolCount >= PacketPoolSize)
			{
				return;
			}
			packet.RawData[0] = 0;
			lock (_poolLock)
			{
				packet.Next = _poolHead;
				_poolHead = packet;
				_poolCount++;
			}
		}

		static NetManager()
		{
			Primes = new int[72]
			{
				3, 7, 11, 17, 23, 29, 37, 47, 59, 71,
				89, 107, 131, 163, 197, 239, 293, 353, 431, 521,
				631, 761, 919, 1103, 1327, 1597, 1931, 2333, 2801, 3371,
				4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, 17519, 21023,
				25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363,
				156437, 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403,
				968897, 1162687, 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559,
				5999471, 7199369
			};
			MulticastAddressV6 = IPAddress.Parse("ff02::1");
			IPv6Support = Socket.OSSupportsIPv6;
		}

		private bool ProcessError(SocketException ex)
		{
			switch (ex.SocketErrorCode)
			{
			case SocketError.NotConnected:
				NotConnected = true;
				return true;
			case SocketError.OperationAborted:
			case SocketError.Interrupted:
			case SocketError.NotSocket:
				return true;
			default:
				NetDebug.WriteError($"[R]Error code: {ex.SocketErrorCode} - {ex}");
				CreateEvent(NetEvent.EType.Error, null, null, ex.SocketErrorCode, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
				break;
			case SocketError.WouldBlock:
			case SocketError.MessageSize:
			case SocketError.NetworkReset:
			case SocketError.ConnectionReset:
			case SocketError.TimedOut:
				break;
			}
			return false;
		}

		private void ManualReceive(Socket socket, EndPoint bufferEndPoint, int maxReceive)
		{
			try
			{
				int num = 0;
				while (socket.Available > 0)
				{
					ReceiveFrom(socket, ref bufferEndPoint);
					num++;
					if (num == maxReceive)
					{
						break;
					}
				}
			}
			catch (SocketException ex)
			{
				ProcessError(ex);
			}
			catch (ObjectDisposedException)
			{
			}
			catch (Exception ex3)
			{
				NetDebug.WriteError("[NM] SocketReceiveThread error: " + ex3);
			}
		}

		private void NativeReceiveLogic()
		{
			IntPtr handle = _udpSocketv4.Handle;
			IntPtr s2 = _udpSocketv6?.Handle ?? IntPtr.Zero;
			byte[] address2 = new byte[16];
			byte[] address3 = new byte[28];
			IPEndPoint tempEndPoint = new IPEndPoint(IPAddress.Any, 0);
			List<Socket> list = new List<Socket>(2);
			Socket udpSocketv = _udpSocketv4;
			Socket udpSocketv2 = _udpSocketv6;
			NetPacket packet = PoolGetPacket(NetConstants.MaxPacketSize);
			while (IsRunning)
			{
				try
				{
					if (udpSocketv2 == null)
					{
						if (!NativeReceiveFrom(handle, address2))
						{
							break;
						}
						continue;
					}
					bool flag = false;
					if (udpSocketv.Available != 0 || list.Contains(udpSocketv))
					{
						if (!NativeReceiveFrom(handle, address2))
						{
							break;
						}
						flag = true;
					}
					if (udpSocketv2.Available != 0 || list.Contains(udpSocketv2))
					{
						if (!NativeReceiveFrom(s2, address3))
						{
							break;
						}
						flag = true;
					}
					list.Clear();
					if (!flag)
					{
						list.Add(udpSocketv);
						list.Add(udpSocketv2);
						Socket.Select(list, null, null, 500000);
					}
				}
				catch (SocketException ex)
				{
					if (ProcessError(ex))
					{
						break;
					}
				}
				catch (ObjectDisposedException)
				{
					break;
				}
				catch (ThreadAbortException)
				{
					break;
				}
				catch (Exception ex4)
				{
					NetDebug.WriteError("[NM] SocketReceiveThread error: " + ex4);
				}
			}
			bool NativeReceiveFrom(IntPtr s, byte[] address)
			{
				int socketAddressSize = address.Length;
				packet.Size = NativeSocket.RecvFrom(s, packet.RawData, NetConstants.MaxPacketSize, address, ref socketAddressSize);
				if (packet.Size == 0)
				{
					return true;
				}
				if (packet.Size == -1)
				{
					return !ProcessError(new SocketException((int)NativeSocket.GetSocketError()));
				}
				short num = (short)((address[1] << 8) | address[0]);
				tempEndPoint.Port = (ushort)((address[2] << 8) | address[3]);
				if ((NativeSocket.UnixMode && num == 10) || (!NativeSocket.UnixMode && num == 23))
				{
					uint num2 = (uint)((address[27] << 24) + (address[26] << 16) + (address[25] << 8) + address[24]);
					tempEndPoint.Address = new IPAddress(new ReadOnlySpan<byte>(address, 8, 16), num2);
				}
				else
				{
					long newAddress = (uint)((address[4] & 0xFF) | ((address[5] << 8) & 0xFF00) | ((address[6] << 16) & 0xFF0000) | (address[7] << 24));
					tempEndPoint.Address = new IPAddress(newAddress);
				}
				if (TryGetPeer(tempEndPoint, out var actualValue))
				{
					OnMessageReceived(packet, actualValue);
				}
				else
				{
					OnMessageReceived(packet, tempEndPoint);
					tempEndPoint = new IPEndPoint(IPAddress.Any, 0);
				}
				packet = PoolGetPacket(NetConstants.MaxPacketSize);
				return true;
			}
		}

		private void ReceiveFrom(Socket s, ref EndPoint bufferEndPoint)
		{
			NetPacket netPacket = PoolGetPacket(NetConstants.MaxPacketSize);
			netPacket.Size = s.ReceiveFrom(netPacket.RawData, 0, NetConstants.MaxPacketSize, SocketFlags.None, ref bufferEndPoint);
			OnMessageReceived(netPacket, (IPEndPoint)bufferEndPoint);
		}

		private void ReceiveLogic()
		{
			EndPoint bufferEndPoint = new IPEndPoint(IPAddress.Any, 0);
			EndPoint bufferEndPoint2 = new IPEndPoint(IPAddress.IPv6Any, 0);
			List<Socket> list = new List<Socket>(2);
			Socket udpSocketv = _udpSocketv4;
			Socket udpSocketv2 = _udpSocketv6;
			while (IsRunning)
			{
				try
				{
					if (udpSocketv2 == null)
					{
						if (udpSocketv.Available != 0 || udpSocketv.Poll(500000, SelectMode.SelectRead))
						{
							ReceiveFrom(udpSocketv, ref bufferEndPoint);
						}
						continue;
					}
					bool flag = false;
					if (udpSocketv.Available != 0 || list.Contains(udpSocketv))
					{
						ReceiveFrom(udpSocketv, ref bufferEndPoint);
						flag = true;
					}
					if (udpSocketv2.Available != 0 || list.Contains(udpSocketv2))
					{
						ReceiveFrom(udpSocketv2, ref bufferEndPoint2);
						flag = true;
					}
					list.Clear();
					if (!flag)
					{
						list.Add(udpSocketv);
						list.Add(udpSocketv2);
						Socket.Select(list, null, null, 500000);
					}
				}
				catch (SocketException ex)
				{
					if (ProcessError(ex))
					{
						break;
					}
				}
				catch (ObjectDisposedException)
				{
					break;
				}
				catch (ThreadAbortException)
				{
					break;
				}
				catch (Exception ex4)
				{
					NetDebug.WriteError("[NM] SocketReceiveThread error: " + ex4);
				}
			}
		}

		public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool manualMode)
		{
			if (IsRunning && !NotConnected)
			{
				return false;
			}
			NotConnected = false;
			_manualMode = manualMode;
			UseNativeSockets = UseNativeSockets && NativeSocket.IsSupported;
			_udpSocketv4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
			if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port)))
			{
				return false;
			}
			LocalPort = ((IPEndPoint)_udpSocketv4.LocalEndPoint).Port;
			IsRunning = true;
			if (_manualMode)
			{
				_bufferEndPointv4 = new IPEndPoint(IPAddress.Any, 0);
			}
			if (IPv6Support && IPv6Enabled)
			{
				_udpSocketv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
				if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort)))
				{
					if (_manualMode)
					{
						_bufferEndPointv6 = new IPEndPoint(IPAddress.IPv6Any, 0);
					}
				}
				else
				{
					_udpSocketv6 = null;
				}
			}
			if (!manualMode)
			{
				ThreadStart start = ReceiveLogic;
				if (UseNativeSockets)
				{
					start = NativeReceiveLogic;
				}
				_receiveThread = new Thread(start)
				{
					Name = $"ReceiveThread({LocalPort})",
					IsBackground = true
				};
				_receiveThread.Start();
				if (_logicThread == null)
				{
					_logicThread = new Thread(UpdateLogic)
					{
						Name = "LogicThread",
						IsBackground = true
					};
					_logicThread.Start();
				}
			}
			return true;
		}

		private bool BindSocket(Socket socket, IPEndPoint ep)
		{
			socket.ReceiveTimeout = 500;
			socket.SendTimeout = 500;
			socket.ReceiveBufferSize = 1048576;
			socket.SendBufferSize = 1048576;
			socket.Blocking = true;
			if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
			{
				try
				{
					socket.IOControl(-1744830452, new byte[1], null);
				}
				catch
				{
				}
			}
			try
			{
				socket.ExclusiveAddressUse = !ReuseAddress;
				socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, ReuseAddress);
				socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, DontRoute);
			}
			catch
			{
			}
			if (ep.AddressFamily == AddressFamily.InterNetwork)
			{
				Ttl = 255;
				try
				{
					socket.EnableBroadcast = true;
				}
				catch (SocketException ex)
				{
					NetDebug.WriteError($"[B]Broadcast error: {ex.SocketErrorCode}");
				}
				if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
				{
					try
					{
						socket.DontFragment = true;
					}
					catch (SocketException ex2)
					{
						NetDebug.WriteError($"[B]DontFragment error: {ex2.SocketErrorCode}");
					}
				}
			}
			try
			{
				socket.Bind(ep);
				if (ep.AddressFamily == AddressFamily.InterNetworkV6)
				{
					try
					{
						socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.AddMembership, new IPv6MulticastOption(MulticastAddressV6));
					}
					catch (Exception)
					{
					}
				}
			}
			catch (SocketException ex4)
			{
				switch (ex4.SocketErrorCode)
				{
				case SocketError.AddressAlreadyInUse:
					if (socket.AddressFamily == AddressFamily.InterNetworkV6)
					{
						try
						{
							socket.DualMode = false;
							socket.Bind(ep);
						}
						catch (SocketException ex5)
						{
							NetDebug.WriteError($"[B]Bind exception: {ex5}, errorCode: {ex5.SocketErrorCode}");
							return false;
						}
						return true;
					}
					break;
				case SocketError.AddressFamilyNotSupported:
					return true;
				}
				NetDebug.WriteError($"[B]Bind exception: {ex4}, errorCode: {ex4.SocketErrorCode}");
				return false;
			}
			return true;
		}

		internal int SendRawAndRecycle(NetPacket packet, IPEndPoint remoteEndPoint)
		{
			int result = SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
			PoolRecycle(packet);
			return result;
		}

		internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint)
		{
			return SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
		}

		internal unsafe int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
		{
			if (!IsRunning)
			{
				return 0;
			}
			NetPacket netPacket = null;
			if (_extraPacketLayer != null)
			{
				netPacket = PoolGetPacket(length + _extraPacketLayer.ExtraPacketSizeForLayer);
				Buffer.BlockCopy(message, start, netPacket.RawData, 0, length);
				start = 0;
				_extraPacketLayer.ProcessOutBoundPacket(ref remoteEndPoint, ref netPacket.RawData, ref start, ref length);
				message = netPacket.RawData;
			}
			Socket socket = _udpSocketv4;
			if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Support)
			{
				socket = _udpSocketv6;
				if (socket == null)
				{
					return 0;
				}
			}
			int num;
			try
			{
				if (UseNativeSockets && remoteEndPoint is NetPeer netPeer)
				{
					fixed (byte* pinnedBuffer = &message[start])
					{
						num = NativeSocket.SendTo(socket.Handle, pinnedBuffer, length, netPeer.NativeAddress, netPeer.NativeAddress.Length);
					}
					if (num == -1)
					{
						throw NativeSocket.GetSocketException();
					}
				}
				else
				{
					num = socket.SendTo(message, start, length, SocketFlags.None, remoteEndPoint);
				}
			}
			catch (SocketException ex)
			{
				switch (ex.SocketErrorCode)
				{
				case SocketError.Interrupted:
				case SocketError.NoBufferSpaceAvailable:
					return 0;
				case SocketError.MessageSize:
					return 0;
				case SocketError.NetworkUnreachable:
				case SocketError.HostUnreachable:
					if (DisconnectOnUnreachable && remoteEndPoint is NetPeer peer)
					{
						DisconnectPeerForce(peer, (ex.SocketErrorCode == SocketError.HostUnreachable) ? DisconnectReason.HostUnreachable : DisconnectReason.NetworkUnreachable, ex.SocketErrorCode, null);
					}
					CreateEvent(NetEvent.EType.Error, null, remoteEndPoint, ex.SocketErrorCode, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
					return -1;
				case SocketError.Shutdown:
					CreateEvent(NetEvent.EType.Error, null, remoteEndPoint, ex.SocketErrorCode, 0, DisconnectReason.ConnectionFailed, null, DeliveryMethod.Unreliable, 0);
					return -1;
				default:
					NetDebug.WriteError($"[S] {ex}");
					return -1;
				}
			}
			catch (Exception value)
			{
				NetDebug.WriteError($"[S] {value}");
				return 0;
			}
			finally
			{
				if (netPacket != null)
				{
					PoolRecycle(netPacket);
				}
			}
			if (num <= 0)
			{
				return 0;
			}
			if (EnableStatistics)
			{
				Statistics.IncrementPacketsSent();
				Statistics.AddBytesSent(length);
			}
			return num;
		}

		public bool SendBroadcast(NetDataWriter writer, int port)
		{
			return SendBroadcast(writer.Data, 0, writer.Length, port);
		}

		public bool SendBroadcast(byte[] data, int port)
		{
			return SendBroadcast(data, 0, data.Length, port);
		}

		public bool SendBroadcast(byte[] data, int start, int length, int port)
		{
			if (!IsRunning)
			{
				return false;
			}
			NetPacket netPacket;
			if (_extraPacketLayer != null)
			{
				int headerSize = NetPacket.GetHeaderSize(PacketProperty.Broadcast);
				netPacket = PoolGetPacket(headerSize + length + _extraPacketLayer.ExtraPacketSizeForLayer);
				netPacket.Property = PacketProperty.Broadcast;
				Buffer.BlockCopy(data, start, netPacket.RawData, headerSize, length);
				int offset = 0;
				int length2 = length + headerSize;
				IPEndPoint endPoint = null;
				_extraPacketLayer.ProcessOutBoundPacket(ref endPoint, ref netPacket.RawData, ref offset, ref length2);
			}
			else
			{
				netPacket = PoolGetWithData(PacketProperty.Broadcast, data, start, length);
			}
			bool flag = false;
			bool flag2 = false;
			try
			{
				flag = _udpSocketv4.SendTo(netPacket.RawData, 0, netPacket.Size, SocketFlags.None, new IPEndPoint(IPAddress.Broadcast, port)) > 0;
				if (_udpSocketv6 != null)
				{
					flag2 = _udpSocketv6.SendTo(netPacket.RawData, 0, netPacket.Size, SocketFlags.None, new IPEndPoint(MulticastAddressV6, port)) > 0;
				}
			}
			catch (SocketException ex)
			{
				if (ex.SocketErrorCode == SocketError.HostUnreachable)
				{
					return flag;
				}
				NetDebug.WriteError($"[S][MCAST] {ex}");
				return flag;
			}
			catch (Exception value)
			{
				NetDebug.WriteError($"[S][MCAST] {value}");
				return flag;
			}
			finally
			{
				PoolRecycle(netPacket);
			}
			return flag || flag2;
		}

		private void CloseSocket()
		{
			IsRunning = false;
			_udpSocketv4?.Close();
			_udpSocketv6?.Close();
			_udpSocketv4 = null;
			_udpSocketv6 = null;
			if (_receiveThread != null && _receiveThread != Thread.CurrentThread)
			{
				_receiveThread.Join();
			}
			_receiveThread = null;
		}
	}
	internal enum PacketProperty : byte
	{
		Unreliable,
		Channeled,
		Ack,
		Ping,
		Pong,
		ConnectRequest,
		ConnectAccept,
		Disconnect,
		UnconnectedMessage,
		MtuCheck,
		MtuOk,
		Broadcast,
		Merged,
		ShutdownOk,
		PeerNotFound,
		InvalidProtocol,
		NatMessage,
		Empty
	}
	internal sealed class NetPacket
	{
		private static readonly int PropertiesCount;

		private static readonly int[] HeaderSizes;

		public byte[] RawData;

		public int Size;

		public object UserData;

		public NetPacket Next;

		public PacketProperty Property
		{
			get
			{
				return (PacketProperty)(RawData[0] & 0x1Fu);
			}
			set
			{
				RawData[0] = (byte)((RawData[0] & 0xE0u) | (uint)value);
			}
		}

		public byte ConnectionNumber
		{
			get
			{
				return (byte)((RawData[0] & 0x60) >> 5);
			}
			set
			{
				RawData[0] = (byte)((RawData[0] & 0x9Fu) | (uint)(value << 5));
			}
		}

		public ushort Sequence
		{
			get
			{
				return BitConverter.ToUInt16(RawData, 1);
			}
			set
			{
				FastBitConverter.GetBytes(RawData, 1, value);
			}
		}

		public bool IsFragmented => (RawData[0] & 0x80) != 0;

		public byte ChannelId
		{
			get
			{
				return RawData[3];
			}
			set
			{
				RawData[3] = value;
			}
		}

		public ushort FragmentId
		{
			get
			{
				return BitConverter.ToUInt16(RawData, 4);
			}
			set
			{
				FastBitConverter.GetBytes(RawData, 4, value);
			}
		}

		public ushort FragmentPart
		{
			get
			{
				return BitConverter.ToUInt16(RawData, 6);
			}
			set
			{
				FastBitConverter.GetBytes(RawData, 6, value);
			}
		}

		public ushort FragmentsTotal
		{
			get
			{
				return BitConverter.ToUInt16(RawData, 8);
			}
			set
			{
				FastBitConverter.GetBytes(RawData, 8, value);
			}
		}

		static NetPacket()
		{
			PropertiesCount = Enum.GetValues(typeof(PacketProperty)).Length;
			HeaderSizes = NetUtils.AllocatePinnedUninitializedArray<int>(PropertiesCount);
			for (int i = 0; i < HeaderSizes.Length; i++)
			{
				switch ((PacketProperty)(byte)i)
				{
				case PacketProperty.Channeled:
				case PacketProperty.Ack:
					HeaderSizes[i] = 4;
					break;
				case PacketProperty.Ping:
					HeaderSizes[i] = 3;
					break;
				case PacketProperty.ConnectRequest:
					HeaderSizes[i] = 18;
					break;
				case PacketProperty.ConnectAccept:
					HeaderSizes[i] = 15;
					break;
				case PacketProperty.Disconnect:
					HeaderSizes[i] = 9;
					break;
				case PacketProperty.Pong:
					HeaderSizes[i] = 11;
					break;
				default:
					HeaderSizes[i] = 1;
					break;
				}
			}
		}

		public void MarkFragmented()
		{
			RawData[0] |= 128;
		}

		public NetPacket(int size)
		{
			RawData = new byte[size];
			Size = size;
		}

		public NetPacket(PacketProperty property, int size)
		{
			size += GetHeaderSize(property);
			RawData = new byte[size];
			Property = property;
			Size = size;
		}

		public static int GetHeaderSize(PacketProperty property)
		{
			return HeaderSizes[(uint)property];
		}

		public int GetHeaderSize()
		{
			return HeaderSizes[RawData[0] & 0x1F];
		}

		public bool Verify()
		{
			byte b = (byte)(RawData[0] & 0x1Fu);
			if (b >= PropertiesCount)
			{
				return false;
			}
			int num = HeaderSizes[b];
			bool flag = (RawData[0] & 0x80) != 0;
			if (Size >= num)
			{
				if (flag)
				{
					return Size >= num + 6;
				}
				return true;
			}
			return false;
		}

		public static implicit operator Span<byte>(NetPacket p)
		{
			return new Span<byte>(p.RawData, 0, p.Size);
		}
	}
	[Flags]
	public enum ConnectionState : byte
	{
		Outgoing = 2,
		Connected = 4,
		ShutdownRequested = 8,
		Disconnected = 0x10,
		EndPointChange = 0x20,
		Any = 0x2E
	}
	internal enum ConnectRequestResult
	{
		None,
		P2PLose,
		Reconnection,
		NewConnection
	}
	internal enum DisconnectResult
	{
		None,
		Reject,
		Disconnect
	}
	internal enum ShutdownResult
	{
		None,
		Success,
		WasConnected
	}
	public class NetPeer : IPEndPoint
	{
		private class IncomingFragments
		{
			public NetPacket[] Fragments;

			public int ReceivedCount;

			public int TotalSize;

			public byte ChannelId;
		}

		private int _rtt;

		private int _avgRtt;

		private int _rttCount;

		private double _resendDelay = 27.0;

		private float _pingSendTimer;

		private float _rttResetTimer;

		private readonly Stopwatch _pingTimer = new Stopwatch();

		private volatile float _timeSinceLastPacket;

		private long _remoteDelta;

		private readonly object _shutdownLock = new object();

		internal volatile NetPeer NextPeer;

		internal NetPeer PrevPeer;

		private NetPacket[] _unreliableSecondQueue;

		private NetPacket[] _unreliableChannel;

		private int _unreliablePendingCount;

		private readonly object _unreliableChannelLock = new object();

		private readonly ConcurrentQueue<BaseChannel> _channelSendQueue;

		private readonly BaseChannel[] _channels;

		private int _mtu;

		private int _mtuIdx;

		private bool _finishMtu;

		private float _mtuCheckTimer;

		private int _mtuCheckAttempts;

		private const int MtuCheckDelay = 1000;

		private const int MaxMtuCheckAttempts = 4;

		private readonly object _mtuMutex = new object();

		private int _fragmentId;

		private readonly Dictionary<ushort, IncomingFragments> _holdedFragments;

		private readonly Dictionary<ushort, ushort> _deliveredFragments;

		private readonly NetPacket _mergeData;

		private int _mergePos;

		private int _mergeCount;

		private int _connectAttempts;

		private float _connectTimer;

		private long _connectTime;

		private byte _connectNum;

		private ConnectionState _connectionState;

		private NetPacket _shutdownPacket;

		private const int ShutdownDelay = 300;

		private float _shutdownTimer;

		private readonly NetPacket _pingPacket;

		private readonly NetPacket _pongPacket;

		private readonly NetPacket _connectRequestPacket;

		private readonly NetPacket _connectAcceptPacket;

		public readonly NetManager NetManager;

		public readonly int Id;

		public object Tag;

		public readonly NetStatistics Statistics;

		private SocketAddress _cachedSocketAddr;

		private int _cachedHashCode;

		internal byte[] NativeAddress;

		internal byte ConnectionNum
		{
			get
			{
				return _connectNum;
			}
			private set
			{
				_connectNum = value;
				_mergeData.ConnectionNumber = value;
				_pingPacket.ConnectionNumber = value;
				_pongPacket.ConnectionNumber = value;
			}
		}

		public ConnectionState ConnectionState => _connectionState;

		internal long ConnectTime => _connectTime;

		public int RemoteId { get; private set; }

		public int Ping => _avgRtt / 2;

		public int RoundTripTime => _avgRtt;

		public int Mtu => _mtu;

		public long RemoteTimeDelta => _remoteDelta;

		public DateTime RemoteUtcTime => new DateTime(DateTime.UtcNow.Ticks + _remoteDelta);

		public float TimeSinceLastPacket => _timeSinceLastPacket;

		internal double ResendDelay => _resendDelay;

		public override SocketAddress Serialize()
		{
			return _cachedSocketAddr;
		}

		public override int GetHashCode()
		{
			return _cachedHashCode;
		}

		internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id)
			: base(remoteEndPoint.Address, remoteEndPoint.Port)
		{
			Id = id;
			Statistics = new NetStatistics();
			NetManager = netManager;
			_cachedSocketAddr = base.Serialize();
			if (NetManager.UseNativeSockets)
			{
				NativeAddress = new byte[_cachedSocketAddr.Size];
				for (int i = 0; i < _cachedSocketAddr.Size; i++)
				{
					NativeAddress[i] = _cachedSocketAddr[i];
				}
			}
			_cachedHashCode = base.GetHashCode();
			ResetMtu();
			_connectionState = ConnectionState.Connected;
			_mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize);
			_pongPacket = new NetPacket(PacketProperty.Pong, 0);
			_pingPacket = new NetPacket(PacketProperty.Ping, 0)
			{
				Sequence = 1
			};
			_unreliableSecondQueue = new NetPacket[8];
			_unreliableChannel = new NetPacket[8];
			_holdedFragments = new Dictionary<ushort, IncomingFragments>();
			_deliveredFragments = new Dictionary<ushort, ushort>();
			_channels = new BaseChannel[netManager.ChannelsCount * 4];
			_channelSendQueue = new ConcurrentQueue<BaseChannel>();
		}

		internal void InitiateEndPointChange()
		{
			ResetMtu();
			_connectionState = ConnectionState.EndPointChange;
		}

		internal void FinishEndPointChange(IPEndPoint newEndPoint)
		{
			if (_connectionState != ConnectionState.EndPointChange)
			{
				return;
			}
			_connectionState = ConnectionState.Connected;
			base.Address = newEndPoint.Address;
			base.Port = newEndPoint.Port;
			if (NetManager.UseNativeSockets)
			{
				NativeAddress = new byte[_cachedSocketAddr.Size];
				for (int i = 0; i < _cachedSocketAddr.Size; i++)
				{
					NativeAddress[i] = _cachedSocketAddr[i];
				}
			}
			_cachedSocketAddr = base.Serialize();
			_cachedHashCode = base.GetHashCode();
		}

		internal void ResetMtu()
		{
			_finishMtu = !NetManager.MtuDiscovery;
			if (NetManager.MtuOverride > 0)
			{
				OverrideMtu(NetManager.MtuOverride);
			}
			else
			{
				SetMtu(0);
			}
		}

		private void SetMtu(int mtuIdx)
		{
			_mtuIdx = mtuIdx;
			_mtu = NetConstants.PossibleMtu[mtuIdx] - NetManager.ExtraPacketSizeForLayer;
		}

		private void OverrideMtu(int mtuValue)
		{
			_mtu = mtuValue;
			_finishMtu = true;
		}

		public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered)
		{
			int num = channelNumber * 4 + (ordered ? 2 : 0);
			BaseChannel baseChannel = _channels[num];
			if (baseChannel == null)
			{
				return 0;
			}
			return ((ReliableChannel)baseChannel).PacketsInQueue;
		}

		public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod, byte channelNumber)
		{
			int mtu = _mtu;
			NetPacket netPacket = NetManager.PoolGetPacket(mtu);
			if (deliveryMethod == DeliveryMethod.Unreliable)
			{
				netPacket.Property = PacketProperty.Unreliable;
				return new PooledPacket(netPacket, mtu, 0);
			}
			netPacket.Property = PacketProperty.Channeled;
			return new PooledPacket(netPacket, mtu, (byte)((uint)(channelNumber * 4) + (ui