Decompiled source of AI LIMIT Together v0.2.5
BepInEx/plugins/AILimitCoop/AILimitCoop.dll
Decompiled 5 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using AILimitCoop.Dev; using AILimitCoop.Game; using AILimitCoop.Net; using AILimitCoop.UI; using AILimitCoop.Util; using ArchiveDef; using BehaviorDesigner.Runtime; using BehaviorDesigner.Runtime.Tasks; using BehaviorDesigner.Runtime.Tasks.Unity.Math; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using BepInEx.Unity.IL2CPP.UnityEngine; using GameDef; using HarmonyLib; using Il2CppInterop.Common.XrefScans; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.Reflection; using LevelEdit; using LiteNetLib; using LiteNetLib.Layers; using LiteNetLib.Utils; using Microsoft.CodeAnalysis; using PlayerShowSateDefine; using RootMotion.Dynamics; using SceneSettingDef; using SenseGame; using SenseGame.Tasks; using Steamworks; using UnityEngine; using UnityEngine.AI; using UnityEngine.AddressableAssets; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Samples; using UnityEngine.InputSystem.Utilities; using UnityEngine.ResourceManagement.AsyncOperations; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("AILimitCoop contributors")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.4.0")] [assembly: AssemblyInformationalVersion("0.2.4")] [assembly: AssemblyProduct("AI LIMIT Co-op")] [assembly: AssemblyTitle("AILimitCoop")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.2.4.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace AILimitCoop { public enum PartyHudCorner { TopRight, TopLeft, BottomRight, BottomLeft } public enum TransportKind { UDP, Steam } public enum HitDetectionMode { Auto, ClientSide, HostForward } public static class CoopConfig { public static ConfigEntry<string> PlayerName; public static ConfigEntry<TransportKind> Transport; public static ConfigEntry<int> Port; public static ConfigEntry<string> JoinAddress; public static ConfigEntry<int> MaxPlayers; public static ConfigEntry<KeyCode> ToggleMenuKey; public static ConfigEntry<KeyCode> HostKey; public static ConfigEntry<KeyCode> JoinKey; public static ConfigEntry<KeyCode> LeaveKey; public static ConfigEntry<float> PlayerSendRate; public static ConfigEntry<float> MonsterSendRate; public static ConfigEntry<float> NetworkDiagnosticsInterval; public static ConfigEntry<float> InterpolationDelay; public static ConfigEntry<float> GhostAnimSmoothing; public static ConfigEntry<float> GhostAnimCrossFade; public static ConfigEntry<bool> SyncMonsters; public static ConfigEntry<float> EnemyHpPerExtraPlayer; public static ConfigEntry<bool> GhostTargeting; public static ConfigEntry<HitDetectionMode> HitDetection; public static ConfigEntry<bool> GhostEquipment; public static ConfigEntry<bool> GhostCollision; public static ConfigEntry<bool> ForceRunInBackground; public static ConfigEntry<bool> EnableOverlay; public static ConfigEntry<bool> ShowHud; public static ConfigEntry<float> HudScale; public static ConfigEntry<float> HudOpacity; public static ConfigEntry<PartyHudCorner> HudCorner; public static ConfigEntry<bool> AllyHealthBars; public static ConfigEntry<float> AllyHealthBarDistance; public static ConfigEntry<bool> AllyHealthBarNames; public static ConfigEntry<bool> ShowChat; public static ConfigEntry<KeyCode> ChatKey; public static ConfigEntry<bool> ShowMenuOnStart; public const string DefaultPlayerName = "Player"; public static ConfigEntry<bool> Verbose; public static ConfigEntry<bool> DevCommands; public static ConfigEntry<string> PatchGroups; public static ConfigEntry<int> SimulatedLatencyMs; public static ConfigEntry<int> SimulatedJitterMs; public static ConfigEntry<int> SimulatedPacketLossPercent; public static int EffectivePort { get { if (InstanceProfile.Port <= 0) { return Port.Value; } return InstanceProfile.Port; } } public static string EffectivePlayerName { get { if (!string.IsNullOrEmpty(InstanceProfile.PlayerName)) { return InstanceProfile.PlayerName; } string text = PlayerName?.Value; if (!string.IsNullOrWhiteSpace(text) && text != "Player") { return text; } string localName = SteamPresence.LocalName; if (!string.IsNullOrWhiteSpace(localName)) { return localName; } return "Player"; } } public static void Bind(ConfigFile cfg) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Expected O, but got Unknown //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Expected O, but got Unknown //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Expected O, but got Unknown //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Expected O, but got Unknown //IL_0378: Unknown result type (might be due to invalid IL or missing references) //IL_0382: Expected O, but got Unknown //IL_03b0: Unknown result type (might be due to invalid IL or missing references) //IL_03ba: Expected O, but got Unknown //IL_041e: Unknown result type (might be due to invalid IL or missing references) //IL_0428: Expected O, but got Unknown //IL_0510: Unknown result type (might be due to invalid IL or missing references) //IL_051a: Expected O, but got Unknown //IL_0540: Unknown result type (might be due to invalid IL or missing references) //IL_054a: Expected O, but got Unknown //IL_056d: Unknown result type (might be due to invalid IL or missing references) //IL_0577: Expected O, but got Unknown PlayerName = cfg.Bind<string>("General", "PlayerName", DefaultName(), "Name shown to other players."); Transport = cfg.Bind<TransportKind>("General", "Transport", TransportKind.UDP, "UDP: connect by IP address (LAN, VPN such as ZeroTier/Radmin/Hamachi, or a forwarded port).\nSteam: experimental peer-to-peer over Steam; requires the host and clients to be Steam friends."); Port = cfg.Bind<int>("General", "Port", 7777, "UDP port to host on (and default port when joining)."); JoinAddress = cfg.Bind<string>("General", "JoinAddress", "127.0.0.1:7777", "Address used by the Join key / menu. UDP: ip:port. Steam: the host's 64-bit Steam id."); MaxPlayers = cfg.Bind<int>("General", "MaxPlayers", 8, new ConfigDescription("Maximum players in a hosted session, including the host. More than two is experimental; up to 16 allowed for stress testing.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 16), Array.Empty<object>())); ToggleMenuKey = cfg.Bind<KeyCode>("Keys", "ToggleMenu", (KeyCode)286, "Show/hide the co-op menu."); HostKey = cfg.Bind<KeyCode>("Keys", "Host", (KeyCode)287, "Start hosting a session."); JoinKey = cfg.Bind<KeyCode>("Keys", "Join", (KeyCode)288, "Join the session at JoinAddress."); LeaveKey = cfg.Bind<KeyCode>("Keys", "Leave", (KeyCode)289, "Leave / stop the current session."); PlayerSendRate = cfg.Bind<float>("Network", "PlayerSendRate", 30f, new ConfigDescription("How many times per second your character state is sent.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), Array.Empty<object>())); MonsterSendRate = cfg.Bind<float>("Network", "MonsterSendRate", 15f, new ConfigDescription("Host only: how many times per second monster states are sent.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 30f), Array.Empty<object>())); NetworkDiagnosticsInterval = cfg.Bind<float>("Network", "DiagnosticsInterval", 30f, new ConfigDescription("Seconds between networking summaries in LogOutput.log. 0 disables periodic summaries; counters and rate-limited send failures remain active.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>())); InterpolationDelay = cfg.Bind<float>("Network", "InterpolationDelay", 0.1f, new ConfigDescription("Seconds of buffering applied to remote characters. Higher is smoother but adds visual latency.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>())); GhostAnimSmoothing = cfg.Bind<float>("Network", "GhostAnimSmoothing", 0.08f, "Seconds of easing applied to another player's animation blending. Updates arrive at the send rate, so writing them straight through makes movement look stepped. 0 disables."); GhostAnimCrossFade = cfg.Bind<float>("Network", "GhostAnimCrossFade", 0.06f, "Seconds to fade when another player's animation state changes, instead of cutting. 0 disables."); SyncMonsters = cfg.Bind<bool>("Gameplay", "SyncMonsters", true, "Share monsters in the host's level. Nearby players simulate enemies with ownership locked during combat. Disable for 'ghost only' mode where everyone fights their own monsters."); EnemyHpPerExtraPlayer = cfg.Bind<float>("Gameplay", "EnemyHpPerExtraPlayer", 0.5f, new ConfigDescription("Host setting, shared when players join. Extra enemy and boss HP per additional connected player: 0.5 = +50%, 0 disables. Includes spectators and players in other areas. Combat locks the maximum until rest/reset. Change before hosting a new session.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); GhostTargeting = cfg.Bind<bool>("Gameplay", "GhostTargeting", true, "The enemy simulator evaluates living players using damage, proximity, threat decay and safe target changes."); HitDetection = cfg.Bind<HitDetectionMode>("Gameplay", "HitDetection", HitDetectionMode.Auto, "How monster attacks against remote players are resolved. Leave on Auto unless you know what you are doing."); GhostEquipment = cfg.Bind<bool>("Gameplay", "GhostEquipment", true, "Show other players' armor and weapons on their ghosts (disable if ghosts fail to spawn)."); GhostCollision = cfg.Bind<bool>("Gameplay", "GhostCollision", false, "Keep the character controller on ghosts so players physically block each other."); ForceRunInBackground = cfg.Bind<bool>("General", "ForceRunInBackground", true, "Keep simulating while the game window is not focused. Needed so your character keeps syncing when you\nalt-tab, and required to run two instances on one PC."); EnableOverlay = cfg.Bind<bool>("UI", "EnableOverlay", true, "Draw the in-game menu and HUD. Turn off if the overlay causes problems; hotkeys and the config file still work."); ShowHud = cfg.Bind<bool>("UI", "ShowHud", true, "Show the party cards while the co-op menu is closed."); HudScale = cfg.Bind<float>("UI", "HudScale", 1f, new ConfigDescription("Party HUD size, relative to automatic resolution scaling.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.75f, 1.5f), Array.Empty<object>())); HudOpacity = cfg.Bind<float>("UI", "HudOpacity", 0.9f, new ConfigDescription("Party HUD panel opacity. Text and health remain readable.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.35f, 1f), Array.Empty<object>())); HudCorner = cfg.Bind<PartyHudCorner>("UI", "HudCorner", PartyHudCorner.TopRight, "Screen corner used by the party HUD."); AllyHealthBars = cfg.Bind<bool>("UI", "AllyHealthBars", true, "Show a small health bar above allies in your area. Hidden while the co-op menu is open and whenever the game hides its own enemy health bars."); AllyHealthBarDistance = cfg.Bind<float>("UI", "AllyHealthBarDistance", 40f, new ConfigDescription("Metres from the camera beyond which allies show no bar. Bars shrink with distance and fade out near this limit.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 100f), Array.Empty<object>())); AllyHealthBarNames = cfg.Bind<bool>("UI", "AllyHealthBarNames", true, "Show each ally's name above their health bar. Names fade out at a shorter distance than the bar."); ShowChat = cfg.Bind<bool>("UI", "ShowChat", true, "Show party chat and allow typing messages. Messages fade after a few seconds while chat is closed."); ChatKey = cfg.Bind<KeyCode>("Keys", "Chat", (KeyCode)13, "Open party chat while in a session. Enter sends and Escape closes; other co-op hotkeys pause while you type."); ShowMenuOnStart = cfg.Bind<bool>("UI", "ShowMenuOnStart", true, "Show the co-op menu when the game starts."); Verbose = cfg.Bind<bool>("Debug", "Verbose", false, "Log per-hit and per-spawn diagnostics to BepInEx/LogOutput.log."); DevCommands = cfg.Bind<bool>("Debug", "DevCommands", false, "Poll <GameDir>/BepInEx/coop_cmd.txt for developer commands (host, join, leave, dump, ...). Development aid only."); PatchGroups = cfg.Bind<string>("Debug", "PatchGroups", "Lifecycle,Ghost,Monster,Hit,Overlay", "Which groups of game hooks to install (comma separated). Troubleshooting aid: remove a group to disable that feature.\nLifecycle = level/game start tracking, Ghost = remote player bodies, Monster = shared monster simulation,\nHit = damage relay and targeting, Overlay = in-game menu/HUD drawing."); SimulatedLatencyMs = cfg.Bind<int>("Debug", "SimulatedLatencyMs", 0, new ConfigDescription("Testing aid: delay every packet this many milliseconds each way. 0 is off.\nA typical distant player sits around 30-80. Leave at 0 for real play.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 500), Array.Empty<object>())); SimulatedJitterMs = cfg.Bind<int>("Debug", "SimulatedJitterMs", 0, new ConfigDescription("Testing aid: vary the simulated delay by up to this much, so packets arrive unevenly.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 200), Array.Empty<object>())); SimulatedPacketLossPercent = cfg.Bind<int>("Debug", "SimulatedPacketLossPercent", 0, new ConfigDescription("Testing aid: drop this percentage of outgoing packets. 0 is off.\n1-3 resembles a poor connection and is the case worth testing.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 50), Array.Empty<object>())); } private static string DefaultName() { return "Player"; } } public sealed class KeyEdge { private readonly Dictionary<KeyCode, bool> _held = new Dictionary<KeyCode, bool>(); private static readonly int _pid = Process.GetCurrentProcess().Id; [DllImport("user32.dll")] private static extern short GetAsyncKeyState(int vKey); public static bool MouseButtonHeld() { if ((GetAsyncKeyState(1) & 0x8000) == 0 && (GetAsyncKeyState(2) & 0x8000) == 0) { return (GetAsyncKeyState(4) & 0x8000) != 0; } return true; } [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); public static bool GameHasFocus() { try { IntPtr foregroundWindow = GetForegroundWindow(); if (foregroundWindow == IntPtr.Zero) { return false; } GetWindowThreadProcessId(foregroundWindow, out var processId); return processId == (uint)_pid; } catch { return true; } } public static int ToVirtualKey(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Expected I4, but got Unknown //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Invalid comparison between Unknown and I4 //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Invalid comparison between Unknown and I4 //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Invalid comparison between Unknown and I4 //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Invalid comparison between Unknown and I4 //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Invalid comparison between Unknown and I4 //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Invalid comparison between Unknown and I4 //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Invalid comparison between Unknown and I4 //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected I4, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Invalid comparison between Unknown and I4 //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Invalid comparison between Unknown and I4 //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Expected I4, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Expected I4, but got Unknown //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Invalid comparison between Unknown and I4 //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Invalid comparison between Unknown and I4 //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Expected I4, but got Unknown //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Invalid comparison between Unknown and I4 //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Invalid comparison between Unknown and I4 //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Invalid comparison between Unknown and I4 //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Invalid comparison between Unknown and I4 int num = (int)key; if ((int)key >= 282 && (int)key <= 296) { return 112 + (num - 282); } if ((int)key >= 97 && (int)key <= 122) { return 65 + (num - 97); } if ((int)key >= 48 && (int)key <= 57) { return 48 + (num - 48); } if ((int)key >= 256 && (int)key <= 265) { return 96 + (num - 256); } if ((int)key <= 32) { if ((int)key <= 13) { if ((int)key == 8) { return 8; } if ((int)key == 9) { return 9; } if ((int)key == 13) { return 13; } } else { if ((int)key == 19) { return 19; } if ((int)key == 27) { return 27; } if ((int)key == 32) { return 32; } } } else if ((int)key <= 61) { switch (key - 39) { default: if ((int)key != 59) { if ((int)key != 61) { break; } return 187; } return 186; case 6: return 189; case 0: return 222; case 5: return 188; case 7: return 190; case 8: return 191; case 1: case 2: case 3: case 4: break; } } else if ((int)key <= 127) { switch (key - 91) { default: if ((int)key != 127) { break; } return 46; case 5: return 192; case 0: return 219; case 2: return 221; case 1: return 220; case 3: case 4: break; } } else { switch (key - 273) { case 7: return 33; case 8: return 34; case 6: return 35; case 5: return 36; case 3: return 37; case 0: return 38; case 2: return 39; case 1: return 40; case 4: return 45; } switch (key - 300) { case 1: return 20; case 0: return 144; case 2: return 145; case 4: return 160; case 3: return 161; case 6: return 162; case 5: return 163; case 8: return 164; case 7: return 165; } } return 0; } public bool Pressed(KeyCode key) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) bool flag = false; try { int num = ToVirtualKey(key); if (num != 0) { flag = (GetAsyncKeyState(num) & 0x8000) != 0; } } catch { } bool value; bool flag2 = _held.TryGetValue(key, out value) && value; _held[key] = flag; if (flag) { return !flag2; } return false; } public bool Held(KeyCode key) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) bool value; return _held.TryGetValue(key, out value) && value; } } public sealed class CoopRuntime { public readonly Session Session; public readonly RemotePlayerManager Ghosts; public readonly MonsterSyncHost MonsterHost; public readonly MonsterSyncClient MonsterClient; public readonly MonsterAuthority Authority; public readonly EnemyHealthScaling HealthScaling; public readonly LocalPlayerSampler Sampler = new LocalPlayerSampler(); public readonly MonsterIndex MonsterIndex = new MonsterIndex(); public readonly CoopUI UI; public readonly MenuInputFocus MenuFocus; public readonly BossRewardSync Rewards; public readonly PartyLife Life; public readonly MonsterThreat Threat; public readonly SharedWorld World; public readonly DevCommands Dev; private readonly KeyEdge _keys = new KeyEdge(); private int _levelId = -1; private bool _loading; private float _nextStateSend; private float _nextEquipCheck; private PlayerEquipMsg _lastEquip; private bool _equipSent; private float _lastErrorAt; private int _errorBurst; private RemotePlayer _fakeGhost; private Peer _fakePeer; private float _nextFakeSend; private readonly PlayerStateMsg _fakeState = new PlayerStateMsg(); public bool MenuVisible; private int _frames; private readonly SessionPerformance _performance = new SessionPerformance(); private int _hostAttempts; private float _nextHostAttempt; private bool _firstGui = true; private int _guiCalls; private float _nextAutoContinue; private int _autoContinueAttempts; private bool _autoStarted; private float _autoStartAt = -1f; private float _autoRetryAt; private int _autoRetries; private bool _steamLaunchJoinChecked; private Action _frameAction; private float _frameActionUntil; private Action _delayedAction; private float _delayedAt; public static CoopRuntime Instance { get; private set; } public int LevelId => _levelId; public bool Loading => _loading; public string LastError { get; private set; } = ""; public bool GameActive { get; private set; } public bool OverlayAvailable => OverlayHost.IsAlive; public bool FakeGhostActive => _fakeGhost != null; public static void Create() { Instance = new CoopRuntime(); HitRelay.Runtime = Instance; } private CoopRuntime() { Log.Trace("runtime ctor: begin"); Session = new Session(new SteamBanList(Path.Combine(Paths.ConfigPath, "com.ailimit.coop.bans" + InstanceProfile.FileSuffix + ".json"))); Session.Diagnostics.SummaryInterval = CoopConfig.NetworkDiagnosticsInterval.Value; Ghosts = new RemotePlayerManager(Session); Authority = new MonsterAuthority(Session); HealthScaling = new EnemyHealthScaling(Session, Authority); MonsterHost = new MonsterSyncHost(Session, Authority, HealthScaling); MonsterClient = new MonsterSyncClient(Session, Authority, HealthScaling, MonsterIndex); Log.Trace("runtime ctor: sync objects"); UI = new CoopUI(this); MenuFocus = new MenuInputFocus(this); Rewards = new BossRewardSync(this); Life = new PartyLife(this); Threat = new MonsterThreat(this); World = new SharedWorld(this); MonsterClient.ActivationTarget = Threat.RecoveryTarget; Dev = new DevCommands(this); Log.Trace("runtime ctor: ui/dev"); MenuVisible = CoopConfig.ShowMenuOnStart.Value; Session.OnMonsterHit += HitRelay.ApplyMonsterHit; Session.OnGhostHit += HitRelay.ApplyGhostHit; Session.OnPeerJoined += delegate(Peer p) { _equipSent = false; UI.Notify(p.Name + " joined", $"{p} joined"); }; Session.OnPeerLeft += delegate(Peer p) { UI.Notify(p.Name + " left", $"{p} left"); }; Session.OnChat += delegate(Peer p, string text) { UI.ReceiveChat(p, text); }; Session.OnSessionStarted += delegate { _equipSent = false; HitRelay.ClientSideHitsObserved = false; UI.Notify(Session.IsHost ? "Hosting session" : "Joining session..."); }; Session.OnSessionEnded += delegate(string reason) { HitRelay.ClearCaches(); UI.ClearChat(); UI.Notify("Session ended: " + reason); }; } private void EnsureHost(float now) { if (!OverlayHost.IsAlive && CoopConfig.EnableOverlay.Value && !(now < _nextHostAttempt) && _hostAttempts < 3) { _hostAttempts++; _nextHostAttempt = now + 5f; Log.Trace("host: creating"); if (OverlayHost.TryCreate()) { Log.Trace("host: ready"); } else { Log.Trace("host: failed"); } } } public void Update() { bool flag = _frames < 3; if (flag) { Log.Trace($"Update {_frames} begin"); } float realtimeSinceStartup = Time.realtimeSinceStartup; _frames++; _performance.Begin(this, realtimeSinceStartup); try { if (_frames == 1) { Log.Info("[Coop] first Update"); } if (_frames > 1) { EnsureHost(realtimeSinceStartup); } GhostRegistry.Prune(Time.frameCount, realtimeSinceStartup); if (flag) { Log.Trace("Update: keys"); } if (KeyEdge.GameHasFocus()) { HandleKeys(); } MenuFocus.Tick(); SteamPresence.Tick(realtimeSinceStartup, MenuVisible); if (flag) { Log.Trace("Update: dev poll"); } Dev.Poll(realtimeSinceStartup); if (flag) { Log.Trace("Update: session poll"); } if (GameActive) { TrackLevel(); } _performance.Mark(CoopWork.Input); Session.Poll(); _performance.Mark(CoopWork.Network); CoopMenuPause.Tick(this); Life.Tick(realtimeSinceStartup); ChestInteractionSafety.Tick(GameActive && !Loading && Session.IsConnected); _performance.Mark(CoopWork.Life); if (!GameActive) { _levelId = -1; _loading = true; AutoContinueTick(realtimeSinceStartup); SendState(realtimeSinceStartup); if (flag) { Log.Trace("Update end (inactive)"); } return; } TrackLevel(); AutoStart(realtimeSinceStartup); PumpFrameAction(realtimeSinceStartup); UpdateFakeGhost(realtimeSinceStartup); if (Session.IsActive || _fakeGhost != null) { Sampler.Bind(GameState.LocalPlayer); SendState(realtimeSinceStartup); _performance.Mark(CoopWork.LocalState); World.Tick(realtimeSinceStartup); Ghosts.Update(realtimeSinceStartup, _levelId, _loading); _performance.Mark(CoopWork.Ghosts); MonsterHost.Tick(realtimeSinceStartup, _levelId, _loading); _performance.Mark(CoopWork.MonsterSend); MonsterClient.Update(realtimeSinceStartup, _levelId); _performance.Mark(CoopWork.MonsterReceive); Threat.Tick(realtimeSinceStartup); _performance.Mark(CoopWork.Threat); Rewards.Tick(); _performance.Mark(CoopWork.Rewards); } } catch (Exception ex) { ReleaseMenuOnError(); ReportError("Update", ex); } finally { _performance.End(this, realtimeSinceStartup); } } public void OnGUI() { if (!CoopConfig.EnableOverlay.Value) { return; } bool flag = _guiCalls < 3; _guiCalls++; if (flag) { Log.Trace($"OnGUI {_guiCalls} begin"); } long stamp = _performance.BeginMenu(); try { if (_firstGui) { _firstGui = false; Log.Info("[Coop] first OnGUI"); } bool menuWasVisible = MenuVisible || UI.ChatOpen; UI.Draw(flag); MenuFocus.AfterGui(menuWasVisible); } catch (Exception ex) { ReleaseMenuOnError(); ReportError("OnGUI", ex); } finally { _performance.EndMenu(stamp); } if (flag) { Log.Trace($"OnGUI {_guiCalls} end"); } } private void ReportError(string where, Exception ex) { float realtimeSinceStartup = Time.realtimeSinceStartup; LastError = $"{where}: {ex.GetType().Name}: {ex.Message}"; if (realtimeSinceStartup - _lastErrorAt > 5f) { _errorBurst = 0; } _lastErrorAt = realtimeSinceStartup; if (++_errorBurst <= 3) { Log.Error("[Coop] " + where, ex); } } private void HandleKeys() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) HandleMenuKey(); HandleChatKey(); if (!UI.ChatOpen) { TrySteamLaunchJoin(); if (_keys.Pressed(CoopConfig.HostKey.Value)) { HostSession(); } if (_keys.Pressed(CoopConfig.JoinKey.Value)) { JoinSession(CoopConfig.JoinAddress.Value); } if (_keys.Pressed(CoopConfig.LeaveKey.Value)) { LeaveSession(); } if (Life.IsSpectating && _keys.Pressed((KeyCode)290)) { Life.CycleTarget(); } } } private void HandleMenuKey() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (_keys.Pressed(CoopConfig.ToggleMenuKey.Value) && !UI.ChatOpen) { MenuVisible = !MenuVisible; } } private void HandleChatKey() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) KeyCode value = CoopConfig.ChatKey.Value; bool num = _keys.Pressed(value); UI.TickChat(_keys.Held(value)); if (num && !UI.ChatOpen && !MenuVisible) { UI.OpenChat(); } } public void BeforeInputUpdate() { try { if (KeyEdge.GameHasFocus()) { HandleMenuKey(); HandleChatKey(); } MenuFocus.Tick(); } catch (Exception ex) { ReleaseMenuOnError(); ReportError("Menu input", ex); } } private void ReleaseMenuOnError() { MenuVisible = false; try { UI.CloseChat(clearDraft: false); } catch { } try { MenuFocus.Release(); } catch (Exception ex) { Log.Warn("[Menu input] restore: " + ex.Message); } } private void TrackLevel() { int currentLevelId = GameState.CurrentLevelId; bool isLoadingLevel = GameState.IsLoadingLevel; if (currentLevelId != _levelId) { Log.Info($"[Game] level changed {_levelId} -> {currentLevelId} ({GameState.CurrentSceneName})"); HealthScaling.RestoreAll(); _levelId = currentLevelId; MonsterClient.OnLevelChanged(currentLevelId); Authority.OnLevelChanged(); MonsterHost.OnLevelChanged(); MonsterIndex.Invalidate(); HitRelay.ClearCaches(); } if (isLoadingLevel != _loading) { _loading = isLoadingLevel; Log.Debug($"[Game] loading = {isLoadingLevel}"); } } private void SendState(float now) { if (!Session.IsConnected) { return; } if (now >= _nextStateSend) { _nextStateSend = now + 1f / UnityMath.Max(1f, CoopConfig.PlayerSendRate.Value); PlayerFlags playerFlags = PlayerFlags.None; if (_loading || (Object)(object)Sampler.Player == (Object)null) { playerFlags |= PlayerFlags.Loading; } if (HitRelay.ClientSideHitsObserved) { playerFlags |= PlayerFlags.ClientSideHits; } if (Life.IsSpectating) { playerFlags |= PlayerFlags.Dead | PlayerFlags.Spectating; } PlayerStateMsg state = Sampler.Sample(_levelId, playerFlags); Session.SendPlayerState(state); } if (now >= _nextEquipCheck && (Object)(object)Sampler.Player != (Object)null) { _nextEquipCheck = now + 1f; PlayerEquipMsg playerEquipMsg = Sampler.SampleEquip(CoopConfig.EffectivePlayerName); if (!_equipSent || !playerEquipMsg.SameAs(in _lastEquip)) { Session.SendPlayerEquip(playerEquipMsg); _lastEquip = playerEquipMsg; _equipSent = true; Log.Debug($"[Coop] sent equipment: weapon {playerEquipMsg.WeaponDefineId} head {playerEquipMsg.HeadId} body {playerEquipMsg.BodyId} acc {playerEquipMsg.AccessoriesId}"); } } } private void AutoContinueTick(float now) { if (InstanceProfile.AutoContinue && !GameActive && !(now < _nextAutoContinue)) { _nextAutoContinue = now + 2f; if (_autoContinueAttempts < 300) { _autoContinueAttempts++; PressThroughTitle(_autoContinueAttempts <= 3 || _autoContinueAttempts % 15 == 0); } } } public bool PressThroughTitle(bool log) { try { TitleView val = Il2CppUtil.FindObjectOfType<TitleView>(); if ((Object)(object)val == (Object)null) { if (log) { Log.Debug("[Auto] title screen not ready yet"); } return false; } try { InputManager instance = InputManager.Instance; if (instance != null) { instance.UISubmitInput((CallbackContext)null); } } catch (Exception ex) { if (log) { Log.Debug("[Auto] UISubmitInput: " + ex.Message); } } try { val.InvokeButton(); } catch (Exception ex2) { if (log) { Log.Debug("[Auto] InvokeButton: " + ex2.Message); } } try { val.Continue(); } catch (Exception ex3) { if (log) { Log.Debug("[Auto] Continue: " + ex3.Message); } } if (log) { Log.Info($"[Auto] title press + Continue() (attempt {_autoContinueAttempts})"); } return true; } catch (Exception ex4) { if (log) { Log.Debug("[Auto] continue failed: " + ex4.Message); } return false; } } private void AutoStart(float now) { bool autoHost = InstanceProfile.AutoHost; bool flag = !string.IsNullOrEmpty(InstanceProfile.AutoJoin); if (_autoStarted) { if (flag && !Session.IsActive && Session.AllowAutoJoinRetry && _autoRetries < 30 && now >= _autoRetryAt) { _autoRetryAt = now + 5f; _autoRetries++; Log.Msg($"[Auto] retrying join {InstanceProfile.AutoJoin} ({_autoRetries})"); JoinSession(InstanceProfile.AutoJoin); } } else if (!autoHost && !flag) { _autoStarted = true; } else if (_loading || _levelId < 0 || (Object)(object)GameState.LocalPlayer == (Object)null) { _autoStartAt = -1f; } else if (_autoStartAt < 0f) { _autoStartAt = now + 3f; } else if (!(now < _autoStartAt)) { _autoStarted = true; if (autoHost) { Log.Msg("[Auto] hosting"); HostSession(); } else { Log.Msg("[Auto] joining " + InstanceProfile.AutoJoin); JoinSession(InstanceProfile.AutoJoin); } } } private void TrySteamLaunchJoin() { if (!_steamLaunchJoinChecked && !Session.IsActive && _levelId >= 0 && !_loading && !((Object)(object)GameState.LocalPlayer == (Object)null)) { _steamLaunchJoinChecked = true; ulong num = SteamPresence.PendingLaunchJoin(); if (num != 0L && num != SteamPresence.LocalSteamId) { Log.Msg($"[Steam] joining {num}, who invited us before the game started"); UI.Notify("Joining your friend's session..."); JoinSteamFriend(num); } } } private INetTransport CreateTransport() { if (CoopConfig.Transport.Value == TransportKind.Steam) { if (SteamP2PTransport.IsAvailable) { return new SteamP2PTransport(); } Log.Warn("[Coop] Steam is not initialised; falling back to UDP."); UI.Notify("Steam unavailable, using UDP"); } return new UdpTransport(); } public void HostSession() { LogPrivacy.RegisterName(SteamPresence.LocalName); Session.LocalName = CoopConfig.EffectivePlayerName; Session.LocalSteamId = SteamP2PTransport.LocalSteamIdOrZero; Session.MaxPlayers = CoopConfig.MaxPlayers.Value; Session.ConfiguredEnemyHpPerExtraPlayer = CoopConfig.EnemyHpPerExtraPlayer.Value; INetTransport netTransport = CreateTransport(); if (!Session.Host(netTransport, CoopConfig.EffectivePort)) { UI.Notify("Could not start hosting: " + Session.StatusText); netTransport.Dispose(); } else if (CoopConfig.Transport.Value == TransportKind.Steam) { SteamPresence.Advertise(); } } public void JoinSteamFriend(ulong hostSteamId) { if (hostSteamId != 0L) { if (CoopConfig.Transport.Value != TransportKind.Steam) { CoopConfig.Transport.Value = TransportKind.Steam; Log.Info("[Coop] switched to the Steam transport to join a friend"); } JoinSession(hostSteamId.ToString()); } } public void InviteOverSteam() { if (!Session.IsHost) { UI.Notify("Host a session first"); return; } if (!SteamPresence.IsAvailable) { UI.Notify("Steam is not available"); return; } SteamPresence.Advertise(); if (SteamPresence.OpenInviteOverlay()) { UI.Notify("Steam invite dialog opened"); } else { UI.Notify("Could not open the Steam overlay"); } } public void JoinSession(string address) { LogPrivacy.RegisterName(SteamPresence.LocalName); Session.LocalName = CoopConfig.EffectivePlayerName; Session.LocalSteamId = SteamP2PTransport.LocalSteamIdOrZero; INetTransport netTransport = CreateTransport(); if (!Session.Join(netTransport, address, CoopConfig.EffectivePort)) { UI.Notify("Could not connect: " + Session.StatusText); netTransport.Dispose(); } } public void LeaveSession() { SteamPresence.StopAdvertising(); _autoStarted = true; _autoRetries = int.MaxValue; if (Session.IsActive) { Session.Leave(); } else { UI.Notify("Not in a session"); } } public void BeforeMonsterReset() { Threat.Clear(); MonsterClient.ReleaseAll(); HealthScaling.RestoreAll(); MonsterIndex.Invalidate(); HitRelay.ClearCaches(); MonsterHost.OnLevelChanged(); } public void OnRemoteRest(int levelId) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (!Session.IsHost || !Session.IsConnected) { return; } if (GameState.CurrentLevelId == levelId && GameActive && !Loading) { LevelRoot currentLevelRoot = GameState.CurrentLevelRoot; if (currentLevelRoot != null) { currentLevelRoot.ResetMonster(); } OnLocalSceneReset(isDead: false); } else { Session.SendSceneReset(new SceneResetMsg { LevelId = levelId, IsDead = false }); } } public void OnLocalSceneReset(bool isDead) { Threat.Clear(); MonsterIndex.Invalidate(); HitRelay.ClearCaches(); MonsterHost.OnLevelChanged(); if (Session.IsHost && Session.IsConnected) { MonsterClient.ReleaseAll(); Authority.Clear(); Log.Msg("[Game] telling clients to reset their monsters (host " + (isDead ? "died" : "rested") + ")"); Session.SendSceneReset(new SceneResetMsg { LevelId = _levelId, IsDead = isDead }); } } public void OnGameLoadNewScene() { World.Clear(); HealthScaling.RestoreAll(); Log.Info("[Game] ProcedureManager.OnLoadNewScene"); MonsterIndex.Invalidate(); HitRelay.ClearCaches(); } public void OnGameStart() { Log.Info("[Game] ProcedureManager.StartGameInitAllLogic -> game active"); GameActive = true; _levelId = -1; } public void OnGameClose() { World.Clear(); HealthScaling.RestoreAll(); Log.Info("[Game] ProcedureManager.CloseAllLogic -> game inactive"); GameActive = false; Life.OnGameClose(); Threat.Clear(); Rewards.OnGameClose(); _levelId = -1; _loading = true; MonsterIndex.Invalidate(); HitRelay.ClearCaches(); MonsterHost.OnLevelChanged(); MonsterClient.ReleaseAll(); Authority.Clear(); Ghosts.DespawnAll(); Sampler.Bind(null); if (_fakeGhost != null) { ToggleFakeGhost(); } } public void RunEachFrame(float seconds, Action action) { _frameAction = action; _frameActionUntil = Time.realtimeSinceStartup + seconds; } public void RunAfter(float seconds, Action action) { _delayedAction = action; _delayedAt = Time.realtimeSinceStartup + seconds; } private void PumpFrameAction(float now) { if (_delayedAction != null && now >= _delayedAt) { Action delayedAction = _delayedAction; _delayedAction = null; try { delayedAction(); } catch (Exception ex) { Log.Warn("[Dev] delayed action: " + ex.Message); } } if (_frameAction == null) { return; } if (now > _frameActionUntil) { _frameAction = null; return; } try { _frameAction(); } catch (Exception ex2) { Log.Warn("[Dev] frame action: " + ex2.Message); _frameAction = null; } } public void ToggleFakeGhost() { if (_fakeGhost != null) { try { _fakeGhost.Despawn(); } catch (Exception ex) { Log.Error("[Fake] despawn", ex); } _fakeGhost = null; _fakePeer = null; Log.Msg("[Fake] fake ghost removed"); } else { _fakePeer = new Peer { Id = 250, Name = "FakeGhost", HandshakeDone = true }; _fakeGhost = new RemotePlayer(_fakePeer); Log.Msg("[Fake] fake ghost enabled (mirrors the local player 2m to the side)"); } } private void UpdateFakeGhost(float now) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if (_fakeGhost == null) { return; } Player localPlayer = GameState.LocalPlayer; Sampler.Bind(localPlayer); if ((Object)(object)localPlayer != (Object)null && now >= _nextFakeSend) { _nextFakeSend = now + 0.05f; PlayerStateMsg o = Sampler.Sample(_levelId, PlayerFlags.None); _fakeState.CopyFrom(o); Transform val = GameState.SafeRoot(localPlayer); Vector3 val2 = (((Object)(object)val != (Object)null) ? val.right : Vector3.right); _fakeState.X += val2.x * 2f; _fakeState.Z += val2.z * 2f; _fakeState.PeerId = 250; _fakeGhost.OnState(_fakeState, now); if (!_fakeGhost.IsSpawned && (Object)(object)Sampler.Player != (Object)null) { _fakeGhost.OnEquip(Sampler.SampleEquip("FakeGhost")); } } _fakeGhost.Update(now, _levelId, _loading, Ghosts.HolderTransform); } } public sealed class MonsterIndex { private readonly Dictionary<int, Monster> _map = new Dictionary<int, Monster>(); private float _builtAt = float.NegativeInfinity; private int _levelId = -1; private IntPtr _root; private const float RefreshSeconds = 2f; private const float MissingRetrySeconds = 0.5f; public int Count => _map.Count; public long RebuildCount { get; private set; } public long MissCount { get; private set; } public void Invalidate() { _map.Clear(); _builtAt = float.NegativeInfinity; } public Monster Find(int instanceId) { float realtimeSinceStartup = Time.realtimeSinceStartup; int currentLevelId = GameState.CurrentLevelId; LevelRoot currentLevelRoot = GameState.CurrentLevelRoot; IntPtr intPtr = ((currentLevelRoot != null) ? ((Il2CppObjectBase)currentLevelRoot).Pointer : IntPtr.Zero); if (currentLevelId != _levelId || intPtr != _root) { Invalidate(); _levelId = currentLevelId; _root = intPtr; } Monster value; bool flag = _map.TryGetValue(instanceId, out value) && Il2CppUtil.Alive((Object)(object)value); float num = realtimeSinceStartup - _builtAt; if (num < 0f || num >= (flag ? 2f : 0.5f)) { _builtAt = realtimeSinceStartup; RebuildCount++; _map.Clear(); foreach (Monster item in GameState.CollectMonsters(currentLevelRoot)) { if (Il2CppUtil.Alive((Object)(object)item)) { int num2 = GameState.MonsterInstanceId(item); if (num2 >= 0) { _map[num2] = item; } } } flag = _map.TryGetValue(instanceId, out value) && Il2CppUtil.Alive((Object)(object)value); } if (!flag) { MissCount++; } if (!flag) { return null; } return value; } } public static class InstanceProfile { public static string Name = ""; public static string PlayerName; public static bool ReadOnlySaves; public static int Port = -1; public static bool AutoContinue; public static bool AutoHost; public static string AutoJoin; public static string FileSuffix { get { if (!string.IsNullOrEmpty(Name)) { return "." + Name; } return ""; } } public static bool IsSecondary => !string.IsNullOrEmpty(Name); public static void Init() { Name = Clean(Get("AILIMITCOOP_PROFILE")); PlayerName = Get("AILIMITCOOP_NAME"); ReadOnlySaves = Flag("AILIMITCOOP_READONLY_SAVE"); AutoContinue = Flag("AILIMITCOOP_AUTOCONTINUE"); AutoHost = Flag("AILIMITCOOP_AUTOHOST"); AutoJoin = Get("AILIMITCOOP_AUTOJOIN"); string text = Get("AILIMITCOOP_PORT"); if (!string.IsNullOrEmpty(text) && int.TryParse(text, out var result)) { Port = result; } } public static string Describe() { if (IsSecondary || ReadOnlySaves || AutoContinue || AutoHost || !string.IsNullOrEmpty(AutoJoin)) { return $"profile='{Name}' name='{PlayerName}' readOnlySaves={ReadOnlySaves} autoContinue={AutoContinue} autoHost={AutoHost} autoJoin='{AutoJoin}' port={((Port > 0) ? Port.ToString() : "config")}"; } return ""; } private static string Get(string key) { try { return Environment.GetEnvironmentVariable(key)?.Trim(); } catch { return null; } } private static bool Flag(string key) { string text = Get(key); if (!string.IsNullOrEmpty(text) && text != "0") { return !text.Equals("false", StringComparison.OrdinalIgnoreCase); } return false; } private static string Clean(string s) { if (string.IsNullOrEmpty(s)) { return ""; } char[] array = s.ToCharArray(); for (int i = 0; i < array.Length; i++) { if (!char.IsLetterOrDigit(array[i]) && array[i] != '_' && array[i] != '-') { array[i] = '_'; } } return new string(array); } } [BepInPlugin("com.ailimit.coop", "AI LIMIT Co-op", "0.2.4")] public class Plugin : BasePlugin { public const string Guid = "com.ailimit.coop"; public const string Name = "AI LIMIT Co-op"; public const string Version = "0.2.4"; private static WillRenderCanvases _frameAction; private static Action _beforeInput; public override void Load() { //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) InstanceProfile.Init(); Log.Init(((BasePlugin)this).Log, Path.Combine(Paths.BepInExRootPath, "coop_trace" + InstanceProfile.FileSuffix + ".txt")); ManagedExceptionCounter.Install(); ((BasePlugin)this).Config.SaveOnConfigSet = false; CoopConfig.Bind(((BasePlugin)this).Config); ((BasePlugin)this).Config.SaveOnConfigSet = !InstanceProfile.IsSecondary; if (((BasePlugin)this).Config.SaveOnConfigSet) { try { ((BasePlugin)this).Config.Save(); } catch (IOException ex) { Log.Warn("Config is busy; using loaded settings: " + ex.Message); } } Log.Verbose = CoopConfig.Verbose.Value; CoopConfig.Verbose.SettingChanged += delegate { Log.Verbose = CoopConfig.Verbose.Value; }; Log.Msg($"{"AI LIMIT Co-op"} v{"0.2.4"} loading (transport {CoopConfig.Transport.Value}, port {CoopConfig.EffectivePort})"); Log.Info($"[Net] Protocol {5}, monster message budget {1100} bytes; send acceptance is local only."); Log.Info("[Party life] Death spectating enabled; rest or boss rewards revive, party wipe resumes native respawn."); Log.Info("[MonsterSync] Proximity simulation ownership with combat locks and independent multiplayer threat."); string text = InstanceProfile.Describe(); if (text.Length > 0) { Log.Msg("Instance profile: " + text); } Log.Trace("config bound"); if (CoopConfig.ForceRunInBackground.Value || InstanceProfile.IsSecondary) { try { Application.runInBackground = true; Log.Trace("runInBackground forced"); } catch (Exception ex2) { Log.Warn("Could not set runInBackground: " + ex2.Message); } } CoopRuntime.Create(); Log.Trace("runtime created"); try { Hooks.Apply(); Log.Trace("hooks applied"); } catch (Exception ex3) { Log.Error("Failed to apply Harmony patches", ex3); } RegisterFrameDriver(); RegisterEarlyInput(); Log.Msg($"{"AI LIMIT Co-op"} loaded. Keys: {CoopConfig.ToggleMenuKey.Value} menu, {CoopConfig.HostKey.Value} host, {CoopConfig.JoinKey.Value} join, {CoopConfig.LeaveKey.Value} leave."); } private static void RegisterEarlyInput() { try { _beforeInput = DelegateSupport.ConvertDelegate<Action>((Delegate)(Action)delegate { CoopRuntime.Instance?.BeforeInputUpdate(); }); InputSystem.onBeforeUpdate += _beforeInput; MenuInputFocus.EarlyInputRegistered = true; Log.Info("[Menu input] " + MenuInputFocus.Readiness); } catch (Exception ex) { Log.Error("Could not register menu input capture", ex); } } private static void RegisterFrameDriver() { try { _frameAction = DelegateSupport.ConvertDelegate<WillRenderCanvases>((Delegate)new Action(Hooks.FrameHooks.Tick)); Canvas.willRenderCanvases += _frameAction; Log.Trace("frame driver registered (Canvas.willRenderCanvases)"); } catch (Exception ex) { Log.Error("Failed to register the per-frame callback; the mod cannot run", ex); } } } } namespace AILimitCoop.Util { internal enum CoopWork { Input, Network, Life, LocalState, Ghosts, MonsterSend, MonsterReceive, Threat, Rewards, Other, Menu, FrameGap, Count } internal sealed class FrameTimingWindow { private readonly double[] _sum = new double[12]; private readonly double[] _max = new double[12]; private readonly int[] _count = new int[12]; public int Count(CoopWork stage) { return _count[(int)stage]; } public double Mean(CoopWork stage) { if (Count(stage) != 0) { return _sum[(int)stage] / (double)Count(stage); } return 0.0; } public double Max(CoopWork stage) { return _max[(int)stage]; } public void Record(CoopWork stage, double milliseconds) { if (double.IsFinite(milliseconds) && !(milliseconds < 0.0) && stage >= CoopWork.Input && stage < CoopWork.Count) { _count[(int)stage]++; _sum[(int)stage] += milliseconds; _max[(int)stage] = Math.Max(_max[(int)stage], milliseconds); } } public void Clear() { Array.Clear(_sum, 0, _sum.Length); Array.Clear(_max, 0, _max.Length); Array.Clear(_count, 0, _count.Length); } public string Describe() { StringBuilder stringBuilder = new StringBuilder("stage ms mean/max (calls):"); for (int i = 0; i < _count.Length; i++) { if (_count[i] != 0) { stringBuilder.Append(' ').Append((CoopWork)i).Append('=') .Append(Mean((CoopWork)i).ToString("F2", CultureInfo.InvariantCulture)) .Append('/') .Append(_max[i].ToString("F2", CultureInfo.InvariantCulture)) .Append('(') .Append(_count[i]) .Append(')'); } } return stringBuilder.ToString(); } } public static class Il2CppUtil { public static Object StringKey(string s) { return (Object)(object)String.op_Implicit(s); } public static bool Alive(Object o) { try { if (o == null || ((Il2CppObjectBase)o).Pointer == IntPtr.Zero) { return false; } return o.m_CachedPtr != IntPtr.Zero || o != (Object)null; } catch { return false; } } public static Vector3 MakeVec3(float x, float y, float z) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) return new Vector3 { x = x, y = y, z = z }; } public static Rect MakeRect(float x, float y, float width, float height) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) return new Rect { m_XMin = x, m_YMin = y, m_Width = width, m_Height = height }; } public static Color MakeColor(float r, float g, float b, float a) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) return new Color { r = r, g = g, b = b, a = a }; } public static Quaternion Yaw(float degrees) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) double num = (double)degrees * (Math.PI / 360.0); return new Quaternion { y = (float)Math.Sin(num), w = (float)Math.Cos(num) }; } public static float SqrDistance(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) float num = a.x - b.x; float num2 = a.y - b.y; float num3 = a.z - b.z; return num * num + num2 * num2 + num3 * num3; } public static T FindObjectOfType<T>() where T : Object { try { Object val = Object.FindObjectOfType(Il2CppType.Of<T>()); return (val == (Object)null) ? default(T) : ((Il2CppObjectBase)val).TryCast<T>(); } catch (Exception ex) { Log.Debug("FindObjectOfType<" + typeof(T).Name + "> failed: " + ex.Message); return default(T); } } public static IEnumerable<T> Each<T>(List<T> list) { if (list == null) { yield break; } int count; try { count = list.Count; } catch { yield break; } for (int i = 0; i < count; i++) { T val; try { val = list[i]; } catch { break; } yield return val; } } public static IEnumerable<T> Each<T>(Il2CppArrayBase<T> array) { if (array != null) { int count = array.Length; for (int i = 0; i < count; i++) { yield return array[i]; } } } public static Il2CppStructArray<byte> ToIl2CppBytes(byte[] data, int length) { Il2CppStructArray<byte> val = new Il2CppStructArray<byte>((long)length); for (int i = 0; i < length; i++) { ((Il2CppArrayBase<byte>)(object)val)[i] = data[i]; } return val; } public static int StableHash(string s) { if (string.IsNullOrEmpty(s)) { return 0; } uint num = 2166136261u; foreach (char c in s) { num ^= c; num *= 16777619; } return (int)num; } public static string DescribeHierarchy(GameObject root, int maxDepth = 2, int maxChildren = 40) { StringBuilder stringBuilder = new StringBuilder(); Describe(root.transform, 0, maxDepth, maxChildren, stringBuilder); return stringBuilder.ToString(); } private static void Describe(Transform t, int depth, int maxDepth, int maxChildren, StringBuilder sb) { sb.Append(' ', depth * 2).Append(((Object)t).name).Append(((Component)t).gameObject.activeSelf ? "" : " (inactive)"); try { Il2CppArrayBase<Component> components = ((Component)t).GetComponents<Component>(); List<string> list = new List<string>(); foreach (Component item in Each<Component>(components)) { if ((Object)(object)item == (Object)null) { continue; } string text = ((MemberInfo)((Object)item).GetIl2CppType()).Name; if (!(text == "Transform")) { Behaviour val = ((Il2CppObjectBase)item).TryCast<Behaviour>(); if ((Object)(object)val != (Object)null && !val.enabled) { text += "(off)"; } list.Add(text); } } if (list.Count > 0) { sb.Append(" [").Append(string.Join(", ", list)).Append(']'); } } catch (Exception ex) { sb.Append(" <components failed: ").Append(ex.Message).Append('>'); } sb.AppendLine(); if (depth < maxDepth) { int childCount = t.childCount; for (int i = 0; i < childCount && i < maxChildren; i++) { Describe(t.GetChild(i), depth + 1, maxDepth, maxChildren, sb); } if (childCount > maxChildren) { sb.Append(' ', (depth + 1) * 2).Append("... ").Append(childCount - maxChildren) .AppendLine(" more"); } } } public static string Vec(Vector3 v) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) return $"({v.x:F2}, {v.y:F2}, {v.z:F2})"; } } public static class Log { private static ManualLogSource _source; public static bool Verbose; private static string _tracePath; public static void Init(ManualLogSource source, string tracePath = null) { _source = source; _tracePath = tracePath; if (_tracePath != null) { try { File.WriteAllText(_tracePath, $"{DateTime.Now:HH:mm:ss.fff} trace start\n"); } catch { _tracePath = null; } } } public static void Trace(string msg) { if (_tracePath == null) { return; } try { File.AppendAllText(_tracePath, $"{DateTime.Now:HH:mm:ss.fff} {LogPrivacy.Redact(msg)}\n"); } catch { } } public static void Info(string msg) { ManualLogSource source = _source; if (source != null) { source.LogInfo((object)LogPrivacy.Redact(msg)); } } public static void Msg(string msg) { ManualLogSource source = _source; if (source != null) { source.LogMessage((object)LogPrivacy.Redact(msg)); } } public static void Warn(string msg) { ManualLogSource source = _source; if (source != null) { source.LogWarning((object)LogPrivacy.Redact(msg)); } } public static void Error(string msg) { ManualLogSource source = _source; if (source != null) { source.LogError((object)LogPrivacy.Redact(msg)); } } public static void Error(string context, Exception ex) { ManualLogSource source = _source; if (source != null) { source.LogError((object)LogPrivacy.Redact($"{context}: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}")); } } public static void Debug(string msg) { if (Verbose) { ManualLogSource source = _source; if (source != null) { source.LogInfo((object)("[dbg] " + LogPrivacy.Redact(msg))); } } } } public static class LogPrivacy { private const int MaxLabelsPerKind = 256; private const int MaxNameLength = 256; private const RegexOptions Options = RegexOptions.Compiled | RegexOptions.CultureInvariant; private static readonly Regex SteamIds; private static readonly Regex Ipv4; private static readonly Regex Ipv6; private static readonly Regex AccountFolder; private static readonly Regex Placeholder; private static readonly HashSet<string> LogWords; private static readonly object Gate; private static readonly Dictionary<string, string> SteamLabels; private static readonly Dictionary<string, string> IpLabels; private static readonly Dictionary<string, string> NameLabels; private static Regex _names; private static Regex _userName; private static Regex _machineName; static LogPrivacy() { SteamIds = new Regex("(?<!\\d)7656119\\d{10}(?!\\d)", RegexOptions.Compiled | RegexOptions.CultureInvariant); Ipv4 = new Regex("(?<![\\w.])(?<!Version=)(?:\\d{1,3}\\.){3}\\d{1,3}(?!\\w|\\.\\d)", RegexOptions.Compiled | RegexOptions.CultureInvariant); Ipv6 = new Regex("(?<![\\w:.])(?:[0-9A-Fa-f]{0,4}:){2,7}[0-9A-Fa-f]{0,4}(?![\\w:])", RegexOptions.Compiled | RegexOptions.CultureInvariant); AccountFolder = new Regex("((?:[A-Za-z]:[\\\\/]+Users|(?:^|[\\\\/])home)[\\\\/]+)(?:[^\\\\/\\r\\n\"'<>|:*?]{1,64}?(?=[\\\\/])|[^\\s\\\\/\"'<>|:*?;,()\\[\\]]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant); Placeholder = new Regex("^(?:Player\\d*|steam-user-\\d+|ip-\\d+|name-\\d+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant); LogWords = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "host", "client", "peer", "player", "ghost", "session", "steam", "udp", "user", "name", "level", "monster", "monstersync", "boss", "game", "coop", "party", "life", "menu", "input", "chat", "local", "unknown", "test", "hit", "threat", "rewards", "overlay", "hooks", "interaction", "perf", "net", "send", "types", "auto", "fake", "dev", "dbg", "trace", "moderation", "combat", "safety", "joined", "left", "username", "computername" }; Gate = new object(); SteamLabels = new Dictionary<string, string>(StringComparer.Ordinal); IpLabels = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); NameLabels = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); string userName = null; string machineName = null; try { userName = Environment.UserName; } catch { } try { machineName = Environment.MachineName; } catch { } UseEnvironment(userName, machineName); } public static void UseEnvironment(string userName, string machineName) { _userName = (IsHideable(userName) ? WholeWords(new string[1] { userName.Trim() }) : null); _machineName = (IsHideable(machineName) ? WholeWords(new string[1] { machineName.Trim() }) : null); } public static void RegisterName(string name) { if (!IsHideable(name)) { return; } name = name.Trim(); lock (Gate) { if (!NameLabels.ContainsKey(name) && NameLabels.Count < 256) { NameLabels[name] = "name-" + (NameLabels.Count + 1); _names = WholeWords(NameLabels.Keys); } } } public static void Reset() { lock (Gate) { SteamLabels.Clear(); IpLabels.Clear(); NameLabels.Clear(); _names = null; } } public static string Redact(string text) { if (string.IsNullOrEmpty(text)) { return text; } text = AccountFolder.Replace(text, "$1%USERNAME%"); text = SteamIds.Replace(text, (Match m) => Label(SteamLabels, m.Value, "steam-user")); text = Ipv4.Replace(text, (Match m) => (!IsHiddenIpv4(m.Value)) ? m.Value : Label(IpLabels, m.Value, "ip")); text = Ipv6.Replace(text, (Match m) => (!LooksLikeIpv6(m.Value)) ? m.Value : Label(IpLabels, m.Value, "ip")); Regex names = _names; Regex userName = _userName; Regex machineName = _machineName; if (names != null) { text = names.Replace(text, delegate(Match m) { lock (Gate) { string value; return NameLabels.TryGetValue(m.Value, out value) ? value : "name"; } }); } if (machineName != null) { text = machineName.Replace(text, "%COMPUTERNAME%"); } if (userName != null) { text = userName.Replace(text, "%USERNAME%"); } return text; } private static string Label(Dictionary<string, string> labels, string value, string kind) { lock (Gate) { if (labels.TryGetValue(value, out var value2)) { return value2; } if (labels.Count >= 256) { return kind; } return labels[value] = kind + "-" + (labels.Count + 1); } } private static bool IsHiddenIpv4(string text) { string[] array = text.Split('.'); int[] array2 = new int[4]; for (int i = 0; i < 4; i++) { if (!int.TryParse(array[i], out array2[i]) || array2[i] > 255) { return false; } } if (array2[0] != 127) { return (array2[0] | array2[1] | array2[2] | array2[3]) != 0; } return false; } private static bool LooksLikeIpv6(string text) { if (text == "::" || text == "::1" || !text.Any(Uri.IsHexDigit)) { return false; } if (!text.Contains("::")) { return text.Count((char c) => c == ':') >= 5; } return true; } private static bool IsHideable(string word) { word = word?.Trim(); if (!string.IsNullOrEmpty(word) && word.Length >= 3 && word.Length <= 256 && word.Any(char.IsLetter) && !LogWords.Contains(word)) { return !Placeholder.IsMatch(word); } return false; } private static Regex WholeWords(IEnumerable<string> words) { return new Regex("(?<![\\p{L}\\p{N}_])(?:" + string.Join("|", words.OrderByDescending((string w) => w.Length).Select(Regex.Escape)) + ")(?![\\p{L}\\p{N}_])", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } } internal static class ManagedExceptionCounter { private static long _count; private static int _installed; public static long Count => Interlocked.Read(in _count); public static void Install() { if (Interlocked.Exchange(ref _installed, 1) == 0) { AppDomain.CurrentDomain.FirstChanceException += delegate { Interlocked.Increment(ref _count); }; } } } internal sealed class SessionPerformance { private readonly FrameTimingWindow _window = new FrameTimingWindow(); private readonly int[] _gc = new int[3]; private bool _active; private long _stamp; private long _lastFrame; private long _allocated; private long _scans; private long _misses; private long _scanFaults; private long _executeSkips; private long _exceptions; private float _windowStart; private static double Milliseconds(long ticks) { return (double)ticks * (1000.0 / (double)Stopwatch.Frequency); } public void Begin(CoopRuntime rt, float now) { bool flag = rt.GameActive && rt.Session.IsConnected; if (flag != _active) { _active = flag; _window.Clear(); _lastFrame = 0L; ResetBaseline(rt, now); } if (_active) { _stamp = Stopwatch.GetTimestamp(); if (_lastFrame != 0L) { _window.Record(CoopWork.FrameGap, Milliseconds(_stamp - _lastFrame)); } _lastFrame = _stamp; } } public void Mark(CoopWork stage) { if (_active) { long timestamp = Stopwatch.GetTimestamp(); _window.Record(stage, Milliseconds(timestamp - _stamp)); _stamp = timestamp; } } public long BeginMenu() { if (!_active) { return 0L; } return Stopwatch.GetTimestamp(); } public void EndMenu(long stamp) { if (stamp != 0L && _active) { _window.Record(CoopWork.Menu, Milliseconds(Stopwatch.GetTimestamp() - stamp)); } } private void ResetBaseline(CoopRuntime rt, float now) { _windowStart = now; _allocated = GC.GetAllocatedBytesForCurrentThread(); for (int i = 0; i < _gc.Length; i++) { _gc[i] = GC.CollectionCount(i); } _scans = rt.MonsterIndex.RebuildCount; _misses = rt.MonsterIndex.MissCount; _scanFaults = Hooks.ExecuteScanFaults; _executeSkips = Hooks.GhostExecuteSkips; _exceptions = ManagedExceptionCounter.Count; } public void End(CoopRuntime rt, float now) { if (!_active) { return; } Mark(CoopWork.Other); if (now - _windowStart < 30f) { return; } try { double num = now - _windowStart; double value = (double)Math.Max(0L, GC.GetAllocatedBytesForCurrentThread() - _allocated) / 1048576.0 / num; string value2 = "unavailable"; try { using Process process = Process.GetCurrentProcess(); value2 = ((double)process.WorkingSet64 / 1048576.0).ToString("F0", CultureInfo.InvariantCulture); } catch { } Log.Info($"[Perf] windowSec={num:F1} level={rt.LevelId} loading={rt.Loading} menu={rt.MenuVisible} managedMiB={(double)GC.GetTotalMemory(forceFullCollection: false) / 1048576.0:F1} residentMiB={value2} mainThreadManagedAllocMiBps={value:F2} gc0/1/2={GC.CollectionCount(0) - _gc[0]}/{GC.CollectionCount(1) - _gc[1]}/{GC.CollectionCount(2) - _gc[2]} ghosts={rt.Ghosts.Count} retiredGhostPointers={GhostRegistry.ZombieCount} ghostExecuteSkips={Hooks.GhostExecuteSkips - _executeSkips} executeScanFaults={Hooks.ExecuteScanFaults - _scanFaults} managedExceptions={ManagedExceptionCounter.Count - _exceptions} replicas={rt.MonsterClient.ControlledCount} indexActors={rt.MonsterIndex.Count} indexScans={rt.MonsterIndex.RebuildCount - _scans} missingLookups={rt.MonsterIndex.MissCount - _misses} safetyTotalsLateMachines/Enemy/Camera={GhostMachineSafety.LateMachines}/{NativeCombatSafety.InvalidEnemyUpdates}/{NativeCombatSafety.InvalidCameraTargets}"); Log.Info("[Perf] " + _window.Describe()); } catch (Exception ex) { Log.Debug("[Perf] summary unavailable: " + ex.Message); } finally { _window.Clear(); ResetBaseline(rt, now); } } } public static class UnityMath { public static float Max(float a, float b) { if (!(a > b)) { return b; } return a; } public static float Min(float a, float b) { if (!(a < b)) { return b; } return a; } public static float Abs(float value) { return Math.Abs(value); } public static float Sqrt(float value) { return (float)Math.Sqrt(value); } public static float Floor(float value) { return (float)Math.Floor(value); } public static float Exp(float power) { return (float)Math.Exp(power); } public static float Clamp(float value, float min, float max) { if (value < min) { value = min; } else if (value > max) { value = max; } return value; } public static float Clamp01(float value) { if (!(value < 0f)) { if (!(value > 1f)) { return value; } return 1f; } return 0f; } public static float Lerp(float a, float b, float t) { return a + (b - a) * Clamp01(t); } public static float MoveTowards(float current, float target, float maxDelta) { if (!(Math.Abs(target - current) <= maxDelta)) { return current + ((target - current >= 0f) ? 1f : (-1f)) * maxDelta; } return target; } public static float Repeat(float t, float length) { return Clamp(t - Floor(t / length) * length, 0f, length); } public static float LerpAngle(float a, float b, float t) { float num = Repeat(b - a, 360f); if (num > 180f) { num -= 360f; } return a + num * Clamp01(t); } } } namespace AILimitCoop.UI { public sealed class ChatLine { public string Name = "Player"; public string Text = ""; public uint Accent = 12297593u; public double At; public float Opacity = 1f; internal float WrapWidth = -1f; internal float NameWidth; internal string First; internal string Second; } public sealed class ChatLog { public const int Capacity = 40; public const int MaxLength = 96; public const int ClosedLines = 5; public const int OpenLines = 8; public const double VisibleSeconds = 12.0; public const double FadeSeconds = 2.0; private readonly List<ChatLine> _lines = new List<ChatLine>(40); public int Count => _lines.Count; public void Add(string name, uint accent, string text, double now) { text = CleanMessage(text); if (text.Length != 0) { ChatLine chatLine; if (_lines.Count == 40) { chatLine = _lines[0]; _lines.RemoveAt(0); } else { chatLine = new ChatLine(); } chatLine.Name = PartyPresentation.Clean(name, 24); chatLine.Text = text; chatLine.Accent = accent; chatLine.At = now; chatLine.Opacity = 1f; chatLine.WrapWidth = -1f; chatLine.First = (chatLine.Second = null); _lines.Add(chatLine); } } public void Clear() { _lines.Clear(); } public void Visible(List<ChatLine> into, double now, bool open) { into.Clear(); for (int i = Math.Max(0, _lines.Count - (open ? 8 : 5)); i < _lines.Count; i++) { ChatLine chatLine = _lines[i]; chatLine.Opacity = (open ? 1f : Fade(now - chatLine.At)); if (chatLine.Opacity > 0f) { into.Add(chatLine); } } } public static float Fade(double age) { if (!(age >= 12.0)) { if (!(age <= 10.0)) { return (float)((12.0 - age) / 2.0); } return 1f; } return 0f; } public static string CleanMessage(string text, int maxElements = 96) { if (string.IsNullOrWhiteSpace(text)) { return ""; } StringBuilder stringBuilder = new StringBuilder(Math.Min(text.Length, maxElements * 2)); TextElementEnumerator textElementEnumerator = StringInfo.GetTextElementEnumerator(text.Trim()); int num = 0; while (num < maxElements && textElementEnumerator.MoveNext()) { string textElement = textElementEnumerator.GetTextElement(); if (char.IsControl(textElement, 0)) { if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != ' ') { stringBuilder.Append(' '); num++; } } else { stringBuilder.Append(textElement); num++; } } return stringBuilder.ToString().Trim(); } } public sealed class ChatKeyGate { public bool Armed { get; private set; } public void Opened() { Armed = false; } public void Closed() { Armed = false; } public void Observe(bool keyHeld) { if (!keyHeld) { Armed = true; } } } public sealed class ChatSendLimiter { public const double Interval = 0.35; private double _last = double.NegativeInfinity; public bool TryAcquire(double now) { if (now - _last < 0.35) { return false; } _last = now; return true; } } public sealed class CoopUI { private sealed class PeerView { public Peer Peer; public readonly PartyMemberView View = new PartyMemberView(); public int DetailKind = -1; public int DetailDistance = -1; public int DetailPing = -1; public float HealthHp = float.NaN; public float HealthMax = float.NaN; } private struct AllyBarPlacement { public float Distance; public float X; public float Y; public float Width; public float Opacity; public float NameOpacity; public float Health; public float Trail; public string Name; } private const float DefaultCharacterHeight = 1.7f; private const float AllyBarClearance = 0.2f; private const float ChatLeft = 28f; private const float ChatBottomMargin = 180f; private const string ChatControlName = "AILimitCoopChat"; private static readonly Color White = Il2CppUtil.MakeColor(1f, 1f, 1f, 1f); private readonly CoopRuntime _rt; private readonly List<(string text, float until)> _notices = new List<(string, float)>(); private readonly List<PartyMemberView> _members = new List<PartyMemberView>(16); private readonly List<PartyMemberView> _allies = new List<PartyMemberView>(15); private readonly Dictionary<byte, PeerView> _peerViews = new Dictionary<byte, PeerView>(); private readonly List<byte> _departed = new List<byte>(); private readonly PartyMemberView _local = new PartyMemberView(); private float _localHealthHp = float.NaN; private float _localHealthMax = float.NaN; private readonly List<AllyBarPlacement> _allyBars = new List<AllyBarPlacement>(15); private readonly ChatLog _chat = new ChatLog(); private readonly List<ChatLine> _chatLines = new List<ChatLine>(8); private readonly ChatKeyGate _chatGate = new ChatKeyGate(); private readonly ChatSendLimiter _chatLimiter = new ChatSendLimiter(); private UnityPartyCanvas _canvas; private GUIStyle _buttonStyle; private GUIStyle _fieldStyle; private GUIStyle _chatFieldStyle; private string _address; private string _chatDraft = ""; private float _nextViewRefresh; private float _lastViewRefresh; private float _nextNameRefresh; private string _localName = "Player"; private bool _details; private int _partyPage; private bool _managePlayers; private bool _showBans; private int _moderationPage; private readonly List<PartyModerationRow> _moderationRows = new List<PartyModerationRow>(); private readonly List<Peer> _moderationPeers = new List<Peer>(); private readonly List<ulong> _moderationIds = new List<ulong>(); private bool _fieldUnavailable; private bool _chatFieldUnavailable; private float _styleRetryAt; private bool _menuPainted; private float _measuredHeight; private float _nextHeightSample; private bool _allyBarErrorLogged; private bool _chatErrorLogged; private Vector2 _mouse; private (bool Active, bool Host, bool Connected, bool Steam, int Rtt) _connectionKey = (Active: false, Host: false, Connected: false, Steam: false, Rtt: int.MinValue); private string _connectionText = "OFFLINE"; private int _menuKeyValue = int.MinValue; private string _menuKeyText = ""; private string _spectatorFor; private string _spectatorText = ""; public bool HasStyles => _canvas != null; public bool ChatOpen { get; private set; } private static float BaseScale => UnityMath.Clamp((float)Screen.height / 1080f, 0.8f, 3f); public bool CanChat { get { if (CoopConfig.ShowChat.Value && CoopConfig.EnableOverlay.Value && HasStyles && OverlayHost.IsAlive && _rt.Session.IsConnected && _rt.GameActive && !_rt.Loading && !_rt.MenuVisible) { return !NativeMenuOpen(); } return false; } } private static float ChatScale => BaseScale * CoopConfig.HudScale.Value; public CoopUI(CoopRuntime rt) { _rt = rt; } public void Notify(string text) { Notify(text, text); } public void Notify(string text, string logText) { Log.Msg("[UI] " + logText); _notices.Add((PartyPresentation.Clean(text, 180), Time.realtimeSinceStartup + 6f)); if (_notices.Count > 3) { _notices.RemoveAt(0); } } private bool EnsureStyles() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) if (_canvas != null) { return true; } if (Time.realtimeSinceStartup < _styleRetryAt) { return false; } try { Color textColor = UnityPartyCanvas.ColorOf(15262937u); _buttonStyle = new GUIStyle(); _buttonStyle.richText = false; _fieldStyle = new GUIStyle(); _fieldStyle.fontSize = 13; _fieldStyle.alignment = (TextAnchor)3; _fieldStyle.clipping = (TextClipping)1; _fieldStyle.richText = false; _fieldStyle.padding = new RectOffset(10, 10, 0, 0); _fieldStyle.normal.textColor = textColor; _fieldStyle.focused.textColor = textColor; _chatFieldStyle = new GUIStyle(); _chatFieldStyle.fontSize = 12; _chatFieldStyle.alignment = (TextAnchor)3; _chatFieldStyle.clipping = (TextClipping)1; _chatFieldStyle.richText = false; _chatFieldStyle.normal.textColor = textColor; _chatFieldStyle.focused.textColor = textColor; _chatFieldStyle.hover.textColor = textColor; _chatFieldStyle.active.textColor = textColor; _canvas = new UnityPartyCanvas(); return true; } catch (Exception ex) { _styleRetryAt = Time.realtimeSinceStartup + 10f; Log.Warn("[UI] styles: " + ex.Message); return false; } } public void Draw(bool trace = false) { //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (trace) { Log.Trace("UI: party styles"); } if (!EnsureStyles()) { return; } RefreshParty(); Matrix4x4 matrix = GUI.matrix; Color color = GUI.color; Color contentColor = GUI.contentColor; Color backgroundColor = GUI.backgroundColor; bool enabled = GUI.enabled; try { GUI.color = (GUI.contentColor = (GUI.backgroundColor = White)); GUI.enabled = true; _canvas.BeginPass(); bool repaint = _canvas.Repaint; if (!_rt.MenuVisible) { _menuPainted = false; } if (repaint) { DrawAllyBars(); if (CoopConfig.ShowHud.Value && !_rt.MenuVisible) { DrawHud(); } if (!_rt.MenuVisible) { DrawChatFeed(); } } if (_rt.MenuVisible) { bool num = !_menuPainted && repaint; if (num) { Log.Info("[UI] party menu first repaint starting"); } DrawMenu(); if (num) { _menuPainted = true; Log.Info("[UI] party menu first repaint complete"); } } if (ChatOpen) { DrawChatInput(); } if (repaint) { if (_rt.Life.IsSpectating && !_rt.MenuVisible) { DrawSpectator(); } DrawNotices(); } } finally { GUI.matrix = matrix; GUI.color = color; GUI.contentColor = contentColor; GUI.backgroundColor = backgroundColor; GUI.enabled = enabled; } } private void RefreshParty() { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_04b3: Unknown result type (might be due to invalid IL or missing references) //IL_04ca: Unknown result type (might be due to invalid IL or missing references) float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextViewRefresh) { return; } float dt = UnityMath.Clamp(realtimeSinceStartup - _lastViewRefresh, 0f, 0.3f); _lastViewRefresh = realtimeSinceStartup; _nextViewRefresh = realtimeSinceStartup + 0.1f; Session session = _rt.Session; _members.Clear(); _allies.Clear(); Player val = ((_rt.GameActive && !GameState.IsLoadingLevel) ? GameState.LocalPlayer : null); bool flag = (Object)(object)val != (Object)null; Vector3 a = (Vector3)(flag ? GameState.SafePosition(val) : default(Vector3)); if (realtimeSinceStartup >= _nextNameRefresh) { _nextNameRefresh = realtimeSinceStartup + 2f; _localName = CoopConfig.EffectivePlayerName; } _local.Name = _localName; _local.Role = (session.IsHost ? "YOU / HOST" : "YOU"); _local.Accent = PartyTheme.Accent(session.IsActive ? session.LocalPeerId : 0); _local.Status = (flag ? "READY" : "NOT IN WORLD"); _local.StatusColor = (flag ? 13225156u : 11184543u); _local.Detail = (session.IsHost ? "Leading the party" : (session.IsConnected ? "Connected to host" : "Your character")); SetHealth(_local, flag ? GameState.SafeHp(val) : 0f, flag ? GameState.SafeHpMax(val) : 0f, flag, dt, ref _localHealthHp, ref _localHealthMax); if (_local.HealthKnown && _local.Health <= 0f) { _local.Status = "DEAD"; _local.StatusColor = 13994619u; } if (_rt.Life.IsSpectating) { _local.Status = "SPECTATING"; } _members.Add(_local); _departed.Clear(); foreach (KeyValuePair<byte, PeerView> peerView in _peerViews) { if (session.GetPeer(peerView.Key) != peerView.Value.Peer) { _departed.Add(peerView.Key); } } foreach (byte item in _departed) { _peerViews.Remove(item); } IReadOnlyList<Peer> peers = session.Peers; for (int i = 0; i < peers.Count; i++) { Peer peer = peers[i]; if (!_peerViews.TryGetValue(peer.Id, out var value)) { value = new PeerView { Peer = peer }; _peerViews[peer.Id] = value; } PartyMemberView view = value.View; PlayerStateMsg lastState = peer.LastState; bool flag2 = peer.LastStateReceivedAt >= 0f && session.Now - peer.LastStateReceivedAt > 3f; bool flag3 = lastState == null || (lastState.Flags & PlayerFlags.Loading) != 0; bool flag4 = flag && lastState != null && lastState.LevelId == _rt.LevelId; bool flag5 = lastState != null && (lastState.Flags & PlayerFlags.Dead) != 0; view.Name = peer.Name; view.Role = ((peer.Id == 0) ? "HOST" : "ALLY"); view.Accent = PartyTheme.Accent(peer.Id); view.StatusColor = 13225156u; if (flag2) { view.Status = "CONNECTION SLOW"; view.StatusColor = 12954232u; } else if (flag3) { view.Status = "LOADING"; view.StatusColor = 11184543u; } else if (flag5) { view.Status = (((lastState.Flags & PlayerFlags.Spectating) != PlayerFlags.None) ? "SPECTATING" : "DEAD"); view.StatusColor = 13994619u; } else if ((lastState.Flags & PlayerFlags.InCombat) != PlayerFlags.None) { view.Status = "IN COMBAT"; view.StatusColor = 12954232u; } else { view.Status = "EXPLORING"; } int num = ((!flag3) ? ((!flag) ? 1 : ((!flag4) ? 2 : 3)) : 0); int num2 = -1; if (flag4 && !flag3 && !flag2) { float num3 = UnityMath.Sqrt(Il2CppUtil.SqrDistance(a, Il2CppUtil.MakeVec3(lastState.X, lastState.Y, lastState.Z))); if (float.IsFinite(num3)) { num = 4; num2 = (int)UnityMath.Clamp(num3, 0f, 9999f); } } int num4 = (session.IsHost ? peer.RttMs : ((peer.Id == 0) ? session.HostRttMs : (-1))); if (num4 <= 0) { num4 = -1; } if (num != value.DetailKind || num2 != value.DetailDistance || num4 != value.DetailPing) { value.DetailKind = num; value.DetailDistance = num2; value.DetailPing = num4; string text = num switch { 0 => "Waiting for player", 1 => "In the world", 2 => "In another area", 4 => num2 + " m away", _ => "In your area", }; view.Detail = ((num4 > 0) ? (text + " / " + num4 + " ms") : text); } SetHealth(view, lastState?.Hp ?? 0f, lastState?.HpMax ?? 0f, !flag3 && !flag2, dt, ref value.HealthHp, ref value.HealthMax); if (view.HealthKnown && view.Health <= 0.25f && !flag5 && !flag2) { view.Status = "LOW HEALTH"; view.StatusColor = 13994619u; } _allies.Add(view); _members.Add(view); } } private static void SetHealth(PartyMemberView view, float hp, float max, bool known, float dt, ref float textHp, ref float textMax) { bool healthKnown = view.HealthKnown; view.HealthKnown = known && float.IsFinite(hp) && float.IsFinite(max) && max > 0f; view.Health = (view.HealthKnown ? PartyPresentation.Fraction(hp, max) : 0f); view.Trail = ((!healthKnown || !view.HealthKnown) ? view.Health : UnityMath.MoveTowards(view.Trail, view.Health, dt * 0.55f)); view.Trail = UnityMath.Max(view.Trail, view.Health); float num = (view.HealthKnown ? Math.Clamp(hp, 0f, max) : float.NaN); float num2 = (view.HealthKnown ? max : float.NaN); if (!num.Equals(textHp) || !num2.Equals(textMax)) { textHp = num; textMax = num2; view.HealthText = (view.HealthKnown ? (num.ToString("N0") + " / " + max.ToString("N0")) : "--"); } } private string ConnectionText() { Session session = _rt.Session; (bool, bool, bool, bool, int) tuple = (session.IsActive, session.IsHost, session.IsConnected, session.Transport is SteamP2PTransport, (!session.IsHost) ? session.HostRttMs : 0); (bool, bool, bool, bool, int) tuple2 = tuple; (bool, bool, bool, bool, int) connectionKey = _connectionKey; if (tuple2.Item1 == connectionKey.Item1 && tuple2.Item2 == connectionKey.Item2 && tuple2.Item3 == connectionKey.Item3 && tuple2.Item4 == connectionKey.Item4 && tuple2.Item5 == connectionKey.Item5) { return _connectionText; } _connectionKey = tuple; string text = (tuple.Item4 ? "STEAM" : "DIRECT"); _connectionText = ((!session.IsActive) ? "OFFLINE" : ((!session.IsHost && !session.IsConnected) ? "CONNECTING" : (session.IsHost ? ("HOSTING / " + text) : (text + " / " + ((session.HostRttMs > 0) ? (session.HostRttMs + " ms") : "CONNECTED"))))); return _connectionText; } private string MenuKeyText() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected I4, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) int num = (int)CoopConfig.ToggleMenuKey.Value; if (num == _menuKeyValue) { return _menuKeyText; } _menuKeyValue = num; _menuKeyText = ((object)CoopConfig.ToggleMenuKey.Value/*cast due to .constrained prefix*/).ToString(); return _menuKeyText; } private static void Scale(float scale) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) GUI.matrix = new Matrix4x4 { m00 = scale, m11 = scale, m22 = 1f, m33 = 1f }; } private void DrawHud() { if (_rt.Session.IsActive) { float num = PartyPresentation.HudHeight(_allies.Count); float num2 = PartyPresentation.HudScale(Screen.width, Screen.height, CoopConfig.HudScale.Value, _allies.Count); Scale(num2); float num3 = (float)Screen.width / num2; float num4 = (float)Screen.height / num2; float num5 = 20f; PartyHudCorner value = CoopConfig.HudCorner.Value; bool flag = value == PartyHudCorner.TopRight || value == PartyHudCorner.BottomRight; bool flag2 = value == PartyHudCorner.BottomRight || value == PartyHudCorner.BottomLeft; PartyPresentation.Hud(_canvas, flag ? (num3 - 292f - num5) : num5, flag2 ? (num4 - num - num5) : num5, _allies, ConnectionText(), MenuKeyText(), CoopConfig.HudOpacity.Value); } } private void DrawMenu() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) float b = UnityMath.Min(BaseScale, UnityMath.Min(((float)Screen.width - 32f) / 860f, ((float)Screen.height - 32f) / 682f)); b = UnityMath.Max(0.25f, b); Scale(b); _mouse = Event.current.mousePosition; float num = (float)Screen.width / b; float num2 = (float)Screen.height / b; float num3 = (num - 860f) / 2f; float num4 = (num2 - 682f) / 2f; _canvas.Fill(0f, 0f, num, num2, 395016u, 0.58f); string footer = (_details ? $"Build {"0.2.4"} / Hooks {Hooks.Applied}/{Hooks.Applied + Hooks.Failed} / Enemies {_rt.MonsterClient.ControlledCount} / {_rt.Session.StatusText}" : (((object)CoopConfig.ToggleMenuKey.Value/*cast due to .constrained prefix*/).ToString() + " party menu / " + ((object)CoopConfig.LeaveKey.Value/*cast due to .constrained prefix*/).ToString() + " leave session")); PartyPresentation.MenuFrame(_canvas, num3, num4, ConnectionText(), _rt.Session.IsActive, footer, _rt.LastError, _rt.GameActive && !GameState.IsLoadingLevel); if (Button(num3 + 758f, num4 + 24f, 78f, 42f, "CLOSE")) { _rt.MenuVisible = false; } DrawConnections(num3 + 24f, num4 + 110f); if (_rt.Session.IsClient) { _managePlayers = false; } if (_managePlayers) { DrawModeration(num3 + 304f, num4 + 110f); } else { DrawPartyRoster(num3, num4); } DrawHudSettings(num3 + 24f, num4 + 575f); if (Button(num3 + 622f, num4 + 575f, 76f, 30f, "PLAYERS", primary: false, !_rt.Session.IsClient)) { _managePlayers = !_managePlayers; } if (Button(num3 + 708f, num4 + 575f, 128f, 30f, _details ? "HIDE DETAILS" : "DETAILS")) { _details = !_details; } } private void DrawAllyBars() { try { if (CollectAllyBars(out var scale) == null) { Scale(scale); float viewWidth = (float)Screen.width / scale; for (int i = 0; i < _allyBars.Count; i++) { AllyBarPlacement allyBarPlacement = _allyBars[i]; PartyPresentation.AllyBar(_canvas, allyBarPlacement.X, allyBarPlacement.Y, allyBarPlacement.Width, allyBarPlacement.Health, allyBarPlacement.Trail, allyBarPlacement.Opacity); PartyPresentation.AllyName(_canvas, allyBarPlacement.X, allyBarPlacement.Y, viewWidth, allyBarPlacement.Name, allyBarPlacement.NameOpacity); } } } catch (Exception ex) { if (!_allyBarErrorLogged) { _allyBarErrorLogged = true; Log.Warn("[UI] ally health bars unavailable: " + ex.Message); } } } private string CollectAllyBars(out float scale) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) _allyBars.Clear(); scale = 1f; if (!CoopConfig.AllyHealthBars.Value) { return "disabled"; } if (_rt.MenuVisible) { return "co-op menu open"; } if (!_rt.Session.IsActive || !_rt.GameActive || _rt.Loading) { return "not in a session world"; } if (_rt.Ghosts.Count == 0) { return "no allies"; } if (!GameShowsHealthBars()) { return "game hid its health bars"; } Camera val = GameCamera(); if ((Object)(object)val == (Object)null) { return "no camera"; } scale = BaseScale * CoopConfig.HudScale.Value; Vector3 position = ((Component)val).transform.position; float num = CharacterHeight() + 0.2f; float value = CoopConfig.AllyHealthBarDistance.Value; bool value2 = CoopConfig.AllyHealthBarNames.Value; float screenWidth = Screen.width; float screenHeight = Screen.height; IReadOnlyList<RemotePlayer> all = _rt.Ghosts.All; for (int i = 0; i < all.Count; i++) { Remo
BepInEx/plugins/AILimitCoop/LiteNetLib.dll
Decompiled 5 days ago
The result has been truncated due to the large size, download it to view full contents!
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