Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of NetworkPerformanceSystem v1.2.1
plugins/NetworkPerformanceSystem.dll
Decompiled 9 hours 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.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using NetworkPerformanceSystem.Patches; using NetworkPerformanceSystem.Runtime; using Steamworks; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("NetworkPerformanceSystem")] [assembly: AssemblyDescription("Latency-aware networking for Valheim: per-peer send windows and simulation authority placement.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("MidnightsFX")] [assembly: AssemblyProduct("NetworkPerformanceSystem")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")] [assembly: AssemblyFileVersion("1.2.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.1.0")] namespace NetworkPerformanceSystem { internal class Logger { public static LogLevel Level = (LogLevel)16; public static void EnableDebugLogging(object sender, EventArgs e) { CheckEnableDebugLogging(); } public static void CheckEnableDebugLogging() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (ValConfig.EnableDebugMode.Value) { Level = (LogLevel)32; } else { Level = (LogLevel)16; } } public static void SetDebugLogging(bool state) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (state) { Level = (LogLevel)32; } else { Level = (LogLevel)16; } } public static void LogDebug(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)Level >= 32) { NetworkPerformanceSystem.Log.LogInfo((object)("[DEBUG]" + message)); } } public static void LogInfo(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)Level >= 16) { NetworkPerformanceSystem.Log.LogInfo((object)message); } } public static void LogWarning(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)Level >= 4) { NetworkPerformanceSystem.Log.LogWarning((object)message); } } public static void LogError(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)Level >= 2) { NetworkPerformanceSystem.Log.LogError((object)message); } } } internal class ValConfig { public static ConfigFile cfg; public static ConfigEntry<bool> EnableDebugMode; public static ConfigEntry<bool> EnableLatencyCompensation; public static ConfigEntry<float> LatencyCompensationStrength; public static ConfigEntry<float> LatencyCompensationMaxMeters; public static ConfigEntry<bool> EnableDebugOverlay; public static ConfigEntry<bool> EnableSendWindowSizing; public static ConfigEntry<int> SendWindowTargetRateKBps; public static ConfigEntry<float> SendWindowBdpFactor; public static ConfigEntry<int> SendWindowMaxBytes; public static ConfigEntry<bool> EnableSchedulerFix; public static ConfigEntry<float> SendIntervalSeconds; public static ConfigEntry<float> SendSchedulerFrameBudgetMs; public static ConfigEntry<bool> EnableOwnershipArbitration; public static ConfigEntry<bool> OwnershipAllowHostOwner; public static ConfigEntry<float> OwnershipMinHoldSeconds; public static ConfigEntry<int> OwnershipChallengeMarginMs; public static ConfigEntry<int> OwnershipMaxReassignsPerPass; public static ConfigEntry<float> OwnershipLoadPenaltyMs; public static ConfigEntry<int> OwnershipUnmeasuredRttMs; public static ConfigEntry<bool> EnableFastRefPos; public static ConfigEntry<float> RefPosSendHz; public static ConfigEntry<float> RefPosMinMoveDistance; public static ConfigEntry<bool> EnableRoutedRpcFilter; public static ConfigEntry<bool> EnableSyncListCache; public static ConfigEntry<float> SyncListCacheMs; public static ConfigEntry<bool> EnableSteamTransportTuning; public static ConfigEntry<int> SteamSendRateMaxKBps; public static ConfigEntry<int> SteamSendRateMinKBps; public static ConfigEntry<int> SteamNagleMicros; public static ConfigEntry<bool> EnablePlayerLimitOverride; public static ConfigEntry<int> MaxPlayers; public static ConfigEntry<bool> EnableConnectionTimeoutTuning; public static ConfigEntry<int> ConnectTimeoutSeconds; public static ConfigEntry<int> ConnectionTimeoutSeconds; public static ConfigEntry<int> LoadingTimeoutSeconds; public const string cfgFolder = "NetworkPerformanceSystem"; public ValConfig(ConfigFile cf) { cfg = cf; cfg.SaveOnConfigSet = false; CreateConfigValues(cf); Logger.SetDebugLogging(EnableDebugMode.Value); } public static void SaveOnSet(bool enabled) { cfg.SaveOnConfigSet = enabled; cfg.Save(); } private void CreateConfigValues(ConfigFile Config) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Expected O, but got Unknown //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown EnableDebugMode = Config.Bind<bool>("Client config", "EnableDebugMode", false, new ConfigDescription("Enables Debug logging.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdvanced = true } })); EnableDebugMode.SettingChanged += Logger.EnableDebugLogging; Logger.CheckEnableDebugLogging(); EnableLatencyCompensation = Config.Bind<bool>("Client config", "EnableLatencyCompensation", true, new ConfigDescription("Render entities owned by other players at their estimated current position rather than their last-received one. Requires the server to be running this mod; without it this setting has no effect and behaviour is exactly vanilla.", (AcceptableValueBase)null, Array.Empty<object>())); LatencyCompensationStrength = Config.Bind<float>("Client config", "LatencyCompensationStrength", 1f, new ConfigDescription("How much of the measured path latency to correct for. 1.0 corrects fully. Lower values trade accuracy for less overshoot when things stop abruptly. 0 disables the correction while leaving the patch in place.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); LatencyCompensationMaxMeters = Config.Bind<float>("Client config", "LatencyCompensationMaxMeters", 3f, new ConfigDescription("Hard cap on the total extrapolated displacement (the game's own gap extrapolation plus this correction). The correction only ever uses whatever headroom remains under the cap, so fast objects such as projectiles stay below the 5m threshold at which the game gives up smoothing and teleports them.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 4.5f), Array.Empty<object>())); EnableDebugOverlay = Config.Bind<bool>("Client config", "EnableDebugOverlay", false, new ConfigDescription("Show the per-entity latency compensation overlay (owner, estimated staleness, applied displacement).", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdvanced = true } })); EnableSendWindowSizing = BindServerConfig("Send Window", "Enable BDP Window Sizing", value: true, "Size each peer's in-flight ZDO window from their measured round-trip time instead of vanilla's fixed 10240 bytes. Low-latency peers are unaffected; distant peers stop being throttled by their distance."); SendWindowTargetRateKBps = BindServerConfig("Send Window", "Target Rate KBps", 150, "Per-peer ZDO throughput to size the window for, in kilobytes/sec. 150 matches the rate Valheim pins its Steam sockets to, so the default asks for exactly what the transport already allows.", advanced: false, 32, 1024); SendWindowBdpFactor = BindServerConfig("Send Window", "BDP Factor", 1.25f, "Multiplier on the bandwidth-delay product. 1.0 allows roughly one round-trip of buffering. Raising this adds throughput headroom at the cost of standing queue delay (bufferbloat).", advanced: false, 1f, 3f); SendWindowMaxBytes = BindServerConfig("Send Window", "Max Window Bytes", 65536, "Upper bound on the computed window. Prevents a peer with a pathological ping reading from being handed an unbounded buffer. The lower bound is always vanilla's 10240 and is not configurable.", advanced: true, 10240, 262144); EnableSchedulerFix = BindServerConfig("Send Scheduler", "Enable Scheduler Fix", value: true, "Send to every peer each tick instead of one peer per rendered frame. Without this the effective per-peer send rate degrades linearly with player count."); SendIntervalSeconds = BindServerConfig("Send Scheduler", "Send Interval Seconds", 0.05f, "Seconds between ZDO send rounds. Vanilla is 0.05 (20Hz).", advanced: true, 0.02f, 0.2f); SendSchedulerFrameBudgetMs = BindServerConfig("Send Scheduler", "Frame Budget Ms", 4f, "Maximum milliseconds per frame the host spends sending ZDOs to peers. Peers are serviced in round-robin order until the budget runs out and the remainder is owed to the next frame, so nobody is starved. On a busy server this is what keeps the frame time bounded: the effective per-peer send rate becomes min(1/interval, budget/cost) - raise it to trade server frame time for send rate, lower it on a CPU-constrained host. nps_stats shows the effective rate and how often the budget is hit.", advanced: true, 0.5f, 16f); EnableOwnershipArbitration = BindServerConfig("Ownership", "Enable Latency-Aware Ownership", value: true, "Assign ZDO ownership to minimise how stale the object looks to the players who can actually see it, instead of vanilla's first-peer-wins ordering."); OwnershipAllowHostOwner = BindServerConfig("Ownership", "Allow Host As Owner", value: true, "Let the host compete for ownership of contested ZDOs in the zones it has loaded. The host is zero hops from everyone, so host-owned is the lowest possible staleness for every viewer, and whenever two or more players share a zone the host has loaded it will win those objects and keep them. A listen host has loaded the zones around its own player; a dedicated server has loaded only the zones around the world origin, so in practice this means a dedicated server owns and simulates the contested objects at the spawn hub whenever players gather there - intended, and worth knowing when budgeting server CPU. Disable to always place on the lowest-latency player present instead."); OwnershipMinHoldSeconds = BindServerConfig("Ownership", "Min Hold Seconds", 5f, "Minimum time an owner keeps a ZDO before it can be challenged. Hysteresis against ownership thrash. Applies only when moving a ZDO away from an owner that is still present - a ZDO whose owner has left the area or the session is re-owned immediately, at any setting.", advanced: false, 0f, 60f); OwnershipChallengeMarginMs = BindServerConfig("Ownership", "Challenge Margin Ms", 25, "A challenger must improve estimated staleness by at least this many milliseconds to take ownership. Prevents ping jitter from ping-ponging ownership between similar peers. Applies only to challenges against a present owner, and also bounds how much the Load Penalty below may shift a decision.", advanced: false, 0, 250); OwnershipMaxReassignsPerPass = BindServerConfig("Ownership", "Max Reassigns Per Pass", 8, "Minimum cap on latency-driven ownership transfers per arbitration pass. Each transfer costs a ZDO resend, so this bounds the burst when a group arrives in a new area. The effective cap is the larger of this value and the number of connected players, so a full server converges at the same per-player rate as a small group rather than linearly slower. Restoring an owner to a ZDO that has none is never deferred by this: an unowned creature does not move and cannot be damaged.", advanced: true, 1, 128); OwnershipLoadPenaltyMs = BindServerConfig("Ownership", "Load Penalty Ms", 0.02f, "Cost added per simulated object (creatures, ships - not walls or trees) a candidate already owns nearby, in milliseconds. Spreads simulation and upload load across peers instead of concentrating every contested object on the lowest-ping player. The total handicap is capped at half of Challenge Margin Ms, so load can shade a close decision but can never on its own amount to the staleness difference that justifies a transfer. 0 disables load spreading and places purely by staleness.", advanced: true, 0f, 0.5f); OwnershipUnmeasuredRttMs = BindServerConfig("Ownership", "Unmeasured Peer RTT Ms", 150, "Round-trip time assumed for a peer the host has no measurement for - crossplay/PlayFab connections never report one, and every Steam peer is unmeasured for its first seconds. Such a peer still wins objects only it can see, but loses contested ones to any measured peer with a lower ping. Treating unmeasured as 0ms instead would hand them everything in range.", advanced: true, 0, 1000); EnableFastRefPos = BindServerConfig("Reference Position", "Enable Fast Reference Position", value: true, "Send a lightweight 12-byte position update on a fast timer so the server arbitrates ownership and interest against live positions rather than up-to-2-second-old ones. Vanilla's 2 second path is left intact as a fallback."); RefPosSendHz = BindServerConfig("Reference Position", "Send Hz", 5f, "How many times per second a moving client reports its position. At 12 bytes per update this costs about 60 bytes/sec.", advanced: false, 1f, 20f); RefPosMinMoveDistance = BindServerConfig("Reference Position", "Min Move Distance", 0.5f, "Skip the update when the player has moved less than this many metres since the last one. A standing player sends nothing.", advanced: false, 0f, 5f); EnableRoutedRpcFilter = BindServerConfig("Routed RPC", "Enable Relay Filtering", value: true, "Relay broadcast RPCs (animation triggers, footsteps, damage numbers, object-destroyed notices, building damage and the like) only to the players that can actually use them, instead of to everyone on the server. A receiving client discards these unless it has the object loaded, so nothing visible changes; on a busy server this removes most of the host's relay traffic and stops it from crowding out ZDO updates. Global messages (chat, pings, events, sleep, server messages) are never filtered."); EnableSyncListCache = BindServerConfig("Sync List Cache", "Enable Sector Scan Cache", value: true, "Reuse each peer's sector scan across the send sweep instead of rebuilding it on every send. The recipient filter and the priority sort still run every single send, so exactly the same ZDOs go out in the same order - only the scan that produces the candidate list is shared. Invalidated immediately whenever the peer changes zone or any object is destroyed."); SyncListCacheMs = BindServerConfig("Sync List Cache", "Cache Ms", 100f, "How long a peer's sector scan may be reused, in milliseconds. The cost is that an object newly arriving in a peer's area can wait this long before it is first considered - bounded, and small next to the send interval. Destroyed objects are never affected: any destruction invalidates the scan immediately, at any setting. 0 disables the cache and rebuilds the scan every send, as vanilla.", advanced: false, 0f, 500f); EnableSteamTransportTuning = BindServerConfig("Steam Transport", "Enable Transport Tuning", value: true, "Let this mod write Steam's global networking config (send-rate bounds and Nagle). Every value below ships at its vanilla setting, so enabling this on its own changes nothing - it only makes the settings reachable and logs a before/after readback of what the transport is actually doing. Requires the Steam backend; on crossplay-only processes it stands down quietly."); SteamSendRateMaxKBps = BindServerConfig("Steam Transport", "Send Rate Max KBps", 0, "Ceiling on Steam's per-connection bandwidth estimate, in kilobytes/sec. 0 leaves vanilla's 150. This is a ceiling, not a target: raising it lets the estimator climb during a burst, it does not push traffic. Until it is raised, Send Window sizing above 150 KBps cannot do anything - the transport meters at 150 regardless and the surplus becomes standing queue. Raise this and Target Rate KBps together, and provision the uplink for the result: 10 players at 500 KBps is 40 Mbit/s of upload worst case.", advanced: false, 0, 4096); SteamSendRateMinKBps = BindServerConfig("Steam Transport", "Send Rate Min KBps", 0, "Floor under Steam's bandwidth estimate, in kilobytes/sec. 0 leaves vanilla's 150. Vanilla sets this equal to the ceiling, which is why the estimator never moves; LOWERING it is the useful direction, because it lets congestion control actually back off for a peer on a weak downlink instead of overdriving the link into loss. This setting cannot be raised above vanilla - that direction converts congestion into buffering and is never what you want.", advanced: true); SteamNagleMicros = BindServerConfig("Steam Transport", "Nagle Micros", 0, "Microseconds Steam may hold a small reliable message back to coalesce it with the next one. Vanilla and Steam both default to 5000 (5ms), which is up to 5ms added in each direction on every update for a saving that mattered on a modem. 0 sends immediately. This mod already batches at the ZDO layer, so there is very little left for Nagle to coalesce - which is why 0 is the default here rather than vanilla's 5000.", advanced: false, 0, 100000); EnablePlayerLimitOverride = BindServerConfig("Player Limit", "Enable Player Limit Override", value: true, "Let this mod decide how many players the server accepts, instead of the game's hard-coded 10. Max Players below ships at 10, so enabling this on its own changes nothing - it only makes the number reachable. Applies on the host; a client has no say in it."); MaxPlayers = BindServerConfig("Player Limit", "Max Players", 60, "How many players the server accepts. 10 is vanilla. This counts the same players the game counts: on a player-hosted game the host is one of them, on a dedicated server it is not. The number is enforced the moment it changes, but the limit shown in the server browser - and the crossplay capacity, which is a real ceiling rather than a label - are set when the server registers, so lower it live if you must and restart to raise it cleanly. Nothing about raising it makes the traffic free: every player added costs the host upload and CPU against every other player, so treat the rest of this config (Send Scheduler's frame budget, Steam Transport's rate ceiling) as the things that decide whether a larger number is actually playable. Crossplay servers cannot exceed 128 whatever is set here - PlayFab's lobbies do not go higher.", advanced: false, 1, 255); EnableConnectionTimeoutTuning = BindServerConfig("Connection Timeout", "Enable Timeout Tuning", value: true, "Let this mod set how long a connection may go quiet before either end hangs up, instead of the game's fixed 30 seconds. Every value below ships at its vanilla setting, so enabling this on its own changes nothing - it only makes the settings reachable and logs a before/after readback of what is actually in force. Turning it back off restores vanilla's values immediately rather than leaving the last-written ones in place."); ConnectTimeoutSeconds = BindServerConfig("Connection Timeout", "Connect Timeout Seconds", 10, "How long a connection attempt may take before Steam abandons it, in seconds. 10 is Steam's own default, which the game never changes. This covers only the handshake, before the connection exists - NAT traversal between two awkward home routers is the usual reason it is not enough, and it is the one timeout the server cannot decide for a client, because nothing has been synced to that client yet: whoever is failing to connect has to raise it in their own config.", advanced: false, 5, 600); ConnectionTimeoutSeconds = BindServerConfig("Connection Timeout", "Connection Timeout Seconds", 30, "How long an established connection may go without a packet before it is dropped, in seconds. 30 is vanilla. This is the setting for players who get disconnected mid-join or during a hitch on a weak link - it is written to BOTH layers the game times out at (ZRpc's ping timeout and Steam's TimeoutConnected), because the effective timeout is the lower of the two and raising one alone achieves nothing. The cost falls on the host: a player who is genuinely gone now holds their slot, and keeps ownership of everything they were simulating, for this long instead of 30 seconds - and objects an absent owner holds do not move. Size it to the worst connection you actually want to keep.", advanced: false, 10, 600); LoadingTimeoutSeconds = BindServerConfig("Connection Timeout", "Loading Timeout Seconds", 90, "The longer allowance the game already gives itself while a crossplay peer is joining and the world is being transferred, in seconds. 90 is vanilla. A slow client can spend minutes here on a large world, and this is the timeout that ends the join when it does. Never applied below 'Connection Timeout Seconds' - a loading peer is not given less slack than an idle one, whatever this is set to.", advanced: true, 30, 900); EnableSteamTransportTuning.SettingChanged += OnSteamTransportSettingChanged; SteamSendRateMaxKBps.SettingChanged += OnSteamTransportSettingChanged; SteamSendRateMinKBps.SettingChanged += OnSteamTransportSettingChanged; SteamNagleMicros.SettingChanged += OnSteamTransportSettingChanged; EnableSendWindowSizing.SettingChanged += OnSteamTransportSettingChanged; SendWindowTargetRateKBps.SettingChanged += OnSteamTransportSettingChanged; EnableConnectionTimeoutTuning.SettingChanged += OnConnectionTimeoutSettingChanged; ConnectTimeoutSeconds.SettingChanged += OnConnectionTimeoutSettingChanged; ConnectionTimeoutSeconds.SettingChanged += OnConnectionTimeoutSettingChanged; LoadingTimeoutSeconds.SettingChanged += OnConnectionTimeoutSettingChanged; } private static void OnSteamTransportSettingChanged(object sender, EventArgs e) { SteamTransport.OnConfigChanged(); } private static void OnConnectionTimeoutSettingChanged(object sender, EventArgs e) { ConnectionTimeout.OnConfigChanged(); } public static ConfigEntry<float[]> BindServerConfig(string category, string key, float[] value, string description, bool advanced = false, float valMin = 0f, float valMax = 150f) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown return cfg.Bind<float[]>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(valMin, valMax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<bool> BindServerConfig(string category, string key, bool value, string description, AcceptableValueBase acceptableValues = null, bool advanced = false) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown return cfg.Bind<bool>(category, key, value, new ConfigDescription(description, acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<int> BindServerConfig(string category, string key, int value, string description, bool advanced = false, int valMin = 0, int valMax = 150) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown return cfg.Bind<int>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(valMin, valMax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<float> BindServerConfig(string category, string key, float value, string description, bool advanced = false, float valMin = 0f, float valMax = 150f) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown return cfg.Bind<float>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(valMin, valMax), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } public static ConfigEntry<string> BindServerConfig(string category, string key, string value, string description, AcceptableValueList<string> acceptableValues = null, bool advanced = false) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown return cfg.Bind<string>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = advanced } })); } } [BepInPlugin("MidnightsFX.NetworkPerformanceSystem", "NetworkPerformanceSystem", "1.2.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInIncompatibility("com.Fire.FiresGhettoNetworkMod")] [BepInIncompatibility("VitByr.VBNetTweaks")] [BepInIncompatibility("sighsorry.SkadiNet")] [BepInIncompatibility("CW_Jesse.BetterNetworking")] [BepInIncompatibility("org.bepinex.plugins.network")] [BepInIncompatibility("Searica.Valheim.NetworkTweaks")] [BepInIncompatibility("dzk.warheimnetwork")] [BepInIncompatibility("com.maxsch.valheim.TimeoutLimit")] internal class NetworkPerformanceSystem : BaseUnityPlugin { public const string PluginGUID = "MidnightsFX.NetworkPerformanceSystem"; public const string PluginName = "NetworkPerformanceSystem"; public const string PluginVersion = "1.2.1"; internal static ManualLogSource Log; internal static Harmony HarmonyInstance; internal ValConfig cfg; public void Awake() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; cfg = new ValConfig(((BaseUnityPlugin)this).Config); HarmonyInstance = new Harmony("MidnightsFX.NetworkPerformanceSystem"); HarmonyInstance.PatchAll(typeof(NetworkPerformanceSystem).Assembly); PlayerLimitPatches.ApplyPlayFabCapacityPatch(HarmonyInstance); PatchGuard.VerifyAfterPatching(); ValConfig.SaveOnSet(enabled: true); } public void OnGUI() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (ValConfig.EnableDebugOverlay != null && ValConfig.EnableDebugOverlay.Value && NpsEnv.NetReady() && !NpsEnv.IsDedicated()) { GUI.Label(new Rect(10f, 10f, 1400f, 24f), NetworkStats.BuildOverlay()); } } public void OnDestroy() { Harmony harmonyInstance = HarmonyInstance; if (harmonyInstance != null) { harmonyInstance.UnpatchSelf(); } } } } namespace NetworkPerformanceSystem.Runtime { internal static class ConnectionTimeout { internal const float VanillaRpcTimeoutSeconds = 30f; internal const float VanillaRpcLongTimeoutSeconds = 90f; internal const int VanillaSteamConnectedMillis = 30000; internal const int VanillaSteamInitialMillis = 10000; private const int MillisPerSecond = 1000; private static bool _longMode; private static bool _rpcSeamSeen; private static bool _steamUp; private static float _lastLoggedRpcSeconds = -1f; internal static float EffectiveRpcTimeoutSeconds { get; private set; } = 30f; internal static string LastSteamReadback { get; private set; } internal static bool Active { get { if (PatchGuard.IsActive(Mechanism.ConnectionTimeout) && ValConfig.EnableConnectionTimeoutTuning != null && ValConfig.ConnectionTimeoutSeconds != null && ValConfig.ConnectTimeoutSeconds != null && ValConfig.LoadingTimeoutSeconds != null) { return ValConfig.EnableConnectionTimeoutTuning.Value; } return false; } } internal static float EffectiveLoadingTimeoutSeconds { get { if (!Active) { return 90f; } return Math.Max(ValConfig.LoadingTimeoutSeconds.Value, ValConfig.ConnectionTimeoutSeconds.Value); } } internal static void OnVanillaRpcTimeoutSet(bool longMode) { _longMode = longMode; _rpcSeamSeen = true; ApplyRpc(longMode ? "SetLongTimeout(true)" : "SetLongTimeout(false)"); } internal static void OnGlobalCallbacksRegistered() { _steamUp = true; ApplySteam("RegisterGlobalCallbacks"); } internal static void OnConfigChanged() { if (_rpcSeamSeen) { ApplyRpc("config changed"); } if (_steamUp) { ApplySteam("config changed"); } } internal static void Reset() { _longMode = false; _lastLoggedRpcSeconds = -1f; } private static void ApplyRpc(string reason) { float timeout = ((!Active) ? (_longMode ? 90f : 30f) : (_longMode ? EffectiveLoadingTimeoutSeconds : ((float)ValConfig.ConnectionTimeoutSeconds.Value))); float timeout2 = ZRpc.m_timeout; ZRpc.m_timeout = timeout; EffectiveRpcTimeoutSeconds = ZRpc.m_timeout; if (EffectiveRpcTimeoutSeconds != _lastLoggedRpcSeconds) { _lastLoggedRpcSeconds = EffectiveRpcTimeoutSeconds; Logger.LogInfo($"Connection timeout ({reason}): ZRpc ping timeout {timeout2}s -> {EffectiveRpcTimeoutSeconds}s" + (_longMode ? " [loading phase]" : "")); } } private static void ApplySteam(string reason) { if (SteamNetConfig.Available) { int value = (Active ? (ValConfig.ConnectTimeoutSeconds.Value * 1000) : 10000); int value2 = (Active ? (ValConfig.ConnectionTimeoutSeconds.Value * 1000) : 30000); int num = ReadOr((ESteamNetworkingConfigValue)24, 10000); int num2 = ReadOr((ESteamNetworkingConfigValue)25, 30000); SteamNetConfig.TryWrite((ESteamNetworkingConfigValue)24, value); SteamNetConfig.TryWrite((ESteamNetworkingConfigValue)25, value2); int millis = ReadOr((ESteamNetworkingConfigValue)24, num); int millis2 = ReadOr((ESteamNetworkingConfigValue)25, num2); string text = "TimeoutInitial " + Sec(num) + " -> " + Sec(millis) + ", TimeoutConnected " + Sec(num2) + " -> " + Sec(millis2); if (!(text == LastSteamReadback)) { LastSteamReadback = text; Logger.LogInfo("Connection timeout (" + SteamNetConfig.InterfaceName + ", " + reason + "): " + text); } } } private static int ReadOr(ESteamNetworkingConfigValue key, int fallback) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (!SteamNetConfig.TryRead(key, out var value)) { return fallback; } return value; } private static string Sec(int millis) { return $"{(float)millis / 1000f}s"; } } internal static class LatencyRegistry { internal sealed class PeerLatency { internal float EwmaMs; internal float JitterMs; internal int LastMs; internal float LastSampleRealtime; internal bool HasSample; } private const float SampleAlpha = 0.25f; private const float JitterDivisor = 16f; private const int MinPlausiblePingMs = 1; private const int MaxPlausiblePingMs = 2000; private static readonly Dictionary<long, PeerLatency> Measured = new Dictionary<long, PeerLatency>(); private static readonly Dictionary<long, int> Published = new Dictionary<long, int>(); private static bool _hasPublishedTable; private static float _publishedAtRealtime; private static bool _warnedBadTable; private const float PublishedTableTtlSeconds = 10f; private const int EntryBytes = 10; private const int MaxTableEntries = 4096; internal static IEnumerable<KeyValuePair<long, PeerLatency>> AllMeasured => Measured; internal static bool HasPublishedTable => _hasPublishedTable; internal static bool HasFreshTable { get { if (_hasPublishedTable) { return Time.realtimeSinceStartup - _publishedAtRealtime < 10f; } return false; } } internal static float PublishedTableAgeSeconds { get { if (!_hasPublishedTable) { return 0f; } return Time.realtimeSinceStartup - _publishedAtRealtime; } } internal static int PublishedEntryCount => Published.Count; internal static void Sample(long peerUid, int pingMs) { if (peerUid != 0L && pingMs >= 1 && pingMs <= 2000) { if (!Measured.TryGetValue(peerUid, out var value)) { value = new PeerLatency(); Measured[peerUid] = value; } if (!value.HasSample) { value.EwmaMs = pingMs; value.JitterMs = 0f; value.HasSample = true; } else { value.JitterMs += ((float)Mathf.Abs(pingMs - value.LastMs) - value.JitterMs) / 16f; value.EwmaMs += ((float)pingMs - value.EwmaMs) * 0.25f; } value.LastMs = pingMs; value.LastSampleRealtime = Time.realtimeSinceStartup; } } internal static float MeasuredRttMs(long peerUid) { if (!Measured.TryGetValue(peerUid, out var value) || !value.HasSample) { return 0f; } return value.EwmaMs; } internal static float MeasuredJitterMs(long peerUid) { if (!Measured.TryGetValue(peerUid, out var value) || !value.HasSample) { return 0f; } return value.JitterMs; } internal static bool HasMeasurement(long peerUid) { if (Measured.TryGetValue(peerUid, out var value)) { return value.HasSample; } return false; } internal static float RttMs(long peerUid) { if (NpsEnv.IsHost()) { return MeasuredRttMs(peerUid); } if (!Published.TryGetValue(peerUid, out var value)) { return 0f; } return value; } internal static float PathStalenessSeconds(long ownerUid) { if (ownerUid == 0L) { return 0f; } long num = NpsEnv.LocalSessionId(); if (ownerUid == num) { return 0f; } if (NpsEnv.IsHost()) { if (!HasMeasurement(ownerUid)) { return 0f; } return MeasuredRttMs(ownerUid) * 0.5f / 1000f; } if (!HasFreshTable) { return 0f; } if (!Published.TryGetValue(ownerUid, out var value) || !Published.TryGetValue(num, out var value2)) { return 0f; } return (float)(value + value2) * 0.5f / 1000f; } internal static ZPackage BuildTablePackage(ZNetPeer recipient) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0019: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) ZPackage val = new ZPackage(); List<ZNetPeer> peers = ZNet.instance.GetPeers(); int radius = int.MaxValue; Vector2s recipientZone = default(Vector2s); if ((Object)(object)ZoneSystem.instance != (Object)null && recipient != null) { radius = 2 * ZoneCompat.NearFor(recipient) + 1; recipientZone = ZoneSystem.GetZone(recipient.GetRefPos()); } int num = 1; for (int i = 0; i < peers.Count; i++) { if (Qualifies(peers[i], recipientZone, radius)) { num++; } } val.Write(num); val.Write(NpsEnv.LocalSessionId()); val.Write((ushort)0); for (int j = 0; j < peers.Count; j++) { ZNetPeer val2 = peers[j]; if (Qualifies(val2, recipientZone, radius)) { val.Write(val2.m_uid); val.Write((ushort)Mathf.Clamp(Mathf.RoundToInt(MeasuredRttMs(val2.m_uid)), 0, 65535)); } } return val; } private static bool Qualifies(ZNetPeer peer, Vector2s recipientZone, int radius) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) long uid = peer.m_uid; if (uid == 0L || !HasMeasurement(uid)) { return false; } if (radius == int.MaxValue) { return true; } return ZoneCompat.InActiveArea(ZoneSystem.GetZone(peer.GetRefPos()), recipientZone, radius); } internal static void ApplyTablePackage(ZPackage pkg) { if (pkg == null) { return; } int num = pkg.ReadInt(); if (num < 0 || num > 4096 || pkg.Size() - pkg.GetPos() < num * 10) { if (!_warnedBadTable) { _warnedBadTable = true; Logger.LogWarning($"Ignoring malformed latency table ({num} entries, {pkg.Size() - pkg.GetPos()} bytes remaining). Further occurrences this session are not logged."); } return; } Published.Clear(); for (int i = 0; i < num; i++) { long key = pkg.ReadLong(); int value = pkg.ReadUShort(); Published[key] = value; } _hasPublishedTable = true; _publishedAtRealtime = Time.realtimeSinceStartup; } internal static void ForgetPeer(long peerUid) { Measured.Remove(peerUid); } internal static void Reset() { Measured.Clear(); Published.Clear(); _hasPublishedTable = false; _publishedAtRealtime = 0f; _warnedBadTable = false; } } internal static class NetworkStats { internal sealed class PeerStats { internal string Name; internal long Uid; internal int SendAttempts; internal int SendsSkippedByBackpressure; internal int LastQueueBytes; internal int LastWindowBytes; internal int StatusSamples; internal long PendingByteSum; internal int PendingBytesPeak; internal long InFlightByteSum; internal int InFlightBytesPeak; internal int LastSendRateBytesPerSec; internal int SamplesWithPending; internal float MeanPendingBytes { get { if (StatusSamples <= 0) { return 0f; } return (float)PendingByteSum / (float)StatusSamples; } } internal float MeanInFlightBytes { get { if (StatusSamples <= 0) { return 0f; } return (float)InFlightByteSum / (float)StatusSamples; } } internal float PendingSampleShare { get { if (StatusSamples <= 0) { return 0f; } return (float)SamplesWithPending / (float)StatusSamples; } } } private static readonly Dictionary<long, PeerStats> Stats = new Dictionary<long, PeerStats>(); private const int MinPackageBytes = 2048; private static float _collectingSince; internal static bool Collecting { get; private set; } internal static float CollectingSeconds { get { if (!Collecting) { return 0f; } return Time.realtimeSinceStartup - _collectingSince; } } internal static void SetCollecting(bool enabled) { Collecting = enabled; if (enabled) { Stats.Clear(); _collectingSince = Time.realtimeSinceStartup; } } internal static void RecordSendAttempt(ZDOPeer peer, bool flush) { if (!Collecting) { return; } ZNetPeer val = peer?.m_peer; if (val?.m_socket == null || val.m_uid == 0L) { return; } PeerStats orCreate = GetOrCreate(val.m_uid, val.m_playerName); int sendQueueSize = val.m_socket.GetSendQueueSize(); int num = SendWindow.For(peer); orCreate.SendAttempts++; orCreate.LastQueueBytes = sendQueueSize; orCreate.LastWindowBytes = num; bool num2 = !flush && sendQueueSize > num; bool flag = num - sendQueueSize < 2048; if (num2 || flag) { orCreate.SendsSkippedByBackpressure++; } if (RttProbe.TryGetLinkStatus(val.m_socket, out var status)) { orCreate.StatusSamples++; orCreate.PendingByteSum += status.PendingBytes; orCreate.InFlightByteSum += status.InFlightBytes; orCreate.LastSendRateBytesPerSec = status.SendRateBytesPerSec; if (status.PendingBytes > orCreate.PendingBytesPeak) { orCreate.PendingBytesPeak = status.PendingBytes; } if (status.InFlightBytes > orCreate.InFlightBytesPeak) { orCreate.InFlightBytesPeak = status.InFlightBytes; } if (status.PendingBytes > 0) { orCreate.SamplesWithPending++; } } } private static PeerStats GetOrCreate(long uid, string name) { if (!Stats.TryGetValue(uid, out var value)) { value = new PeerStats { Uid = uid, Name = name }; Stats[uid] = value; } if (!string.IsNullOrEmpty(name)) { value.Name = name; } return value; } internal static string BuildReport() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("=== NetworkPerformanceSystem ==="); stringBuilder.AppendLine(Describe()); stringBuilder.AppendLine(); if ((Object)(object)ZNet.instance == (Object)null) { stringBuilder.AppendLine("Not connected."); return stringBuilder.ToString(); } stringBuilder.AppendLine((!NpsEnv.IsHost()) ? "Role: client" : (NpsEnv.IsDedicated() ? "Role: dedicated host" : "Role: listen host")); AppendPlayerLimit(stringBuilder); AppendPeerTable(stringBuilder); AppendLinkPressure(stringBuilder); AppendTransport(stringBuilder); AppendTimeouts(stringBuilder); AppendScheduler(stringBuilder); AppendSyncListCache(stringBuilder); AppendRoutedRpc(stringBuilder); AppendOwnership(stringBuilder); AppendExtrapolation(stringBuilder); if (!Collecting) { stringBuilder.AppendLine(); stringBuilder.AppendLine("Send-window instrumentation is off. Run 'nps_stats collect' to start sampling,"); stringBuilder.AppendLine("play for a while, then run 'nps_stats' again."); } return stringBuilder.ToString(); } private static string Describe() { List<string> list = new List<string>(); foreach (Mechanism value in Enum.GetValues(typeof(Mechanism))) { string text = PatchGuard.GetDisableReason(value); if (text == null && !IsEnabledInConfig(value)) { text = "disabled in config"; } list.Add((text == null) ? $"{value}: on" : $"{value}: OFF ({text})"); } return string.Join("\n", list.ToArray()); } private static bool IsEnabledInConfig(Mechanism mechanism) { return mechanism switch { Mechanism.RttSampling => true, Mechanism.SendWindow => ValConfig.EnableSendWindowSizing.Value, Mechanism.SendScheduler => ValConfig.EnableSchedulerFix.Value, Mechanism.Ownership => ValConfig.EnableOwnershipArbitration.Value, Mechanism.RefPos => ValConfig.EnableFastRefPos.Value, Mechanism.Extrapolation => ValConfig.EnableLatencyCompensation.Value, Mechanism.RoutedRpcFilter => ValConfig.EnableRoutedRpcFilter.Value, Mechanism.SteamTransport => ValConfig.EnableSteamTransportTuning.Value, Mechanism.SyncListCache => ValConfig.EnableSyncListCache.Value, Mechanism.ConnectionTimeout => ValConfig.EnableConnectionTimeoutTuning.Value, _ => true, }; } private static void AppendPeerTable(StringBuilder sb) { List<ZNetPeer> peers = ZNet.instance.GetPeers(); sb.AppendLine(); sb.AppendLine($"Peers ({peers.Count}):"); if (peers.Count == 0) { sb.AppendLine(" (none)"); return; } sb.AppendLine(" name rtt jitter window queue skipped"); for (int i = 0; i < peers.Count; i++) { ZNetPeer val = peers[i]; long uid = val.m_uid; string value = (string.IsNullOrEmpty(val.m_playerName) ? "(connecting)" : val.m_playerName); string value2 = (LatencyRegistry.HasMeasurement(uid) ? $"{LatencyRegistry.MeasuredRttMs(uid):F0}ms" : "-"); string value3 = (LatencyRegistry.HasMeasurement(uid) ? $"{LatencyRegistry.MeasuredJitterMs(uid):F0}ms" : "-"); int bytes; string value4 = (SendWindow.TryGetLastWindow(uid, out bytes) ? $"{(float)bytes / 1024f:F1}KB" : "vanilla"); string value5 = "-"; string text = "-"; if (Stats.TryGetValue(uid, out var value6) && value6.SendAttempts > 0) { value5 = $"{(float)value6.LastQueueBytes / 1024f:F1}KB"; float num = 100f * (float)value6.SendsSkippedByBackpressure / (float)value6.SendAttempts; text = $"{num:F1}% ({value6.SendsSkippedByBackpressure}/{value6.SendAttempts})"; } sb.AppendLine(" " + Pad(value, 20) + " " + Pad(value2, 7) + " " + Pad(value3, 7) + " " + Pad(value4, 9) + " " + Pad(value5, 7) + " " + text); } if (Collecting) { sb.AppendLine($" (sampling for {CollectingSeconds:F0}s)"); } } private static void AppendLinkPressure(StringBuilder sb) { sb.AppendLine(); sb.AppendLine("Link pressure (transport view):"); if (!Collecting) { sb.AppendLine(" (not sampling - run 'nps_stats collect')"); return; } List<ZNetPeer> peers = ZNet.instance.GetPeers(); bool flag = false; sb.AppendLine(" name in-flight pending pending% steam est fill verdict"); for (int i = 0; i < peers.Count; i++) { long uid = peers[i].m_uid; if (Stats.TryGetValue(uid, out var value) && value.StatusSamples != 0) { flag = true; string value2 = (string.IsNullOrEmpty(peers[i].m_playerName) ? "(connecting)" : peers[i].m_playerName); int num = ((value.LastWindowBytes > 0) ? value.LastWindowBytes : 10240); float num2 = value.MeanInFlightBytes / (float)num; float pendingSampleShare = value.PendingSampleShare; sb.AppendLine(" " + Pad(value2, 20) + " " + Pad($"{value.MeanInFlightBytes / 1024f:F1}KB", 10) + " " + Pad($"{value.MeanPendingBytes / 1024f:F1}KB", 9) + " " + Pad($"{pendingSampleShare * 100f:F0}%", 9) + " " + Pad($"{(float)value.LastSendRateBytesPerSec / 1024f:F0}KB/s", 11) + " " + Pad($"{num2 * 100f:F0}%", 6) + " " + Verdict(pendingSampleShare, num2)); } } if (!flag) { sb.AppendLine(" (no transport samples - Steam sockets only; crossplay peers report nothing here)"); return; } sb.AppendLine(" in-flight = on the wire, unacked (the window working). pending = Steam holding it back (congestion)."); sb.AppendLine(" fill = in-flight as a share of the sized window. steam est = Steam's own rate estimate for the link."); } private static string Verdict(float pendingShare, float fill) { if (pendingShare > 0.25f) { return "CONGESTED - target rate above what this link carries"; } if (pendingShare > 0.05f) { return "some queueing"; } if (fill > 0.8f) { return "window-bound (working)"; } if (fill > 0.25f) { return "healthy"; } return "idle - nothing to send"; } private static void AppendTransport(StringBuilder sb) { sb.AppendLine(); sb.AppendLine("Steam transport:"); if (SteamTransport.LastReadback == null) { sb.AppendLine(" not applied (disabled, or no Steam networking interface in this process)"); return; } sb.AppendLine(" " + SteamTransport.LastReadback); int num = SteamTransport.EffectiveSendRateMaxBytesPerSec / 1024; int value = ValConfig.SendWindowTargetRateKBps.Value; sb.AppendLine($" window target {value} KB/s against a transport ceiling of {num} KB/s"); if (value > num) { sb.AppendLine(" WARNING: sizing windows for throughput the transport will not pass. The surplus becomes"); sb.AppendLine(" queueing delay. Raise 'Steam Transport / Send Rate Max KBps' or lower the target."); } } private static void AppendTimeouts(StringBuilder sb) { sb.AppendLine(); sb.AppendLine("Connection timeouts:"); sb.AppendLine($" drop after {ConnectionTimeout.EffectiveRpcTimeoutSeconds}s without a packet (ZRpc ping)"); sb.AppendLine((ConnectionTimeout.LastSteamReadback == null) ? " steam layer not applied (no Steam networking interface in this process)" : (" steam layer " + ConnectionTimeout.LastSteamReadback)); sb.AppendLine($" loading phase {ConnectionTimeout.EffectiveLoadingTimeoutSeconds}s (crossplay joins and world transfer)"); if (!ConnectionTimeout.Active) { sb.AppendLine(" vanilla (timeout tuning is off)"); } else if (NpsEnv.IsHost() && (float)ValConfig.ConnectionTimeoutSeconds.Value > 30f) { sb.AppendLine(" note: a peer that is genuinely gone holds its slot, and ownership of everything it was"); sb.AppendLine(" simulating, for that long. Objects an absent owner holds do not move."); } } private static void AppendPlayerLimit(StringBuilder sb) { if (!NpsEnv.IsHost()) { return; } sb.AppendLine(); sb.AppendLine("Player limit:"); if (!PlayerLimit.Active) { sb.AppendLine($" vanilla ({10} players)"); string disableReason = PatchGuard.GetDisableReason(Mechanism.PlayerLimit); if (disableReason != null) { sb.AppendLine(" stood down: " + disableReason); } } else { sb.AppendLine($" accepting {ZNet.instance.GetNrOfPlayers()} of {PlayerLimit.Configured}"); if (PlayerLimit.CrossplayCapacityPinned && PlayerLimit.Configured > 10) { sb.AppendLine($" WARNING: the crossplay lobby is still capped at {10}. Steam players can join"); sb.AppendLine(" past that; crossplay players are told the server is full. See the warning at startup."); } } } private static void AppendSyncListCache(StringBuilder sb) { if (!NpsEnv.IsHost()) { return; } sb.AppendLine(); sb.AppendLine("Sector scan cache (since start):"); if (!PatchGuard.IsActive(Mechanism.SyncListCache) || !ValConfig.EnableSyncListCache.Value) { sb.AppendLine(" vanilla (full sector scan rebuilt on every send, per peer)"); return; } long num = SyncListCache.Hits + SyncListCache.Misses; if (num == 0L) { sb.AppendLine(" no sends yet"); return; } sb.AppendLine($" scans avoided {SyncListCache.Hits}/{num} sends ({100f * (float)SyncListCache.Hits / (float)num:F0}%)"); sb.AppendLine($" cache window {ValConfig.SyncListCacheMs.Value:F0}ms (invalidated early on zone change or any destroy)"); } private static void AppendScheduler(StringBuilder sb) { if (!NpsEnv.IsHost()) { return; } sb.AppendLine(); sb.AppendLine("Send scheduler (last second):"); if (!PatchGuard.IsActive(Mechanism.SendScheduler) || !ValConfig.EnableSchedulerFix.Value) { sb.AppendLine(" vanilla round-robin (one peer per rendered frame)"); return; } int num = ((ZDOMan.s_instance != null) ? ZDOMan.s_instance.m_peers.Count : 0); float num2 = 1f / Mathf.Max(0.01f, ValConfig.SendIntervalSeconds.Value); float num3 = ((num > 0) ? ((float)SendSchedulerPatches.ServicedLastSecond / (float)num) : 0f); sb.AppendLine($" sends/s {SendSchedulerPatches.ServicedLastSecond} across {num} peers"); sb.AppendLine($" per-peer rate {num3:F1} Hz (target {num2:F0} Hz)"); sb.AppendLine($" last frame {SendSchedulerPatches.LastFrameServiced} peers"); sb.AppendLine($" budget breaks/s {SendSchedulerPatches.BudgetBreaksLastSecond} (frame budget {ValConfig.SendSchedulerFrameBudgetMs.Value:F1}ms)"); if (num > 0 && SendSchedulerPatches.BudgetBreaksLastSecond > 0 && num3 < num2 * 0.75f) { sb.AppendLine(" NOTE: send rate is CPU-bound - the frame budget is cutting rounds short. Raise Frame Budget Ms"); sb.AppendLine(" to trade server frame time for send rate, or accept the lower rate."); } } private static void AppendRoutedRpc(StringBuilder sb) { if (NpsEnv.IsHost()) { sb.AppendLine(); sb.AppendLine("Routed RPC relay (since start):"); if (!PatchGuard.IsActive(Mechanism.RoutedRpcFilter) || !ValConfig.EnableRoutedRpcFilter.Value) { sb.AppendLine(" vanilla (every broadcast RPC relayed to every peer)"); return; } AppendRelayRow(sb, "ZDO-targeted", RoutedRpcFilter.TargetedEvents, RoutedRpcFilter.TargetedSent, RoutedRpcFilter.TargetedSuppressed); AppendRelayRow(sb, "DestroyZDO", RoutedRpcFilter.DestroyEvents, RoutedRpcFilter.DestroySent, RoutedRpcFilter.DestroySuppressed); AppendRelayRow(sb, "positional", RoutedRpcFilter.PositionalEvents, RoutedRpcFilter.PositionalSent, RoutedRpcFilter.PositionalSuppressed); sb.AppendLine(string.Format(" {0} {1} events (relayed to everyone, by design)", Pad("global", 13), RoutedRpcFilter.GlobalEvents)); sb.AppendLine($" last second sent {RoutedRpcFilter.SentLastSecond} msgs, suppressed {RoutedRpcFilter.SuppressedLastSecond} msgs"); } } private static void AppendRelayRow(StringBuilder sb, string label, long events, long sent, long suppressed) { long num = sent + suppressed; string text = ((num > 0) ? $"{100f * (float)suppressed / (float)num:F0}% saved" : "-"); sb.AppendLine($" {Pad(label, 13)} {events} events, {sent} sent, {suppressed} suppressed ({text})"); } private static void AppendOwnership(StringBuilder sb) { if (NpsEnv.IsHost()) { sb.AppendLine(); sb.AppendLine("Ownership (last pass):"); sb.AppendLine($" candidates {OwnershipArbiter.LastPassCandidates}"); sb.AppendLine($" considered {OwnershipArbiter.LastPassConsidered}"); sb.AppendLine($" unowned {OwnershipArbiter.LastPassUnownedOnEntry} on entry"); sb.AppendLine($" rescued {OwnershipArbiter.LastPassRescued} (had no present owner - never capped)"); sb.AppendLine($" released {OwnershipArbiter.LastPassReleased} (no eligible owner in range)"); sb.AppendLine($" optimised {OwnershipArbiter.LastPassOptimised} (moved to a lower-latency owner)"); sb.AppendLine($" deferred {OwnershipArbiter.LastPassDeferred} (optimisations only, hit the per-pass cap of {OwnershipArbiter.LastPassCap})"); sb.AppendLine($" pass time {OwnershipArbiter.LastPassMs:F1}ms"); sb.AppendLine($" total since start rescued {OwnershipArbiter.TotalRescued}, optimised {OwnershipArbiter.TotalOptimised}"); if (OwnershipArbiter.LastPassUnownedOnEntry > OwnershipArbiter.LastPassRescued && OwnershipArbiter.LastPassUnownedOnEntry * 4 > OwnershipArbiter.LastPassConsidered) { sb.AppendLine(" WARNING: unowned backlog exceeds what this pass restored."); } } } private static void AppendExtrapolation(StringBuilder sb) { if (NpsEnv.IsHost() && NpsEnv.IsDedicated()) { return; } sb.AppendLine(); sb.AppendLine("Latency compensation (per second):"); if (!NpsEnv.IsHost() && !LatencyRegistry.HasPublishedTable) { sb.AppendLine(" no latency table received - the host is not running this mod, so"); sb.AppendLine(" rendering is exactly vanilla."); return; } if (!NpsEnv.IsHost() && !LatencyRegistry.HasFreshTable) { sb.AppendLine($" latency table stale ({LatencyRegistry.PublishedTableAgeSeconds:F0}s old) - compensation"); sb.AppendLine(" suspended until the host publishes again."); return; } if (!NpsEnv.IsHost()) { sb.AppendLine($" table entries {LatencyRegistry.PublishedEntryCount} (host + measured peers near you)"); } sb.AppendLine($" entities corrected {NpsExtrapolate.SamplesThisSecond}"); sb.AppendLine($" mean staleness {NpsExtrapolate.MeanStalenessMs:F0}ms"); sb.AppendLine($" mean correction {NpsExtrapolate.MeanDisplacement:F2}m"); sb.AppendLine($" peak correction {NpsExtrapolate.MaxDisplacement:F2}m"); sb.AppendLine($" clamp hits (total) {NpsExtrapolate.ClampHits}"); } internal static string BuildOverlay() { if (!NpsEnv.IsHost() && !LatencyRegistry.HasPublishedTable) { return "NPS: no latency table (host not running this mod) - rendering is vanilla"; } if (!NpsEnv.IsHost() && !LatencyRegistry.HasFreshTable) { return $"NPS: latency table stale ({LatencyRegistry.PublishedTableAgeSeconds:F0}s old) - compensation suspended"; } long peerUid = NpsEnv.LocalSessionId(); float num = (NpsEnv.IsHost() ? 0f : LatencyRegistry.RttMs(peerUid)); return $"NPS myRtt {num:F0}ms corrected {NpsExtrapolate.SamplesThisSecond}/s" + $" staleness {NpsExtrapolate.MeanStalenessMs:F0}ms" + $" shift {NpsExtrapolate.MeanDisplacement:F2}m (peak {NpsExtrapolate.MaxDisplacement:F2}m)" + $" clamps {NpsExtrapolate.ClampHits}"; } private static string Pad(string value, int width) { if (value == null) { value = ""; } if (value.Length < width) { return value + new string(' ', width - value.Length); } return value; } internal static void ForgetPeer(long uid) { Stats.Remove(uid); } internal static void Reset() { Stats.Clear(); Collecting = false; } } internal static class NpsEnv { private static bool? _isDedicatedCache; internal static bool IsHost() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } internal static bool IsDedicated() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 if (_isDedicatedCache.HasValue) { return _isDedicatedCache.Value; } bool flag = Application.isBatchMode || (int)SystemInfo.graphicsDeviceType == 4; _isDedicatedCache = flag; return flag; } internal static bool IsClient() { if ((Object)(object)ZNet.instance != (Object)null) { return !ZNet.instance.IsServer(); } return false; } internal static long LocalSessionId() { if (ZDOMan.s_instance == null) { return 0L; } return ZDOMan.GetSessionID(); } internal static bool NetReady() { if ((Object)(object)ZNet.instance != (Object)null) { return ZDOMan.s_instance != null; } return false; } } internal static class NpsExtrapolate { private const float MaxExtrapolationSeconds = 2f; internal static int SamplesThisSecond; internal static float MeanStalenessMs; internal static float MeanDisplacement; internal static float MaxDisplacement; internal static int ClampHits; private static float _stalenessAccum; private static float _displacementAccum; private static int _accumCount; private static float _windowStart; internal static Vector3 Offset(Vector3 velocity, float rawTimer, ZDO zdo) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) Vector3 val = velocity * rawTimer; if (!PatchGuard.IsActive(Mechanism.Extrapolation)) { return val; } if (!ValConfig.EnableLatencyCompensation.Value) { return val; } if (zdo == null) { return val; } float value = ValConfig.LatencyCompensationStrength.Value; if (value <= 0f) { return val; } float num = LatencyRegistry.PathStalenessSeconds(zdo.GetOwner()) * value; if (num <= 0f) { return val; } float num2 = Mathf.Min(rawTimer + num, 2f) - rawTimer; if (num2 <= 0f) { return val; } float magnitude = ((Vector3)(ref velocity)).magnitude; if (magnitude <= 0f) { return val; } float value2 = ValConfig.LatencyCompensationMaxMeters.Value; float num3 = magnitude * rawTimer; float num4 = magnitude * num2; if (num3 + num4 > value2) { num4 = Mathf.Max(0f, value2 - num3); ClampHits++; } if (num4 <= 0f) { return val; } Record(num, num4); return val + velocity * (num4 / magnitude); } private static void Record(float stalenessSeconds, float displacement) { _stalenessAccum += stalenessSeconds * 1000f; _displacementAccum += displacement; _accumCount++; if (displacement > MaxDisplacement) { MaxDisplacement = displacement; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup - _windowStart < 1f)) { SamplesThisSecond = _accumCount; MeanStalenessMs = ((_accumCount > 0) ? (_stalenessAccum / (float)_accumCount) : 0f); MeanDisplacement = ((_accumCount > 0) ? (_displacementAccum / (float)_accumCount) : 0f); _windowStart = realtimeSinceStartup; _accumCount = 0; _stalenessAccum = 0f; _displacementAccum = 0f; MaxDisplacement = 0f; } } internal static void Reset() { SamplesThisSecond = 0; MeanStalenessMs = 0f; MeanDisplacement = 0f; MaxDisplacement = 0f; ClampHits = 0; _accumCount = 0; _stalenessAccum = 0f; _displacementAccum = 0f; _windowStart = 0f; } } internal static class OwnershipArbiter { private struct Candidate { internal long Uid; internal Vector2s Zone; internal float RttMs; internal bool CanOwn; internal bool IsViewer; internal int NearRadius; } private struct Verdict { internal float TotalCostMs; internal float WorstCostMs; internal float OwnerRttMs; } private sealed class SectorVerdict { internal bool HasEligible; internal long BestUid; internal float BestTotalMs; internal readonly Dictionary<long, float> TotalByOwner = new Dictionary<long, float>(); internal readonly List<long> Present = new List<long>(); internal void Clear() { HasEligible = false; BestUid = 0L; BestTotalMs = float.MaxValue; TotalByOwner.Clear(); Present.Clear(); } } private struct OwnershipRecord { internal long Owner; internal float ChangedAt; internal float LastSeenAt; } private struct PendingMove { internal ZDO Zdo; internal long NewOwner; internal float ImprovementMs; } private static readonly List<Candidate> Candidates = new List<Candidate>(); private static readonly HashSet<Vector2s> ZonesToScan = new HashSet<Vector2s>(); private static readonly Dictionary<ZDOID, OwnershipRecord> OwnerHistory = new Dictionary<ZDOID, OwnershipRecord>(); private static readonly List<PendingMove> Pending = new List<PendingMove>(); private static readonly Dictionary<Vector2s, SectorVerdict> SectorCache = new Dictionary<Vector2s, SectorVerdict>(); private static readonly List<SectorVerdict> VerdictPool = new List<SectorVerdict>(); private static int _verdictsInUse; private static readonly Dictionary<long, int> OwnedCount = new Dictionary<long, int>(); private static readonly Dictionary<long, int> MovesPerTarget = new Dictionary<long, int>(); internal static int LastPassCandidates; internal static int LastPassConsidered; internal static int LastPassUnownedOnEntry; internal static int LastPassRescued; internal static int LastPassReleased; internal static int LastPassOptimised; internal static int LastPassDeferred; internal static int LastPassCap; internal static long TotalRescued; internal static long TotalOptimised; internal static float LastPassMs; private const float HoldTableTtlSeconds = 120f; private static float _lastPrune; private const float RescueWarningIntervalSeconds = 30f; private static float _lastRescueWarning; private const int RescueWarningConsecutivePasses = 3; private static int _rescueBurstStreak; internal static void RunPass(ZDOMan zdoMan) { //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Invalid comparison between Unknown and I4 if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null) { return; } Stopwatch stopwatch = Stopwatch.StartNew(); float realtimeSinceStartup = Time.realtimeSinceStartup; float value = ValConfig.OwnershipMinHoldSeconds.Value; float margin = ValConfig.OwnershipChallengeMarginMs.Value; float value2 = ValConfig.OwnershipLoadPenaltyMs.Value; BuildCandidates(zdoMan); LastPassCandidates = Candidates.Count; if (Candidates.Count == 0) { LastPassMs = 0f; return; } CollectZonesToScan(); TallyOwnedLoad(zdoMan, value2 > 0f); SectorCache.Clear(); _verdictsInUse = 0; Pending.Clear(); LastPassConsidered = 0; LastPassUnownedOnEntry = 0; LastPassRescued = 0; LastPassReleased = 0; long sessionID = zdoMan.m_sessionID; foreach (Vector2s item in ZonesToScan) { List<ZDO> list = ZoneObjects(zdoMan, item); List<ZDO> list2 = ZonePortals(zdoMan, item); bool flag = list != null && list.Count > 0; bool flag2 = list2 != null && list2.Count > 0; if (flag || flag2) { SectorVerdict verdict = VerdictFor(item); if (flag) { ApplyVerdict(list, verdict, sessionID, realtimeSinceStartup, value, margin); } if (flag2) { ApplyVerdict(list2, verdict, sessionID, realtimeSinceStartup, value, margin); } } } ApplyUpgrades(realtimeSinceStartup); PruneHoldTable(realtimeSinceStartup); stopwatch.Stop(); LastPassMs = (float)stopwatch.Elapsed.TotalMilliseconds; _rescueBurstStreak = ((LastPassConsidered > 0 && LastPassRescued * 4 > LastPassConsidered) ? (_rescueBurstStreak + 1) : 0); if (_rescueBurstStreak >= 3 && realtimeSinceStartup - _lastRescueWarning > 30f) { _lastRescueWarning = realtimeSinceStartup; Logger.LogWarning($"Ownership: rescued {LastPassRescued} of {LastPassConsidered} nearby ZDOs with no present owner, {_rescueBurstStreak} passes in a row."); } if ((int)Logger.Level >= 32) { Logger.LogDebug($"Ownership pass: considered {LastPassConsidered} ({LastPassUnownedOnEntry} unowned) over {ZonesToScan.Count} zones, rescued {LastPassRescued}, released {LastPassReleased}, optimised {LastPassOptimised}, deferred {LastPassDeferred}, history {OwnerHistory.Count}, {LastPassMs:F1}ms."); } } private static void ApplyVerdict(List<ZDO> objects, SectorVerdict verdict, long sessionId, float now, float minHold, float margin) { int count = verdict.Present.Count; if (!verdict.HasEligible && count == 0) { ReleaseZone(objects); } else if (verdict.HasEligible && count == 1) { AssignZone(objects, verdict.BestUid, sessionId, now); } else { ArbitrateZone(objects, verdict, now, minHold, margin); } } private static SectorVerdict VerdictFor(Vector2s sector) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (SectorCache.TryGetValue(sector, out var value)) { return value; } SectorVerdict sectorVerdict; if (_verdictsInUse < VerdictPool.Count) { sectorVerdict = VerdictPool[_verdictsInUse]; sectorVerdict.Clear(); } else { sectorVerdict = new SectorVerdict(); VerdictPool.Add(sectorVerdict); } _verdictsInUse++; BuildVerdict(sector, sectorVerdict); SectorCache[sector] = sectorVerdict; return sectorVerdict; } private static void BuildVerdict(Vector2s sector, SectorVerdict verdict) { //IL_003d: 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_0064: Unknown result type (might be due to invalid IL or missing references) float value = ValConfig.OwnershipLoadPenaltyMs.Value; float num = 0.5f * (float)ValConfig.OwnershipChallengeMarginMs.Value; Verdict incumbent = default(Verdict); bool flag = false; for (int i = 0; i < Candidates.Count; i++) { Candidate owner = Candidates[i]; if (ZoneCompat.InActiveArea(sector, owner.Zone, 1)) { verdict.Present.Add(owner.Uid); Verdict verdict2 = Score(owner, sector); if (value > 0f && OwnedCount.TryGetValue(owner.Uid, out var value2)) { verdict2.TotalCostMs += Mathf.Min((float)value2 * value, num); } verdict.TotalByOwner[owner.Uid] = verdict2.TotalCostMs; if (owner.CanOwn && (!flag || IsBetter(verdict2, incumbent))) { incumbent = verdict2; verdict.BestUid = owner.Uid; flag = true; } } } verdict.HasEligible = flag; verdict.BestTotalMs = (flag ? incumbent.TotalCostMs : float.MaxValue); } private static float Touch(ZDOID uid, long currentOwner, float now) { //IL_0005: 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) //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (OwnerHistory.TryGetValue(uid, out var value) && value.Owner == currentOwner) { value.LastSeenAt = now; OwnerHistory[uid] = value; return value.ChangedAt; } OwnerHistory[uid] = new OwnershipRecord { Owner = currentOwner, ChangedAt = now, LastSeenAt = now }; return now; } private static void BuildCandidates(ZDOMan zdoMan) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //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) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: 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_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) Candidates.Clear(); SimulationDistance val = ZoneCompat.Local(); Candidates.Add(new Candidate { Uid = zdoMan.m_sessionID, Zone = ZoneSystem.GetZone(ZNet.instance.GetReferencePosition()), RttMs = 0f, CanOwn = ValConfig.OwnershipAllowHostOwner.Value, IsViewer = !NpsEnv.IsDedicated(), NearRadius = Mathf.Max(1, ((SimulationDistance)(ref val)).NearSimulationDistance) }); float num = ValConfig.OwnershipUnmeasuredRttMs.Value; List<ZDOPeer> peers = zdoMan.m_peers; for (int i = 0; i < peers.Count; i++) { ZNetPeer val2 = peers[i]?.m_peer; if (val2 != null && val2.m_uid != 0L) { Vector3 refPos = val2.GetRefPos(); if (!(refPos == Vector3.zero)) { long uid = val2.m_uid; Candidates.Add(new Candidate { Uid = uid, Zone = ZoneSystem.GetZone(refPos), RttMs = (LatencyRegistry.HasMeasurement(uid) ? LatencyRegistry.MeasuredRttMs(uid) : num), CanOwn = true, IsViewer = true, NearRadius = ZoneCompat.NearFor(val2) }); } } } } private static void CollectZonesToScan() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) ZonesToScan.Clear(); for (int i = 0; i < Candidates.Count; i++) { Candidate candidate = Candidates[i]; Vector2s zone = candidate.Zone; int nearRadius = candidate.NearRadius; for (int j = -nearRadius; j <= nearRadius; j++) { for (int k = -nearRadius; k <= nearRadius; k++) { ZonesToScan.Add(new Vector2s(zone.x + j, zone.y + k)); } } } } private static List<ZDO> ZoneObjects(ZDOMan zdoMan, Vector2s zone) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return ZoneCompat.SectorObjects(zdoMan, zone); } private static List<ZDO> ZonePortals(ZDOMan zdoMan, Vector2s zone) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return ZoneCompat.PortalObjects(zdoMan, zone); } private static void TallyOwnedLoad(ZDOMan zdoMan, bool tallyLoad) { //IL_001d: 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_0024: 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) OwnedCount.Clear(); if (!tallyLoad) { return; } foreach (Vector2s item in ZonesToScan) { TallyZone(ZoneObjects(zdoMan, item)); TallyZone(ZonePortals(zdoMan, item)); } } private static void TallyZone(List<ZDO> objects) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 if (objects == null) { return; } for (int i = 0; i < objects.Count; i++) { ZDO val = objects[i]; if (val != null && val.Persistent && (int)val.Type == 1 && val.HasOwner()) { long owner = val.GetOwner(); OwnedCount.TryGetValue(owner, out var value); OwnedCount[owner] = value + 1; } } } private static void ReleaseZone(List<ZDO> objects) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < objects.Count; i++) { ZDO val = objects[i]; if (val != null && val.Persistent) { LastPassConsidered++; if (!val.HasOwner()) { LastPassUnownedOnEntry++; continue; } val.SetOwner(0L); OwnerHistory.Remove(val.m_uid); LastPassReleased++; } } } private static void AssignZone(List<ZDO> objects, long bestUid, long sessionId, float now) { bool flag = bestUid == sessionId; for (int i = 0; i < objects.Count; i++) { ZDO val = objects[i]; if (val == null || !val.Persistent) { continue; } LastPassConsidered++; if (!val.HasOwner()) { LastPassUnownedOnEntry++; Rescue(val, bestUid, now); continue; } if (flag) { if (val.IsOwner()) { continue; } } else if (!val.IsOwner() && val.GetOwner() == bestUid) { continue; } Rescue(val, bestUid, now); } } private static void ArbitrateZone(List<ZDO> objects, SectorVerdict verdict, float now, float minHold, float margin) { //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < objects.Count; i++) { ZDO val = objects[i]; if (val == null || !val.Persistent) { continue; } LastPassConsidered++; if (!val.HasOwner()) { LastPassUnownedOnEntry++; if (verdict.HasEligible) { Rescue(val, verdict.BestUid, now); } continue; } long owner = val.GetOwner(); if (!verdict.HasEligible) { if (!IsPresent(verdict, owner)) { val.SetOwner(0L); OwnerHistory.Remove(val.m_uid); LastPassReleased++; } } else if (!IsPresent(verdict, owner)) { Rescue(val, verdict.BestUid, now); } else { if (verdict.BestUid == owner || OwnershipPolicy.IsDirectlyControlled(val)) { continue; } float num = Touch(val.m_uid, owner, now); if (!(now - num < minHold)) { float value; float num2 = (verdict.TotalByOwner.TryGetValue(owner, out value) ? value : float.MaxValue) - verdict.BestTotalMs; if (!(num2 < margin)) { Pending.Add(new PendingMove { Zdo = val, NewOwner = verdict.BestUid, ImprovementMs = num2 }); } } } } } private static void Rescue(ZDO zdo, long newOwner, float now) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) zdo.SetOwner(newOwner); OwnerHistory[zdo.m_uid] = new OwnershipRecord { Owner = newOwner, ChangedAt = now, LastSeenAt = now }; LastPassRescued++; TotalRescued++; } private static bool IsPresent(SectorVerdict verdict, long uid) { List<long> present = verdict.Present; for (int i = 0; i < present.Count; i++) { if (present[i] == uid) { return true; } } return false; } private static bool IsBetter(Verdict candidate, Verdict incumbent) { if (!Mathf.Approximately(candidate.TotalCostMs, incumbent.TotalCostMs)) { return candidate.TotalCostMs < incumbent.TotalCostMs; } if (!Mathf.Approximately(candidate.WorstCostMs, incumbent.WorstCostMs)) { return candidate.WorstCostMs < incumbent.WorstCostMs; } return candidate.OwnerRttMs < incumbent.OwnerRttMs; } private static Verdict Score(Candidate owner, Vector2s sector) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) float num = 0f; float num2 = 0f; for (int i = 0; i < Candidates.Count; i++) { Candidate candidate = Candidates[i]; if (candidate.IsViewer && candidate.Uid != owner.Uid && ZoneCompat.InActiveArea(sector, candidate.Zone, 1)) { float num3 = (owner.RttMs + candidate.RttMs) * 0.5f; num += num3; if (num3 > num2) { num2 = num3; } } } return new Verdict { TotalCostMs = num, WorstCostMs = num2, OwnerRttMs = owner.RttMs }; } private static void ApplyUpgrades(float now) { //IL_0107: Unknown result type (might be due to invalid IL or missing references) LastPassOptimised = 0; LastPassDeferred = 0; int num = (LastPassCap = Mathf.Max(Mathf.Max(1, ValConfig.OwnershipMaxReassignsPerPass.Value), Candidates.Count)); if (Pending.Count == 0) { return; } Pending.Sort((PendingMove a, PendingMove b) => b.ImprovementMs.CompareTo(a.ImprovementMs)); int num2 = Mathf.Max(1, (num + 1) / 2); MovesPerTarget.Clear(); for (int num3 = 0; num3 < Pending.Count; num3++) { if (LastPassOptimised >= num) { LastPassDeferred += Pending.Count - num3; break; } PendingMove pendingMove = Pending[num3]; MovesPerTarget.TryGetValue(pendingMove.NewOwner, out var value); if (value >= num2) { LastPassDeferred++; continue; } MovesPerTarget[pendingMove.NewOwner] = value + 1; pendingMove.Zdo.SetOwner(pendingMove.NewOwner); OwnerHistory[pendingMove.Zdo.m_uid] = new OwnershipRecord { Owner = pendingMove.NewOwner, ChangedAt = now, LastSeenAt = now }; LastPassOptimised++; TotalOptimised++; } } private static void PruneHoldTable(float now) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) if (now - _lastPrune < 120f) { return; } _lastPrune = now; List<ZDOID> list = null; foreach (KeyValuePair<ZDOID, OwnershipRecord> item in OwnerHistory) { if (now - item.Value.LastSeenAt > 120f) { (list ?? (list = new List<ZDOID>())).Add(item.Key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { OwnerHistory.Remove(list[i]); } } } internal static void Reset() { Candidates.Clear(); ZonesToScan.Clear(); OwnerHistory.Clear(); Pending.Clear(); SectorCache.Clear(); VerdictPool.Clear(); _verdictsInUse = 0; OwnedCount.Clear(); MovesPerTarget.Clear(); _lastPrune = 0f; _lastRescueWarning = 0f; _rescueBurstStreak = 0; LastPassCandidates = 0; LastPassConsidered = 0; LastPassUnownedOnEntry = 0; LastPassRescued = 0; LastPassReleased = 0; LastPassOptimised = 0; LastPassDeferred = 0; LastPassCap = 0; TotalRescued = 0L; TotalOptimised = 0L; LastPassMs = 0f; } } internal static class OwnershipPolicy { private enum PrefabClass : byte { Ordinary, Player, Ship, Mount, Cart } private static readonly Dictionary<int, PrefabClass> ClassCache = new Dictionary<int, PrefabClass>(); internal static bool IsDirectlyControlled(ZDO zdo) { return Classify(zdo) switch { PrefabClass.Player => true, PrefabClass.Ship => true, PrefabClass.Mount => zdo.GetLong(ZDOVars.s_user, 0L) != 0, PrefabClass.Cart => zdo.GetBool(ZDOVars.s_attachJointHash, false), _ => false, }; } private static PrefabClass Classify(ZDO zdo) { int prefab = zdo.GetPrefab(); if (ClassCache.TryGetValue(prefab, out var value)) { return value; } PrefabClass prefabClass = PrefabClass.Ordinary; if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab); if ((Object)(object)prefab2 != (Object)null) { if ((Object)(object)prefab2.GetComponent<Player>() != (Object)null) { prefabClass = PrefabClass.Player; } else if ((Object)(object)prefab2.GetComponent<Ship>() != (Object)null) { prefabClass = PrefabClass.Ship; } else if ((Object)(object)prefab2.GetComponent<Vagon>() != (Object)null) { prefabClass = PrefabClass.Cart; } else if ((Object)(object)prefab2.GetComponent<Tameable>() != (Object)null) { prefabClass = PrefabClass.Mount; } ClassCache[prefab] = prefabClass; return prefabClass; } return PrefabClass.Ordinary; } return PrefabClass.Ordinary; } internal static void Reset() { ClassCache.Clear(); } } internal enum Mechanism { RttSampling, SendWindow, SendScheduler, Ownership, RefPos, Extrapolation, RoutedRpcFilter, SteamTransport, SyncListCache, PlayerLimit, ConnectionTimeout } internal static class PatchGuard { internal const string ReturnToSenderGUID = "redseiko.valheim.returntosender"; internal const string BetterZeeRouterGUID = "redseiko.valheim.betterzeerouter"; internal const string EnRouteGUID = "redseiko.valheim.enroute"; private static readonly HashSet<Mechanism> Disabled = new HashSet<Mechanism>(); private static readonly Dictionary<Mechanism, string> DisableReasons = new Dictionary<Mechanism, string>(); internal static bool IsActive(Mechanism mechanism) { return !Disabled.Contains(mechanism); } internal static void Disable(Mechanism mechanism, string reason) { if (Disabled.Add(mechanism)) { DisableReasons[mechanism] = reason; Logger.LogWarning($"{mechanism} disabled: {reason}"); } } internal static string GetDisableReason(Mechanism mechanism) { if (!DisableReasons.TryGetValue(mechanism, out var value)) { return null; } return value; } internal static bool IsPluginLoaded(string guid) { if (Chainloader.PluginInfos != null) { return Chainloader.PluginInfos.ContainsKey(guid); } return false; } internal static void VerifyAfterPatching() { List<string> list = new List<string>(); foreach (Mechanism value in Enum.GetValues(typeof(Mechanism))) { if (IsActive(value)) { list.Add(value.ToString()); } } if (list.Count == 0) { Logger.LogError("No mechanisms are active - the mod is loaded but doing nothing. See the warnings above."); return; } Logger.LogInfo("Active mechanisms: " + string.Join(", ", list.ToArray())); if (Disabled.Count <= 0) { return; } foreach (KeyValuePair<Mechanism, string> disableReason in DisableReasons) { Logger.LogInfo($" inactive - {disableReason.Key}: {disableReason.Value}"); } } } internal static class PlayerLimit { internal const int VanillaLimit = 10; private const int SteamLobbyMemberCeiling = 250; private const int PlayFabLobbyMemberCeiling = 128; internal static bool CrossplayCapacityPinned; private static bool _warnedPlayFabClamp; private static bool _warnedSteamClamp; internal static bool Active { get { if (PatchGuard.IsActive(Mechanism.PlayerLimit) && ValConfig.EnablePlayerLimitOverride != null && ValConfig.MaxPlayers != null) { return ValConfig.EnablePlayerLimitOverride.Value; } return false; } } internal static int Configured { get { if (!Active) { return 10; } return ValConfig.MaxPlayers.Value; } } internal static int Current() { return Configured; } internal static int SteamLobbyCapacity() { int configured = Configured; if (configured > 250) { if (!_warnedSteamClamp) { _warnedSteamClamp = true; Logger.LogWarning($"Max Players is {configured}, above Steam's {250}-member lobby ceiling. " + $"The server browser will advertise {250}; the limit the server actually enforces is unaffected."); } return 250; } return configured; } internal static uint PlayFabLobbyCapacity() { int num = Configured + (NpsEnv.IsDedicated() ? 1 : 0); if (num > 128) { if (!_warnedPlayFabClamp) { _warnedPlayFabClamp = true; Logger.LogWarning($"Max Players is {Configured}, which needs {num} PlayFab lobby slots - above PlayFab's " + $"{128}-member ceiling. Crossplay joins will stop at {128} " + "even though the server itself would accept more. Steam-only servers are unaffected."); } num = 128; } return (uint)num; } } internal static class RoutedRpcFilter { private const string RoutedRpcMethod = "RoutedRPC"; private static readonly int DestroyZdoHash = StringExtensionMethods.GetStableHashCode("DestroyZDO"); private static readonly int DamageTextHash = StringExtensionMethods.GetStableHashCode("RPC_DamageText"); private static readonly int SpawnObjectHash = StringExtensionMethods.GetStableHashCode("SpawnObject"); private const int MaxDestroyBatch = 65536; private static readonly HashSet<long> DestroyHolders = new HashSet<long>(); private static int _destroyBatchCount = -1; private static ZDOID _destroyBatchFirst = ZDOID.None; internal static long TargetedEvents; internal static long TargetedSent; internal static long TargetedSuppressed; internal static long DestroyEvents; internal static long DestroySent; internal static long DestroySuppressed; internal static long PositionalEvents; internal static long PositionalSent; internal static long PositionalSuppressed; internal static long GlobalEvents; internal static int SentLastSecond; internal static int SuppressedLastSecond; private static int _sentAccum; private static int _suppressedAccum; private static float _windowStart; internal static bool TryRelay(ZRoutedRpc router, RoutedRPCData data) { if (!PatchGuard.IsActive(Mechanism.RoutedRpcFilter)) { return false; } if (!ValConfig.EnableRoutedRpcFilter.Value) { return false; } if (router == null || data == null || !router.m_server) { return false; } if (data.m_targetPeerID != 0L) { return false; } try { if (!((ZDOID)(ref data.m_targetZDO)).IsNone()) { return RelayTargeted(data); } if (data.m_methodHash == DestroyZdoHash) { return RelayDestroy(router, data); } if (data.m_methodHash == DamageTextHash || data.m_methodHash == SpawnObjectHash) { return RelayPositional(router, data); } } catch (Exception ex) { Logger.LogWarning("Routed RPC filter fell back to vanilla relay: " + ex.GetType().Name + ": " + ex.Message); ClearDestroySnapshot(); return false; } GlobalEvents++; return false; } private static bool RelayTargeted(RoutedRPCData data) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown ZDOMan s_instance = ZDOMan.s_instance; if (s_instance == null) { return false; } List<ZDOPeer> peers = s_instance.m_peers; ZPackage val = null; int num = 0; int num2 = 0; for (int i = 0; i < peers.Count; i++) { ZDOPeer val2 = peers[i]; ZNetPeer val3 = val2?.m_peer; if (val3 == null || !val3.IsReady() || val3.m_uid == data.m_senderPeerID) { continue; } if (!val2.m_zdos.ContainsKey(data.m_targetZDO)) { num2++; continue; } if (val == null) { val = new ZPackage(); data.Serialize(val); } val3.m_rpc.Invoke("RoutedRPC", new object[1] { val }); num++; } TargetedEvents++; TargetedSent += num; TargetedSuppressed += num2; Record(num, num2); return true; } internal static void SnapshotDestroyHolders(ZDOMan zdoMan, ZPackage pkg) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) ClearDestroySnapshot(); if (!PatchGuard.IsActive(Mechanism.RoutedRpcFilter) || !ValConfig.EnableRoutedRpcFilter.Value || zdoMan == null || pkg == null || !NpsEnv.IsHost()) { return; } int pos = pkg.GetPos(); try { int num = pkg.ReadInt(); if (num < 0 || num > 65536) { return; } _destroyBatchCount = num; List<ZDOPeer> peers = zdoMan.m_peers; for (int i = 0; i < num; i++) { ZDOID val = pkg.ReadZDOID(); if (i == 0) { _destroyBatchFirst = val; } if (DestroyHolders.Count >= peers.Count) { break; } for (int j = 0; j < peers.Count; j++) { ZDOPeer val2 = peers[j]; ZNetPeer val3 = val2?.m_peer; if (val3 != null && val3.m_uid != 0L && !DestroyHolders.Contains(val3.m_uid) && val2.m_zdos.ContainsKey(val)) { DestroyHolders.Add(val3.m_uid); } } } } catch (Exception) { ClearDestroySnapshot(); } finally { pkg.SetPos(pos); } } private static bool RelayDestroy(ZRoutedRpc router, RoutedRPCData data) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown if (!SnapshotMatches(data)) { ClearDestroySnapshot(); DestroyEvents++; return false; } List<ZNetPeer> peers = router.m_peers; ZPackage val = null; int num = 0; int num2 = 0; for (int i = 0; i < peers.Count; i++) { ZNetPeer val2 = peers[i]; if (val2 == null || !val2.IsReady() || val2.m_uid == data.m_senderPeerID) { continue; } if (!DestroyHolders.Contains(val2.m_uid)) { num2++; continue; } if (val == null) { val = new ZPackage(); data.Serialize(val); } val2.m_rpc.Invoke("RoutedRPC", new object[1] { val }); num++; } ClearDestroySnapshot(); DestroyEvents++; DestroySent += num; DestroySuppressed += num2; Record(num, num2); return true; } private static bool SnapshotMatches(RoutedRPCData data) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (_destroyBatchCount < 0) { return false; } ZPackage parameters = data.m_parameters; int pos = parameters.GetPos(); try { parameters.SetPos(0); ZPackage val = parameters.ReadPackage(); int num = val.ReadInt(); if (num != _destroyBatchCount) { return false; } if (num > 0 && val.ReadZDOID() != _destroyBatchFirst) { return false; } return true; } catch (Exception) { return false; } finally { parameters.SetPos(pos); } } private static void ClearDestroySnapshot() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) DestroyHolders.Clear(); _destroyBatchCount = -1; _destroyBatchFirst = ZDOID.None; } private static bool RelayPositional(ZRoutedRpc router, RoutedRPCData data) { //IL_001b: 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_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown if ((Object)(object)ZoneSystem.instance == (Object)null) { return false; } if (!TryReadPosition(data, out var pos)) { return false; } Vector2s zone = ZoneSystem.GetZone(pos); List<ZNetPeer> peers = router.m_peers; ZPackage val = null; int num = 0; int num2 = 0; for (int i = 0; i < peers.Count; i++) { ZNetPeer val2 = peers[i]; if (val2 == null || !val2.IsReady() || val2.m_uid == data.m_senderPeerID) { continue; } int radius = ZoneCompat.NearFor(val2) + 1; if (!ZoneCompat.InActiveArea(ZoneSystem.GetZone(val2.GetRefPos()), zone, radius)) { num2++; continue; } if (val == null) { val = new ZPackage(); data.Serialize(val); } val2.m_rpc.Invoke("RoutedRPC", new object[1] { val }); num++; } PositionalEvents++; PositionalSent += num; PositionalSuppressed += num2; Record(num, num2); return true; } private static bool TryReadPosition(RoutedRPCData data, out Vector3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) pos = default(Vector3); ZPackage parameters = data.m_parameters; int pos2 = parameters.GetPos(); try { parameters.SetPos(0); if (data.m_methodHash == DamageTextHash) { ZPackage val = parameters.ReadPackage(); val.ReadInt(); pos = val.ReadVector3(); } else { pos = parameters.ReadVector3(); } return !float.IsNaN(pos.x) && !float.IsNaN(pos.z) && !float.IsInfinity(pos.x) && !float.IsInfinity(pos.z); } catch (Exception) { return false; } finally { parameters.SetPos(pos2); } } private static void Record(int sent, int suppressed) { _sentAccum += sent; _suppressedAccum += suppressed; float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup - _windowStart < 1f)) { SentLastSecond = _sentAccum; SuppressedLastSecond = _suppressedAccum; _sentAccum = 0; _suppressedAccum = 0; _windowStart = realtimeSinceStartup; } } internal static void Reset() { ClearDestroySnapshot(); TargetedEvents = 0L; TargetedSent = 0L; TargetedSuppressed = 0L; DestroyEvents = 0L; DestroySent = 0L; DestroySuppressed = 0L; PositionalEvents = 0L; PositionalSent = 0L; PositionalSuppressed = 0L; GlobalEvents = 0L; SentLastSecond = 0; SuppressedLastSecond = 0; _sentAccum = 0; _suppressedAccum = 0; _windowStart = 0f; } } internal static class RttProbe { private enum SteamApi { Unresolved, Client, GameServer } internal struct LinkStatus { internal int PendingBytes; internal int InFlightBytes; internal int SendRateBytesPerSec; internal float QualityLocal; internal float QualityRemote; } private static SteamApi _resolved = SteamApi.Unresolved; private static int _consecutiveDirectFailures; private const int MaxConsecutiveDirectFailures = 3; private static readonly Dictionary<Type, FieldInfo> WrapperOriginal = new Dictionary<Type, FieldInfo>(); private const int MaxUnwrapDepth = 4; private static int _consecutiveStatusFailures; private static bool _statusUnavailable; private const int MaxConsecutiveStatusFailures = 20; internal static bool TryGetPingMs(ISocket socket, out int pingMs) { pingMs = 0; if (socket == null || !PatchGuard.IsActive(Mechanism.RttSampling)) { return false; } socket = Unwrap(socket); ZSteamSocket val = (ZSteamSocket)(object)((socket is ZSteamSocket) ? socket : null); if (val != null) { return TrySteam(val, out pingMs); } try { float num = default(float); float num2 = default(float); flo