Decompiled source of AI LIMIT Coop v0.3.10

BepInEx/plugins/AILimitCoop/AILimitCoop.dll

Decompiled 11 hours ago
using System;
using System.Collections;
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 AtmosphericHeightFog;
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 Bullets;
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 Logic.BossRush;
using Logic.RoguelikeAbility;
using Microsoft.CodeAnalysis;
using MonsterGroupManager;
using PlayerShowSateDefine;
using ProtoUtility;
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.Playables;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;

[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.3.10.0")]
[assembly: AssemblyInformationalVersion("0.3.8")]
[assembly: AssemblyProduct("AI LIMIT Co-op")]
[assembly: AssemblyTitle("AILimitCoop")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.3.10.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<bool> SyncEnemyBreakState;

		public static ConfigEntry<bool> SyncEnemyEffects;

		public static ConfigEntry<bool> SyncPlayerEffects;

		public static ConfigEntry<bool> SyncPlayerProjectiles;

		public static ConfigEntry<bool> SyncPlayerWorldEffects;

		public static ConfigEntry<bool> SyncRogueLayout;

		public static ConfigEntry<bool> AssistBossWake;

		public static ConfigEntry<bool> SyncRogueTrials;

		public static ConfigEntry<bool> SyncRogueStageExits;

		public static ConfigEntry<bool> SyncExecutions;

		public static ConfigEntry<bool> SyncRoomClears;

		public static ConfigEntry<float> EnemyHpPerExtraPlayer;

		public static ConfigEntry<DifficultyPreset> Difficulty;

		public static ConfigEntry<float> BossHpPerExtraPlayer;

		public static ConfigEntry<float> BreakResistPerExtraPlayer;

		public static ConfigEntry<bool> AllowPvp;

		public static ConfigEntry<PvpDamagePreset> PvpDamage;

		public static ConfigEntry<bool> PvpArena;

		public static ConfigEntry<bool> GhostTargeting;

		public static ConfigEntry<HitDetectionMode> HitDetection;

		public static ConfigEntry<bool> GhostEquipment;

		public static ConfigEntry<bool> GhostCollision;

		public static ConfigEntry<bool> ShowFallenPlayers;

		public static ConfigEntry<bool> SyncWeakPoints;

		public static ConfigEntry<bool> ShowAllySync;

		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 const string DevFileName = "com.ailimit.coop.dev.cfg";

		public static DifficultySettings EffectiveDifficulty => CoopDifficulty.For(Difficulty.Value, EnemyHpPerExtraPlayer.Value, BossHpPerExtraPlayer.Value, BreakResistPerExtraPlayer.Value);

		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 bool DevFile { get; private set; }

		public static int RetiredDropped { get; private set; }

		public static void Bind(ConfigFile cfg)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Expected O, but got Unknown
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Expected O, but got Unknown
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Expected O, but got Unknown
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Expected O, but got Unknown
			//IL_025a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0264: Expected O, but got Unknown
			//IL_032a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0334: Expected O, but got Unknown
			//IL_0350: Unknown result type (might be due to invalid IL or missing references)
			//IL_035a: Expected O, but got Unknown
			//IL_0445: Unknown result type (might be due to invalid IL or missing references)
			//IL_044f: Expected O, but got Unknown
			//IL_047d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0487: Expected O, but got Unknown
			//IL_04b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_04bf: Expected O, but got Unknown
			//IL_067e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0688: Expected O, but got Unknown
			//IL_06b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_06c0: Expected O, but got Unknown
			//IL_073f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0749: Expected O, but got Unknown
			//IL_0831: Unknown result type (might be due to invalid IL or missing references)
			//IL_083b: Expected O, but got Unknown
			//IL_0861: Unknown result type (might be due to invalid IL or missing references)
			//IL_086b: Expected O, but got Unknown
			//IL_088e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0898: Expected O, but got Unknown
			string text = Path.Combine(Path.GetDirectoryName(cfg.ConfigFilePath) ?? "", "com.ailimit.coop.dev.cfg");
			DevFile = File.Exists(text);
			ConfigFile val = new ConfigFile(text, false)
			{
				SaveOnConfigSet = false
			};
			bool flag = false;
			try
			{
				flag = File.Exists(cfg.ConfigFilePath) && !Regex.IsMatch(File.ReadAllText(cfg.ConfigFilePath), "^\\s*Difficulty\\s*=", RegexOptions.Multiline);
			}
			catch (IOException)
			{
			}
			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: host and join through your Steam friends list - no address or port forwarding needed.");
			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 = val.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 = val.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 = val.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 = val.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 = val.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 = val.Bind<float>("Network", "GhostAnimCrossFade", 0.06f, "Seconds to fade when another player's animation state changes, instead of cutting. 0 disables.");
			SyncMonsters = val.Bind<bool>("Gameplay", "SyncMonsters", true, "Share enemies, so everyone fights the same ones. Off: each player fights their own copy of every enemy and only the players themselves are shared.");
			SyncEnemyBreakState = val.Bind<bool>("Gameplay", "SyncEnemyBreakState", true, "Show the stagger gauge of enemies another player's game runs, and break them when it breaks there, so an execution can be offered. Disable if a broken enemy misbehaves on your machine.");
			SyncEnemyEffects = val.Bind<bool>("Gameplay", "SyncEnemyEffects", true, "Show the attack effects, attack sounds and weapon trails of enemies that another player's game runs. Cosmetic only; disable if it causes problems.");
			SyncPlayerEffects = val.Bind<bool>("Gameplay", "SyncPlayerEffects", true, "Show the spell, skill and weapon-art effects of the other players. Cosmetic only.");
			SyncPlayerProjectiles = val.Bind<bool>("Gameplay", "SyncPlayerProjectiles", true, new ConfigDescription("Show the flying projectiles of other players' spells and skills. The copy is visual only: it cannot hit anyone, and damage is unaffected.", (AcceptableValueBase)null, Array.Empty<object>()));
			SyncPlayerWorldEffects = val.Bind<bool>("Gameplay", "SyncPlayerWorldEffects", true, new ConfigDescription("Show the effects other players' abilities put into the world: a lightning strike where it was aimed, its impact, a buff flash, a weapon art's slash, a spell's flames on their blade. Visual only.", (AcceptableValueBase)null, Array.Empty<object>()));
			SyncRogueLayout = val.Bind<bool>("Gameplay", "SyncRogueLayout", true, "Furnace of War: every player fights the same rooms in a run, and the second stage is made of the same maps for everyone. Off makes each game roll its own, and each player then plays every stage alone.");
			AssistBossWake = val.Bind<bool>("Gameplay", "AssistBossWake", true, "While the host is dead and spectating, a boss starts for the living player standing in front of it. Off leaves a run unfinishable once the host is down.");
			SyncRoomClears = val.Bind<bool>("Gameplay", "SyncRoomClears", true, "When a DLC room is cleared on another player's game and not on yours - a Furnace room whose doors stay shut, a Boss Rush room with no exit rift - open it on yours too.");
			SyncRogueTrials = val.Bind<bool>("Gameplay", "SyncRogueTrials", true, "Furnace of War: a spectating player's game enters a trial when a teammate does, so the arena and its boss exist for the game that simulates them. Living players always enter their own.");
			SyncRogueStageExits = val.Bind<bool>("Gameplay", "SyncRogueStageExits", true, "Furnace of War: when a teammate takes the portal to the next stage while you are down, you get up and follow them instead of being left behind, which ended your run. Living players are told and take the portal themselves.");
			SyncExecutions = val.Bind<bool>("Gameplay", "SyncExecutions", true, "An execution (finisher) of a broken enemy, or a backstab, on an enemy that another player's game runs deals your damage there. Without it they do no damage. Executions are new in 0.3.0 and backstabs in 0.3.1; turn it off if either misbehaves in co-op.");
			Difficulty = cfg.Bind<DifficultyPreset>("Gameplay", "Difficulty", DifficultyPreset.Balanced, "Host setting, shared when players join. How much tougher enemies get for each extra player in the same area. Balanced: enemies +75% and bosses +100% HP per extra player, stagger/sync-rate breaks +50% harder per extra player. Hard: enemies +100%, bosses +150%, breaks +100% harder. Challenge: enemies +150%, bosses +200%, breaks +150% harder. Custom: the three values below. Change before hosting a new session.");
			EnemyHpPerExtraPlayer = cfg.Bind<float>("Gameplay", "EnemyHpPerExtraPlayer", 0.5f, new ConfigDescription("Difficulty = Custom only. Extra HP for ordinary enemies per extra player in the area: 0.5 = +50%, 0 disables. Combat locks the maximum until rest/reset.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			BossHpPerExtraPlayer = cfg.Bind<float>("Gameplay", "BossHpPerExtraPlayer", 1f, new ConfigDescription("Difficulty = Custom only. Extra HP for bosses and mini-bosses per extra player in the area: 1 = +100%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			BreakResistPerExtraPlayer = cfg.Bind<float>("Gameplay", "BreakResistPerExtraPlayer", 0.5f, new ConfigDescription("Difficulty = Custom only. How much slower enemies stagger and lose sync rate per extra player: damage to those gauges is divided by 1 + this x (players - 1). 0 = the game's own rate.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			if (flag && Math.Abs(EnemyHpPerExtraPlayer.Value - 0.5f) > 0.001f)
			{
				Difficulty.Value = DifficultyPreset.Custom;
				BossHpPerExtraPlayer.Value = EnemyHpPerExtraPlayer.Value;
				BreakResistPerExtraPlayer.Value = 0f;
			}
			AllowPvp = cfg.Bind<bool>("Gameplay", "AllowPvp", true, "Host setting, shared when players join. Lets players turn PvP on for themselves (party menu, Rules). PvP only ever applies between two players who have both turned it on, and it starts off in every session. Off: no one in your sessions can turn it on.");
			PvpDamage = cfg.Bind<PvpDamagePreset>("Gameplay", "PvpDamage", PvpDamagePreset.Standard, "Host setting, shared when players join. How hard players hit each other in PvP, as a share of an attack's usual damage (player attacks are tuned for fighting enemies, so at full strength a few hits end a duel). Low: weapons 35%, spells and items 20%. Standard: weapons 50%, spells and items 30%. High: weapons 75%, spells and items 50%. Full: 100% (enemy-strength damage).");
			PvpArena = cfg.Bind<bool>("Gameplay", "PvpArena", true, "Lists the PvP Arena in the teleport menu of every rest point (under PvP): a walled floor high above the Southwest with its own rest point, for duels. Rest there to teleport back out. Saves made in the arena resume at the rest point you came from.");
			GhostTargeting = val.Bind<bool>("Gameplay", "GhostTargeting", true, "Enemies pick which player to attack by who hurts them and who is close, as they would among allies in the base game. Off: an enemy only attacks the player whose game runs it.");
			HitDetection = val.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 = val.Bind<bool>("Gameplay", "GhostEquipment", true, "Show other players' armour and weapons. Turn off if other players' characters fail to appear.");
			ShowFallenPlayers = val.Bind<bool>("Gameplay", "ShowFallenPlayers", true, "When another player dies, their character stays where they fell and turns to mud like a death in the game, then disappears until they are revived. Turn off to keep the old behaviour.");
			SyncWeakPoints = val.Bind<bool>("Gameplay", "SyncWeakPoints", false, "Experimental, host only: share boss weak points (shields and crystals on some bosses) so breaking one breaks it for everyone. Every player follows the host's setting. Off by default until it has been tested in a real fight.");
			GhostCollision = val.Bind<bool>("Gameplay", "GhostCollision", false, "Players physically block each other instead of passing through one another.");
			ForceRunInBackground = val.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 = val.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.");
			ShowAllySync = cfg.Bind<bool>("UI", "ShowAllySync", true, "Show each teammate's sync rate as a thin blue bar under their health, on the party cards and above their head.");
			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 = val.Bind<bool>("Debug", "Verbose", false, "Log per-hit and per-spawn diagnostics to BepInEx/LogOutput.log.");
			DevCommands = val.Bind<bool>("Debug", "DevCommands", false, "Poll <GameDir>/BepInEx/coop_cmd.txt for developer commands (host, join, leave, dump, ...). Development aid only.");
			PatchGroups = val.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 = val.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 = val.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 = val.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>()));
			DropRetired(cfg);
			Log.Info("[Config] advanced settings " + (DevFile ? "read from com.ailimit.coop.dev.cfg" : "at their defaults") + ((RetiredDropped > 0) ? $"; {RetiredDropped} unused entries removed from the config file" : ""));
		}

		private static void DropRetired(ConfigFile cfg)
		{
			try
			{
				if (AccessTools.Property(typeof(ConfigFile), "OrphanedEntries")?.GetValue(cfg) is IDictionary { Count: >0 } dictionary)
				{
					RetiredDropped = dictionary.Count;
					dictionary.Clear();
				}
			}
			catch (Exception ex)
			{
				Log.Warn("[Config] could not tidy the config file: " + ex.Message);
			}
		}

		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 MonsterFxSync EnemyFx;

		public readonly PlayerFxSync PlayerFx;

		public readonly PlayerProjectileSync PlayerShots;

		public readonly PlayerWorldFxSync PlayerWorldFx;

		public readonly RogueLayoutSync RogueLayout;

		public readonly BossWakeAssist BossWake;

		public readonly RogueTrialSync RogueTrial;

		public readonly RogueStageSync RogueStage;

		public readonly RoomClearSync RoomClear;

		public readonly SummonSync Summons;

		public readonly GuardianFightSync Guardians;

		public readonly WeakPointSync WeakPoints;

		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 BossProgressMirror Progress;

		public readonly PartyLife Life;

		public readonly DefenseWatch Defense = new DefenseWatch();

		public BossMusicTracker Music;

		public readonly MonsterThreat Threat;

		public readonly TeammateTriggers Triggers;

		public readonly SharedWorld World;

		public readonly BossEncounterSync Encounters;

		public readonly BossPresentationSync Presentation;

		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 bool _hostedLast;

		private int _frames;

		private readonly SessionPerformance _performance = new SessionPerformance();

		private int _hostAttempts;

		private float _nextHostAttempt;

		private bool _firstGui = true;

		private int _guiCalls;

		private const float IdleStateInterval = 0.1f;

		private float _lastStateSentAt = -10f;

		private float _sentX;

		private float _sentY;

		private float _sentZ;

		private float _sentYaw;

		private float _sentHp;

		private float _sentSync;

		private PlayerFlags _sentFlags;

		private int _sentLevel;

		private int _sentLayers;

		private readonly int[] _sentLayerHashes = new int[6];

		private byte _sentShield;

		private byte _sentEnchant;

		private readonly HashSet<byte> _apartPeers = new HashSet<byte>();

		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;
			PvpCombat.Attach(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);
			EnemyFx = new MonsterFxSync(this);
			PlayerFx = new PlayerFxSync(this);
			PlayerShots = new PlayerProjectileSync(this);
			PlayerWorldFx = new PlayerWorldFxSync(this);
			RogueLayout = new RogueLayoutSync(this);
			Session.SeparateBuild = delegate(int level, int build)
			{
				int currentLevelId = GameState.CurrentLevelId;
				return RogueLayoutSync.Separate(level, build, currentLevelId, RogueLayout.BuildOf(currentLevelId));
			};
			BossWake = new BossWakeAssist(this);
			RogueTrial = new RogueTrialSync(this);
			RogueStage = new RogueStageSync(this);
			RoomClear = new RoomClearSync(this);
			Summons = new SummonSync(this);
			Authority.PinToOwner = Summons.HelperOwnerOf;
			Guardians = new GuardianFightSync(this);
			WeakPoints = new WeakPointSync(this);
			Log.Trace("runtime ctor: sync objects");
			UI = new CoopUI(this);
			MenuFocus = new MenuInputFocus(this);
			Rewards = new BossRewardSync(this);
			Progress = new BossProgressMirror(this);
			Life = new PartyLife(this);
			Threat = new MonsterThreat(this);
			Triggers = new TeammateTriggers(this);
			World = new SharedWorld(this);
			Encounters = new BossEncounterSync(this);
			Presentation = new BossPresentationSync(this);
			Music = new BossMusicTracker(this);
			MonsterClient.ActivationTarget = Threat.RecoveryTarget;
			MonsterClient.JoinerOnlyActor = Encounters.JoinerOnlyActor;
			MonsterClient.JoinerOnlyGone = Encounters.JoinerStandInGone;
			Dev = new DevCommands(this);
			Log.Trace("runtime ctor: ui/dev");
			MenuVisible = CoopConfig.ShowMenuOnStart.Value;
			ConfigureNetworkConditions();
			Session.OnMonsterHit += HitRelay.ApplyMonsterHit;
			Session.OnGhostHit += HitRelay.ReceiveGhostHit;
			Session.OnGhostHitResult += HitRelay.ApplyGhostHitResult;
			Session.OnPeerJoined += delegate(Peer p)
			{
				_equipSent = false;
				UI.Notify(p.Name + " joined the party", $"{p} joined");
			};
			Session.OnPeerLeft += delegate(Peer p)
			{
				if (!Session.Ending)
				{
					UI.Notify(p.Name + " left the party", $"{p} left");
				}
			};
			Session.OnPeerLeft += delegate(Peer p)
			{
				HitRelay.ForgetPeer(p.Id);
			};
			Session.OnPeerLeft += delegate(Peer p)
			{
				_apartPeers.Remove(p.Id);
			};
			Session.OnChat += delegate(Peer p, string text)
			{
				UI.ReceiveChat(p, text);
			};
			Session.OnSessionStarted += delegate
			{
				_equipSent = false;
				HitRelay.ClientSideHitsObserved = false;
				_hostedLast = Session.IsHost;
				UI.Notify(Session.IsHost ? "You're hosting. Friends can join now." : "Joining the host...");
			};
			Session.LocalGameBuild = GameBuildId.Describe();
			Log.Info("[Coop] game build " + Session.LocalGameBuild);
			Session.OnGameBuildMismatch += delegate(string theirs, string ours)
			{
				UI.Notify("Players have different game versions; some effects may not show.", "[Coop] game build mismatch: theirs " + theirs + ", ours " + ours);
			};
			Session.OnSessionEnded += delegate(string reason)
			{
				HitRelay.ClearCaches();
				PvpArena.OnSessionEnded();
				_apartPeers.Clear();
				UI.ClearChat();
				string text = SessionText.EndedNotice(reason, _hostedLast);
				if (text != null)
				{
					UI.Notify(text, "[Session] ended: " + reason);
				}
			};
		}

		private void ConfigureNetworkConditions()
		{
			string environmentVariable = Environment.GetEnvironmentVariable("AILIMITCOOP_NETSIM");
			string[] parts;
			if (!string.IsNullOrWhiteSpace(environmentVariable) && CoopConfig.DevCommands.Value)
			{
				parts = environmentVariable.Split(',');
				Session.Conditioner.Configure(Part(0), Part(1), Part(2));
				Log.Warn("[Coop] developer network conditions active: " + Session.Conditioner.Diagnostics);
			}
			float Part(int i)
			{
				if (i >= parts.Length || !float.TryParse(parts[i], NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					return 0f;
				}
				return result;
			}
		}

		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");
				}
				AttackDefenseCapture.ResetScopes();
				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();
				Session.RefreshBuilds();
				WatchBuilds();
				_performance.Mark(CoopWork.Network);
				CoopMenuPause.Tick(this);
				Life.Tick(realtimeSinceStartup);
				ChestInteractionSafety.Tick(GameActive && !Loading && Session.IsConnected);
				HolidayItems.Tick(realtimeSinceStartup, this);
				_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);
					Encounters.Tick(realtimeSinceStartup);
					BossWake.Tick(realtimeSinceStartup);
					Presentation.Tick(realtimeSinceStartup);
					Music?.Tick(realtimeSinceStartup);
					Ghosts.Update(realtimeSinceStartup, _levelId, _loading);
					_performance.Mark(CoopWork.Ghosts);
					MonsterHost.Tick(realtimeSinceStartup, _levelId, _loading);
					_performance.Mark(CoopWork.MonsterSend);
					MonsterClient.Update(realtimeSinceStartup, _levelId);
					Summons.Tick(realtimeSinceStartup);
					HelperRules.Refresh(realtimeSinceStartup);
					Guardians.Tick(realtimeSinceStartup);
					WeakPoints.Tick(realtimeSinceStartup);
					EnemyFx.Tick(realtimeSinceStartup);
					int frameCount = Time.frameCount;
					Defense.Record(frameCount, realtimeSinceStartup, MonsterClient, HitRelay.PendingAttackers, PvpCombat.Duelists(Ghosts));
					HitRelay.TickTimeline(frameCount, realtimeSinceStartup);
					PvpCombat.Tick(realtimeSinceStartup);
					_performance.Mark(CoopWork.MonsterReceive);
					Threat.Tick(realtimeSinceStartup);
					Triggers.Tick(realtimeSinceStartup);
					_performance.Mark(CoopWork.Threat);
					Rewards.Tick();
					Progress.Tick();
					_performance.Mark(CoopWork.Rewards);
					PlayerFx.Tick();
					PlayerShots.Tick(realtimeSinceStartup);
					PlayerWorldFx.Tick(realtimeSinceStartup);
					_performance.Mark(CoopWork.PlayerFx);
					RoomClear.Tick(Time.realtimeSinceStartup);
					RogueLayout.Tick(Time.realtimeSinceStartup);
					RogueStage.Tick(Time.realtimeSinceStartup);
					_performance.Mark(CoopWork.Rogue);
				}
			}
			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);
				RogueLayout.LevelChanged(currentLevelId);
				RogueTrial.LevelChanged(currentLevelId);
				RogueStage.LevelChanged(currentLevelId);
				RoomClear.LevelChanged(currentLevelId);
				Summons.LevelChanged(currentLevelId);
				Guardians.LevelChanged(currentLevelId);
				WeakPoints.LevelChanged(currentLevelId);
				Authority.OnLevelChanged();
				MonsterHost.OnLevelChanged();
				MonsterIndex.Invalidate();
				HitRelay.ClearCaches();
			}
			if (isLoadingLevel != _loading)
			{
				_loading = isLoadingLevel;
				Log.Debug($"[Game] loading = {isLoadingLevel}");
			}
		}

		private bool StateIdle(PlayerStateMsg m)
		{
			if (m.LevelId != _sentLevel || m.Flags != _sentFlags || m.ShieldStage != _sentShield || m.Enchant != _sentEnchant)
			{
				return false;
			}
			if (Math.Abs(m.X - _sentX) > 0.01f || Math.Abs(m.Y - _sentY) > 0.01f || Math.Abs(m.Z - _sentZ) > 0.01f)
			{
				return false;
			}
			if (Math.Abs(Mathf.DeltaAngle(m.Yaw, _sentYaw)) > 0.5f || m.Hp != _sentHp || Math.Abs(m.Sync - _sentSync) > 0.05f)
			{
				return false;
			}
			if (m.LayerCount != _sentLayers)
			{
				return false;
			}
			for (int i = 0; i < m.LayerCount && i < _sentLayerHashes.Length; i++)
			{
				if (m.Layers[i].Hash != _sentLayerHashes[i])
				{
					return false;
				}
			}
			for (int j = 0; j < m.ParamCount; j++)
			{
				if (Math.Abs(m.Params[j].Value) > 0.01f && IsMovementParam(j))
				{
					return false;
				}
			}
			return true;
		}

		private static bool IsMovementParam(int index)
		{
			return index < 3;
		}

		private void RememberSent(PlayerStateMsg m)
		{
			_sentLevel = m.LevelId;
			_sentFlags = m.Flags;
			_sentShield = m.ShieldStage;
			_sentEnchant = m.Enchant;
			_sentX = m.X;
			_sentY = m.Y;
			_sentZ = m.Z;
			_sentYaw = m.Yaw;
			_sentHp = m.Hp;
			_sentSync = m.Sync;
			_sentLayers = m.LayerCount;
			for (int i = 0; i < m.LayerCount && i < _sentLayerHashes.Length; i++)
			{
				_sentLayerHashes[i] = m.Layers[i].Hash;
			}
		}

		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;
				}
				if (Session.LocalPvp && Session.PvpAllowed)
				{
					playerFlags |= PlayerFlags.PvpOptIn;
				}
				PlayerStateMsg playerStateMsg = Sampler.Sample(_levelId, playerFlags);
				playerStateMsg.Layout = RogueLayout.BuildOf(_levelId);
				if (StateIdle(playerStateMsg) && now - _lastStateSentAt < 0.1f)
				{
					return;
				}
				_lastStateSentAt = now;
				RememberSent(playerStateMsg);
				Session.SendPlayerState(playerStateMsg);
			}
			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} reserve {playerEquipMsg.ReserveWeaponDefineId} head {playerEquipMsg.HeadId} body {playerEquipMsg.BodyId} acc {playerEquipMsg.AccessoriesId}");
				}
			}
		}

		private void WatchBuilds()
		{
			if (_apartPeers.Count == 0 && GameState.CurrentLevelId <= 0)
			{
				return;
			}
			IReadOnlyList<Peer> peers = Session.Peers;
			for (int i = 0; i < peers.Count; i++)
			{
				Peer peer = peers[i];
				bool flag = Session.IsApart(peer);
				if (flag == _apartPeers.Contains(peer.Id))
				{
					continue;
				}
				if (!flag)
				{
					_apartPeers.Remove(peer.Id);
					Log.Info($"[Rogue] {peer} is no longer in a different build of this game's stage");
					continue;
				}
				_apartPeers.Add(peer.Id);
				int reportedLevelId = peer.LastState.ReportedLevelId;
				Log.Warn($"[Rogue] {peer} is in level {reportedLevelId} too, but their game built it differently (build {peer.LastState.Layout}, this game's {RogueLayout.BuildOf(reportedLevelId)}): enemies, rooms and revives are not shared with them in this stage");
				if (peer.Id == 0)
				{
					UI.Notify("Rooms differ from the host's: you each play this stage alone.");
					continue;
				}
				UI.Notify(peer.Name + "'s rooms here differ from yours, so you each play this stage alone.", $"{peer}'s rooms differ from this game's; each plays this stage alone");
			}
		}

		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 isn't running, so this uses a direct connection.");
			}
			return new UdpTransport();
		}

		public void HostSession()
		{
			LogPrivacy.RegisterName(SteamPresence.LocalName);
			Session.LocalName = CoopConfig.EffectivePlayerName;
			Session.LocalSteamId = SteamP2PTransport.LocalSteamIdOrZero;
			Session.MaxPlayers = CoopConfig.MaxPlayers.Value;
			DifficultySettings effectiveDifficulty = CoopConfig.EffectiveDifficulty;
			Session.ConfiguredEnemyHpPerExtraPlayer = effectiveDifficulty.EnemyHp;
			Session.ConfiguredBossHpPerExtraPlayer = effectiveDifficulty.BossHp;
			Session.ConfiguredBreakResistPerExtraPlayer = effectiveDifficulty.BreakResist;
			Session.ConfiguredAllowPvp = CoopConfig.AllowPvp.Value;
			Session.ConfiguredSyncWeakPoints = CoopConfig.SyncWeakPoints.Value;
			Session.ConfiguredPvpDamage = PvpRules.DamageFor(CoopConfig.PvpDamage.Value);
			Log.Info($"[Difficulty] hosting on {CoopConfig.Difficulty.Value}: {effectiveDifficulty}; PvP " + (CoopConfig.AllowPvp.Value ? $"allowed (opt-in), damage {CoopConfig.PvpDamage.Value}: {Session.ConfiguredPvpDamage}" : "disabled"));
			INetTransport netTransport = CreateTransport();
			if (!Session.Host(netTransport, CoopConfig.EffectivePort))
			{
				UI.Notify(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 to invite friends.");
				return;
			}
			if (!SteamPresence.IsAvailable)
			{
				UI.Notify("Steam isn't running.");
				return;
			}
			SteamPresence.Advertise();
			if (!SteamPresence.OpenInviteOverlay())
			{
				UI.Notify("Couldn't open the Steam overlay. Check that it's turned on in Steam.");
			}
		}

		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(Session.StatusText);
				netTransport.Dispose();
			}
		}

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

		public void BeforeMonsterReset()
		{
			Encounters?.OnReset(GameState.CurrentLevelId);
			Summons?.OnReset();
			Presentation?.OnSceneReset();
			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");
			GameState.InvalidateFrameCache();
			MonsterIndex.Invalidate();
			HitRelay.ClearCaches();
		}

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

		public void OnGameClose()
		{
			World.Clear();
			HealthScaling.RestoreAll();
			Log.Info("[Game] ProcedureManager.CloseAllLogic -> game inactive");
			GameState.InvalidateFrameCache();
			GameActive = false;
			Life.OnGameClose();
			Threat.Clear();
			Rewards.OnGameClose();
			Progress.Clear();
			PlayerFx.Clear();
			PlayerShots.Clear();
			PlayerWorldFx.Clear();
			BossWake.Clear();
			RogueTrial.Clear();
			RoomClear.Clear();
			Summons.Clear();
			Guardians.Clear();
			WeakPoints.Clear();
			RogueLayout.OnGameClose();
			Encounters.OnGameClose();
			Presentation.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.3.10")]
	public class Plugin : BasePlugin
	{
		public const string Guid = "com.ailimit.coop";

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

		public const string Version = "0.3.10";

		public const string DisplayVersion = "0.3.10 (protocol 37)";

		private static WillRenderCanvases _frameAction;

		private static Action _beforeInput;

		public override void Load()
		{
			//IL_026f: Unknown result type (might be due to invalid IL or missing references)
			//IL_028c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c6: 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"} {"0.3.10 (protocol 37)"} loading (transport {CoopConfig.Transport.Value}, port {CoopConfig.EffectivePort})");
			Log.Info($"[Net] Protocol {37}, 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,
		PlayerFx,
		Rogue,
		Other,
		Menu,
		FrameGap,
		Count
	}
	internal sealed class FrameTimingWindow
	{
		private readonly double[] _sum = new double[14];

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

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

		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 GameBuildId
	{
		private static string _cached;

		public static string Describe()
		{
			if (_cached != null)
			{
				return _cached;
			}
			string text = "?";
			try
			{
				text = Application.version;
			}
			catch
			{
			}
			string text2 = "?";
			try
			{
				FileInfo fileInfo = new FileInfo(Path.Combine(Paths.GameRootPath, "GameAssembly.dll"));
				if (fileInfo.Exists)
				{
					text2 = fileInfo.Length.ToString();
				}
			}
			catch
			{
			}
			_cached = text + "/" + text2;
			return _cached;
		}
	}
	public sealed class GlbModel
	{
		public float[] Positions;

		public float[] Normals;

		public float[] Uvs;

		public int[] Triangles;

		public byte[] Image;

		public int VertexCount => Positions.Length / 3;

		public int TriangleCount => Triangles.Length / 3;

		public static GlbModel Read(byte[] glb)
		{
			if (glb == null || glb.Length < 28 || BitConverter.ToUInt32(glb, 0) != 1179937895)
			{
				throw new FormatException("not a binary glTF file");
			}
			int num = BitConverter.ToInt32(glb, 12);
			if (BitConverter.ToUInt32(glb, 16) != 1313821514)
			{
				throw new FormatException("the first chunk is not JSON");
			}
			int binStart = 20 + num + 8;
			if (binStart > glb.Length || BitConverter.ToUInt32(glb, 20 + num + 4) != 5130562)
			{
				throw new FormatException("no binary chunk");
			}
			JsonElement accessors;
			JsonElement views;
			using (JsonDocument jsonDocument = JsonDocument.Parse(new ReadOnlyMemory<byte>(glb, 20, num)))
			{
				JsonElement rootElement = jsonDocument.RootElement;
				accessors = rootElement.GetProperty("accessors");
				views = rootElement.GetProperty("bufferViews");
				JsonElement jsonElement = rootElement.GetProperty("meshes")[0].GetProperty("primitives")[0];
				JsonElement property = jsonElement.GetProperty("attributes");
				GlbModel glbModel = new GlbModel
				{
					Positions = Floats(property.GetProperty("POSITION").GetInt32(), 3)
				};
				if (property.TryGetProperty("NORMAL", out var value))
				{
					glbModel.Normals = Floats(value.GetInt32(), 3);
				}
				if (property.TryGetProperty("TEXCOORD_0", out var value2))
				{
					glbModel.Uvs = Floats(value2.GetInt32(), 2);
				}
				int vertexCount = glbModel.VertexCount;
				for (int i = 0; i < vertexCount; i++)
				{
					glbModel.Positions[i * 3] = 0f - glbModel.Positions[i * 3];
					if (glbModel.Normals != null)
					{
						glbModel.Normals[i * 3] = 0f - glbModel.Normals[i * 3];
					}
					if (glbModel.Uvs != null)
					{
						glbModel.Uvs[i * 2 + 1] = 1f - glbModel.Uvs[i * 2 + 1];
					}
				}
				JsonElement a = accessors[jsonElement.GetProperty("indices").GetInt32()];
				int @int = a.GetProperty("count").GetInt32();
				int int2 = a.GetProperty("componentType").GetInt32();
				int num2 = Start(a);
				int num3 = int2 switch
				{
					5123 => 2, 
					5125 => 4, 
					_ => 1, 
				};
				if (@int % 3 != 0 || num2 + @int * num3 > glb.Length)
				{
					throw new FormatException("bad index data");
				}
				glbModel.Triangles = new int[@int];
				for (int j = 0; j < @int; j += 3)
				{
					int num4 = Index(glb, num2, int2, j);
					int num5 = Index(glb, num2, int2, j + 1);
					int num6 = Index(glb, num2, int2, j + 2);
					if (num4 >= vertexCount || num5 >= vertexCount || num6 >= vertexCount)
					{
						throw new FormatException("an index points past the vertices");
					}
					glbModel.Triangles[j] = num4;
					glbModel.Triangles[j + 1] = num6;
					glbModel.Triangles[j + 2] = num5;
				}
				if (rootElement.TryGetProperty("images", out var value3) && value3.GetArrayLength() > 0 && value3[0].TryGetProperty("bufferView", out var value4))
				{
					JsonElement jsonElement2 = views[value4.GetInt32()];
					int num7 = ViewStart(value4.GetInt32());
					int int3 = jsonElement2.GetProperty("byteLength").GetInt32();
					if (num7 + int3 <= glb.Length)
					{
						glbModel.Image = new byte[int3];
						Buffer.BlockCopy(glb, num7, glbModel.Image, 0, int3);
					}
				}
				return glbModel;
			}
			float[] Floats(int index, int width)
			{
				JsonElement a2 = accessors[index];
				if (a2.GetProperty("componentType").GetInt32() != 5126)
				{
					throw new FormatException("vertex data is not float");
				}
				int num8 = a2.GetProperty("count").GetInt32() * width;
				int num9 = Start(a2);
				if (num9 < binStart || num9 + num8 * 4 > glb.Length)
				{
					throw new FormatException("vertex data outside the file");
				}
				float[] array = new float[num8];
				Buffer.BlockCopy(glb, num9, array, 0, num8 * 4);
				return array;
			}
			int Start(JsonElement jsonElement3)
			{
				JsonElement value5;
				return ViewStart(jsonElement3.GetProperty("bufferView").GetInt32()) + (jsonElement3.TryGetProperty("byteOffset", out value5) ? value5.GetInt32() : 0);
			}
			int ViewStart(int view)
			{
				JsonElement jsonElement3 = views[view];
				JsonElement value5;
				return binStart + (jsonElement3.TryGetProperty("byteOffset", out value5) ? value5.GetInt32() : 0);
			}
		}

		private static int Index(byte[] b, int offset, int type, int i)
		{
			return type switch
			{
				5125 => (int)BitConverter.ToUInt32(b, offset + i * 4), 
				5123 => BitConverter.ToUInt16(b, offset + i * 2), 
				_ => b[offset + i], 
			};
		}

		public GlbModel Lining(Func<float, float, float, bool> keep, float inset)
		{
			int[] array = new int[VertexCount];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = -1;
			}
			List<int> list = new List<int>();
			int num = 0;
			for (int j = 0; j < Triangles.Length; j += 3)
			{
				int num2 = Triangles[j];
				int num3 = Triangles[j + 1];
				int num4 = Triangles[j + 2];
				float arg = (Positions[num2 * 3] + Positions[num3 * 3] + Positions[num4 * 3]) / 3f;
				float arg2 = (Positions[num2 * 3 + 1] + Positions[num3 * 3 + 1] + Positions[num4 * 3 + 1]) / 3f;
				float arg3 = (Positions[num2 * 3 + 2] + Positions[num3 * 3 + 2] + Positions[num4 * 3 + 2]) / 3f;
				if (!keep(arg, arg2, arg3))
				{
					continue;
				}
				int[] array2 = new int[3] { num2, num4, num3 };
				foreach (int num5 in array2)
				{
					if (array[num5] < 0)
					{
						array[num5] = num++;
					}
					list.Add(array[num5]);
				}
			}
			GlbModel glbModel = new GlbModel
			{
				Positions = new float[num * 3],
				Normals = new float[num * 3],
				Uvs = ((Uvs != null) ? new float[num * 2] : null),
				Triangles = list.ToArray()
			};
			for (int l = 0; l < array.Length; l++)
			{
				int num6 = array[l];
				if (num6 >= 0)
				{
					float num7 = ((Normals != null) ? Normals[l * 3] : 0f);
					float num8 = ((Normals != null) ? Normals[l * 3 + 1] : 0f);
					float num9 = ((Normals != null) ? Normals[l * 3 + 2] : 0f);
					glbModel.Positions[num6 * 3] = Positions[l * 3] - num7 * inset;
					glbModel.Positions[num6 * 3 + 1] = Positions[l * 3 + 1] - num8 * inset;
					glbModel.Positions[num6 * 3 + 2] = Positions[l * 3 + 2] - num9 * inset;
					glbModel.Normals[num6 * 3] = 0f - num7;
					glbModel.Normals[num6 * 3 + 1] = 0f - num8;
					glbModel.Normals[num6 * 3 + 2] = 0f - num9;
					if (glbModel.Uvs != null)
					{
						glbModel.Uvs[num6 * 2] = Uvs[l * 2];
						glbModel.Uvs[num6 * 2 + 1] = Uvs[l * 2 + 1];
					}
				}
			}
			return glbModel;
		}
	}
	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;
		}

		private static string EnemyBarWidgets()
		{
			try
			{
				return (UiHPPool._NormalHp?.Count ?? (-1)).ToString();
			}
			catch
			{
				return "unavailable";
			}
		}

		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} enemyBarWidgets={EnemyBarWidgets()}");
				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(

BepInEx/plugins/AILimitCoop/LiteNetLib.dll

Decompiled 11 hours 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