Decompiled source of SkadiNet v1.0.4

SkadiNet.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyFileVersion("1.0.4.0")]
[assembly: AssemblyInformationalVersion("1.0.4")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("sighsorry")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyProduct("SkadiNet")]
[assembly: AssemblyTitle("SkadiNet")]
[assembly: AssemblyVersion("1.0.4.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SkadiNet
{
	internal enum ConfigSyncScope
	{
		ServerSynced,
		ClientLocal
	}
	internal static class ConfigSyncManager
	{
		private static MethodInfo _addConfigEntryMethod;

		internal static ConfigSync Sync { get; private set; }

		internal static ConfigEntry<bool> LockServerConfig { get; private set; }

		internal static void Initialize(ConfigFile config)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			Sync = new ConfigSync("sighsorry.SkadiNet")
			{
				DisplayName = "SkadiNet",
				CurrentVersion = "1.0.4",
				MinimumRequiredVersion = "1.0.4",
				ModRequired = true
			};
			LockServerConfig = config.Bind<bool>("General", "LockServerConfig", true, new ConfigDescription("Lock server-synced config for non-admin clients.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 90
				}
			}));
			Sync.AddLockingConfigEntry<bool>(LockServerConfig).SynchronizedConfig = true;
		}

		internal static ConfigEntry<T> Bind<T>(ConfigFile config, string group, string name, T value, ConfigDescription description, ConfigSyncScope scope)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			string text = SuffixFor(scope);
			ConfigDescription val = new ConfigDescription(description.Description + text, description.AcceptableValues, description.Tags);
			ConfigEntry<T> obj = config.Bind<T>(group, name, value, val);
			Register((ConfigEntryBase)(object)obj, scope == ConfigSyncScope.ServerSynced);
			return obj;
		}

		private static string SuffixFor(ConfigSyncScope scope)
		{
			return scope switch
			{
				ConfigSyncScope.ServerSynced => " [Synced with Server]", 
				ConfigSyncScope.ClientLocal => " [Client Local; Not Synced with Server]", 
				_ => throw new ArgumentOutOfRangeException("scope", scope, null), 
			};
		}

		private static void Register(ConfigEntryBase entry, bool synchronized)
		{
			if (Sync != null && entry != null && (LockServerConfig == null || !object.Equals(entry.Definition, ((ConfigEntryBase)LockServerConfig).Definition)) && GetAddConfigEntryMethod().MakeGenericMethod(entry.SettingType).Invoke(Sync, new object[1] { entry }) is OwnConfigEntryBase ownConfigEntryBase)
			{
				ownConfigEntryBase.SynchronizedConfig = synchronized;
			}
		}

		private static MethodInfo GetAddConfigEntryMethod()
		{
			if (_addConfigEntryMethod != null)
			{
				return _addConfigEntryMethod;
			}
			_addConfigEntryMethod = typeof(ConfigSync).GetMethods(BindingFlags.Instance | BindingFlags.Public).First((MethodInfo method) => method.Name == "AddConfigEntry" && method.IsGenericMethodDefinition);
			return _addConfigEntryMethod;
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public int? Order;
	}
	internal static class EffectiveConfig
	{
		internal const int SteamSendRateBytes = 36000000;

		internal const int CompressionFailureLimitPerPeer = 1;

		internal const float OwnershipRelativeHysteresis = 0.15f;

		internal const float OwnershipAbsoluteHysteresisMs = 20f;

		internal const float OwnerSwitchCooldownSeconds = 3f;

		internal const float OwnerHintSwitchCooldownSeconds = 5f;

		internal const float ShipOwnerSwitchCooldownSeconds = 8f;

		internal const float RecoverUnownedAfterSeconds = 2f;

		internal static bool SchedulerEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.SchedulerThroughput);
				}
				return false;
			}
		}

		internal static bool PayloadReducerEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.PayloadReducerStrength);
				}
				return false;
			}
		}

		internal static bool CompressionEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.CompressionAggression);
				}
				return false;
			}
		}

		internal static bool RpcAoiEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.RpcAoiAggression);
				}
				return false;
			}
		}

		internal static bool ClientStutterGuardEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.ClientStutterGuardStrength);
				}
				return false;
			}
		}

		internal static bool AdaptiveOwnershipEnabled
		{
			get
			{
				if (ModConfig.Enabled.Value)
				{
					return IsPositive(ModConfig.OwnershipIntensity);
				}
				return false;
			}
		}

		internal static bool PeerQualityEnabled => AdaptiveOwnershipEnabled;

		internal static bool OwnerHintsEnabled => AdaptiveOwnershipEnabled;

		internal static float SendInterval => Map(ModConfig.SchedulerThroughput, 0.1f, 0.05f, 0.02f);

		internal static float MinSendInterval => Map(ModConfig.SchedulerThroughput, 0.06f, 0.03f, 0.015f);

		internal static float MaxSendInterval => Map(ModConfig.SchedulerThroughput, 0.2f, 0.1f, 0.045f);

		internal static int BasePeersPerTick => MapInt(ModConfig.SchedulerThroughput, 1, 4, 12);

		internal static int MaxPeersPerTick => MapInt(ModConfig.SchedulerThroughput, 4, 12, 32);

		internal static int ZdoQueueLimitBytes => MapInt(ModConfig.SchedulerThroughput, 10240, 65536, 196608);

		internal static int ZdoQueueMinPackageBytes => MapInt(ModConfig.SchedulerThroughput, 2048, 2048, 768);

		internal static int PeerQueueSoftLimitBytes => MapInt(ModConfig.SchedulerThroughput, 1572864, 524288, 196608);

		internal static int PeerQueueHardLimitBytes => MapInt(ModConfig.SchedulerThroughput, 6291456, 2097152, 786432);

		internal static float LaggingPeerMaxSkipSeconds => Map(ModConfig.SchedulerThroughput, 3f, 1f, 0.2f);

		internal static float PayloadVec3CullSize => Map(ModConfig.PayloadReducerStrength, 0.005f, 0.04f, 0.1f);

		internal static float PayloadQuaternionDotThreshold => Map(ModConfig.PayloadReducerStrength, 0.9998f, 0.995f, 0.99f);

		internal static float PayloadForceRefreshSeconds => Map(ModConfig.PayloadReducerStrength, 0.15f, 1f, 2f);

		internal static int CompressionThresholdBytes => MapInt(ModConfig.CompressionAggression, 8192, 1024, 256);

		internal static float CompressionMinUsefulRatio => Map(ModConfig.CompressionAggression, 0.7f, 0.9f, 0.97f);

		internal static float RpcAoiVisualRadius => Map(ModConfig.RpcAoiAggression, 640f, 192f, 64f);

		internal static float ClientStutterInitialSyncWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 2f, 10f, 24f);

		internal static float ClientStutterTeleportWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 1f, 5f, 14f);

		internal static float ClientStutterFullSnapshotWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.25f, 1.5f, 5f);

		internal static float ClientStutterCombatWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.35f, 2f, 6f);

		internal static float ClientStutterShipWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.35f, 2f, 6f);

		internal static float ClientStutterMaxDelaySeconds => Map(ModConfig.ClientStutterGuardStrength, 6f, 30f, 60f);

		internal static int ClientStutterMemoryPressureThresholdPercent => MapInt(ModConfig.ClientStutterGuardStrength, 55, 75, 90);

		internal static int ClientStutterMinimumFreeMemoryMB => MapInt(ModConfig.ClientStutterGuardStrength, 6144, 2048, 1024);

		internal static float ClientStutterIdleCleanupPollSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.25f, 1f, 4f);

		internal static float PeerPingEmaHalfLifeSeconds => Map(ModConfig.OwnershipIntensity, 6f, 2.5f, 0.75f);

		internal static int PeerPingSampleWindow => MapInt(ModConfig.OwnershipIntensity, 120, 60, 20);

		internal static float PeerQualityMeanWeight => Map(ModConfig.OwnershipIntensity, 0.35f, 0f, 0f);

		internal static float PeerQualityStdDevWeight => Map(ModConfig.OwnershipIntensity, 0.1f, 0.25f, 0.7f);

		internal static float PeerQualityJitterWeight => Map(ModConfig.OwnershipIntensity, 0.2f, 0.5f, 1.2f);

		internal static float PeerQualityEmaWeight => 1f;

		internal static float MaxCandidatePingMs => Map(ModConfig.OwnershipIntensity, 320f, 220f, 140f);

		internal static float MaxCandidateJitterMs => Map(ModConfig.OwnershipIntensity, 180f, 100f, 45f);

		internal static int OwnershipScanBudget => MapInt(ModConfig.OwnershipIntensity, 24, 96, 192);

		internal static int OwnershipScanStride => MapInt(ModConfig.OwnershipIntensity, 10, 4, 2);

		internal static float OwnershipScanIntervalSeconds => Map(ModConfig.OwnershipIntensity, 3f, 1f, 0.5f);

		internal static float OwnershipCandidateRadius => Map(ModConfig.OwnershipIntensity, 80f, 160f, 280f);

		internal static float OwnerHintCandidateRadius => Map(ModConfig.OwnershipIntensity, 128f, 256f, 512f);

		internal static float OwnershipDistanceScoreWeight => Map(ModConfig.OwnershipIntensity, 0.4f, 0.2f, 0.06f);

		internal static float ServerFallbackPenaltyMs => Map(ModConfig.OwnershipIntensity, 450f, 650f, 1000f);

		internal static float OwnerHintScoreBonusMs => Map(ModConfig.OwnershipIntensity, 20f, 90f, 240f);

		internal static float OwnerHintLifetimeSeconds => Map(ModConfig.OwnershipIntensity, 2f, 8f, 20f);

		private static bool IsPositive(ConfigEntry<int> entry)
		{
			return Clamp(entry?.Value ?? 0, 0, 100) > 0;
		}

		private static float Strength(ConfigEntry<int> entry)
		{
			return (float)Clamp(entry?.Value ?? 50, 0, 100) / 100f;
		}

		private static float Map(ConfigEntry<int> entry, float safe, float current, float aggressive)
		{
			float num = Strength(entry);
			if (!(num <= 0.5f))
			{
				return Lerp(current, aggressive, (num - 0.5f) * 2f);
			}
			return Lerp(safe, current, num * 2f);
		}

		private static int MapInt(ConfigEntry<int> entry, int safe, int current, int aggressive)
		{
			return (int)Math.Round(Map(entry, safe, current, aggressive));
		}

		private static float Clamp(float value, float min, float max)
		{
			if (float.IsNaN(value) || float.IsInfinity(value))
			{
				return min;
			}
			return Math.Max(min, Math.Min(max, value));
		}

		private static int Clamp(int value, int min, int max)
		{
			return Math.Max(min, Math.Min(max, value));
		}

		private static float Lerp(float a, float b, float t)
		{
			return a + (b - a) * Math.Max(0f, Math.Min(1f, t));
		}
	}
	internal static class ModConfig
	{
		private const string GeneralSection = "General";

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> LockServerConfig;

		internal static ConfigEntry<bool> DebugLogging;

		internal static ConfigEntry<int> SchedulerThroughput;

		internal static ConfigEntry<int> PayloadReducerStrength;

		internal static ConfigEntry<int> OwnershipIntensity;

		internal static ConfigEntry<int> CompressionAggression;

		internal static ConfigEntry<int> RpcAoiAggression;

		internal static ConfigEntry<int> ClientStutterGuardStrength;

		internal static void Bind(ConfigFile config)
		{
			Enabled = ConfigSyncManager.Bind(config, "General", "Enabled", value: true, Description("Master switch.", 100), ConfigSyncScope.ServerSynced);
			LockServerConfig = ConfigSyncManager.LockServerConfig;
			DebugLogging = ConfigSyncManager.Bind(config, "General", "DebugLogging", value: false, Description("Diagnostic log output. Enable temporarily to inspect network queues, scheduler stalls, compression cost, and ownership scan cost; keep this off during normal live play.", 80), ConfigSyncScope.ClientLocal);
			SchedulerThroughput = ConfigSyncManager.Bind(config, "General", "SchedulerThroughput", 35, FeatureSliderDescription("0 disables the adaptive scheduler. 1 keeps package caps close to vanilla while using the gentlest adaptive scheduler; 35 is recommended; 50 is balanced; 100 favors lower latency, higher ZDO throughput, and faster lagging-peer backfill. Steam send-rate is fixed internally at 36 MB/s while SkadiNet is enabled.", 70), ConfigSyncScope.ServerSynced);
			PayloadReducerStrength = ConfigSyncManager.Bind(config, "General", "PayloadReducerStrength", 30, FeatureSliderDescription("0 disables the payload reducer. 1 favors sync fidelity; 50 is balanced; 100 applies stronger Vector3/Quaternion micro-update reduction.", 60), ConfigSyncScope.ServerSynced);
			CompressionAggression = ConfigSyncManager.Bind(config, "General", "CompressionAggression", 50, FeatureSliderDescription("0 disables negotiated package compression. 1 compresses only large/high-value packets; 50 is balanced; 100 considers smaller packets and smaller savings.", 50), ConfigSyncScope.ServerSynced);
			OwnershipIntensity = ConfigSyncManager.Bind(config, "General", "OwnershipIntensity", 45, FeatureSliderDescription("0 disables Profile A adaptive ownership, peer-quality gates, and combat owner hints. 1 is very conservative with low CPU, narrow candidate reach, weak hints, and forgiving peer quality; 45 is recommended; 50 is balanced; 100 scans farther/faster within fixed ownership-switch safety guards, uses stronger hints, and rejects poor ping/jitter candidates more aggressively.", 40), ConfigSyncScope.ServerSynced);
			ClientStutterGuardStrength = ConfigSyncManager.Bind(config, "General", "ClientStutterGuardStrength", 50, FeatureSliderDescription("0 disables the client stutter guard. 50 is the balanced default. 1 runs cleanup sooner under pressure; 100 protects longer against cleanup stutter.", 30), ConfigSyncScope.ClientLocal);
			RpcAoiAggression = ConfigSyncManager.Bind(config, "General", "RpcAoiAggression", 35, FeatureSliderDescription("0 disables RPC AoI. 35 is the conservative default. 1 keeps a larger radius for safe visual RPCs; 50 is balanced; 100 routes eligible visual RPCs to smaller local areas. Unknown, unresolved, global, animation, noise, and state-critical RPCs always use vanilla routing.", 20), ConfigSyncScope.ServerSynced);
		}

		private static ConfigDescription Description(string description, int order)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			return new ConfigDescription(description, (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = order
				}
			});
		}

		private static ConfigDescription FeatureSliderDescription(string description, int order)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			return new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = order
				}
			});
		}
	}
	internal enum ClientCriticalWindow
	{
		InitialSync,
		Teleport,
		FullSnapshotBurst,
		Combat,
		ShipTravel
	}
	internal struct MemoryPressureSnapshot
	{
		public bool Known;

		public ulong TotalMB;

		public ulong AvailableMB;

		public int LoadPercent;

		public override string ToString()
		{
			if (!Known)
			{
				return "unknown";
			}
			return $"load={LoadPercent}% available={AvailableMB}MB total={TotalMB}MB";
		}
	}
	internal static class ClientStutterGuard
	{
		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
		private struct MEMORYSTATUSEX
		{
			public uint dwLength;

			public uint dwMemoryLoad;

			public ulong ullTotalPhys;

			public ulong ullAvailPhys;

			public ulong ullTotalPageFile;

			public ulong ullAvailPageFile;

			public ulong ullTotalVirtual;

			public ulong ullAvailVirtual;

			public ulong ullAvailExtendedVirtual;
		}

		private static readonly Dictionary<ClientCriticalWindow, double> CriticalUntil = new Dictionary<ClientCriticalWindow, double>();

		private static Plugin _plugin;

		private static Coroutine _cleanupCoroutine;

		private static bool _pendingGc;

		private static double _firstPendingSince;

		private static bool _runningCleanup;

		internal static bool IsActive
		{
			get
			{
				if (!EffectiveConfig.ClientStutterGuardEnabled)
				{
					return false;
				}
				if (IsDedicatedLike())
				{
					return false;
				}
				return true;
			}
		}

		internal static void Initialize(Plugin plugin)
		{
			_plugin = plugin;
			CriticalUntil.Clear();
			_pendingGc = false;
			_firstPendingSince = 0.0;
			_runningCleanup = false;
			EnsureCleanupScheduler();
		}

		internal static void Shutdown()
		{
			try
			{
				if (_cleanupCoroutine != null && (Object)(object)_plugin != (Object)null)
				{
					((MonoBehaviour)_plugin).StopCoroutine(_cleanupCoroutine);
				}
			}
			catch
			{
			}
			_cleanupCoroutine = null;
			_plugin = null;
		}

		private static bool IsDedicatedLike()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Invalid comparison between Unknown and I4
			try
			{
				if (NetReflection.IsDedicatedServer())
				{
					return true;
				}
			}
			catch
			{
			}
			try
			{
				if (Application.isBatchMode)
				{
					return true;
				}
				if ((int)SystemInfo.graphicsDeviceType == 4)
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		internal static void MarkCriticalWindow(ClientCriticalWindow window, float seconds)
		{
			if (IsActive && !(seconds <= 0f))
			{
				double num = Time.realtimeSinceStartupAsDouble + (double)seconds;
				if (!CriticalUntil.TryGetValue(window, out var value) || value < num)
				{
					CriticalUntil[window] = num;
				}
			}
		}

		internal static void MarkInitialSyncWindow()
		{
			MarkCriticalWindow(ClientCriticalWindow.InitialSync, Math.Max(0.1f, EffectiveConfig.ClientStutterInitialSyncWindowSeconds));
		}

		internal static void MarkTeleportWindow()
		{
			MarkCriticalWindow(ClientCriticalWindow.Teleport, Math.Max(0.1f, EffectiveConfig.ClientStutterTeleportWindowSeconds));
		}

		internal static void MarkFullSnapshotBurstWindow()
		{
			MarkCriticalWindow(ClientCriticalWindow.FullSnapshotBurst, Math.Max(0.1f, EffectiveConfig.ClientStutterFullSnapshotWindowSeconds));
		}

		internal static void MarkCombatWindow()
		{
			MarkCriticalWindow(ClientCriticalWindow.Combat, Math.Max(0.1f, EffectiveConfig.ClientStutterCombatWindowSeconds));
		}

		internal static void MarkShipTravelWindow()
		{
			MarkCriticalWindow(ClientCriticalWindow.ShipTravel, Math.Max(0.1f, EffectiveConfig.ClientStutterShipWindowSeconds));
		}

		internal static bool TryHandleGcCollect()
		{
			if (!IsActive || _runningCleanup)
			{
				return true;
			}
			if (ShouldDelayCleanup(out var reason))
			{
				RequestPending(reason);
				return false;
			}
			_pendingGc = false;
			_firstPendingSince = 0.0;
			return true;
		}

		private static void RequestPending(string reason)
		{
			_pendingGc = true;
			if (_firstPendingSince <= 0.0)
			{
				_firstPendingSince = Time.realtimeSinceStartupAsDouble;
			}
			EnsureCleanupScheduler();
			if (ModConfig.DebugLogging.Value)
			{
				Plugin.Log.LogDebug((object)("ClientStutterGuard: deferred GarbageCollect (" + reason + ")."));
			}
		}

		private static void EnsureCleanupScheduler()
		{
			if (_cleanupCoroutine == null && !((Object)(object)_plugin == (Object)null) && IsActive)
			{
				_cleanupCoroutine = ((MonoBehaviour)_plugin).StartCoroutine(CleanupScheduler());
			}
		}

		private static IEnumerator CleanupScheduler()
		{
			while (true)
			{
				float num = Math.Max(0.25f, EffectiveConfig.ClientStutterIdleCleanupPollSeconds);
				yield return (object)new WaitForSeconds(num);
				if (IsActive)
				{
					TryRunPendingCleanup();
				}
			}
		}

		private static bool TryRunPendingCleanup()
		{
			if (!_pendingGc)
			{
				return false;
			}
			if (!IsActive)
			{
				return false;
			}
			if (_runningCleanup)
			{
				return false;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			bool flag = _firstPendingSince > 0.0 && realtimeSinceStartupAsDouble - _firstPendingSince >= (double)Math.Max(1f, EffectiveConfig.ClientStutterMaxDelaySeconds);
			if (!flag && TryGetCriticalReason(out var reason))
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogDebug((object)("ClientStutterGuard: pending cleanup waits for " + reason + "."));
				}
				return false;
			}
			if (!flag && IsMemoryPlentiful(out var snapshot))
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogDebug((object)$"ClientStutterGuard: pending cleanup waits; memory plentiful ({snapshot}).");
				}
				return false;
			}
			_pendingGc = false;
			_firstPendingSince = 0.0;
			_runningCleanup = true;
			try
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogDebug((object)"ClientStutterGuard: running coalesced GC.Collect.");
				}
				GC.Collect();
			}
			finally
			{
				_runningCleanup = false;
			}
			return true;
		}

		private static bool ShouldDelayCleanup(out string reason)
		{
			reason = "safe window";
			if (!IsActive)
			{
				return false;
			}
			if (_firstPendingSince > 0.0 && Time.realtimeSinceStartupAsDouble - _firstPendingSince >= (double)Math.Max(1f, EffectiveConfig.ClientStutterMaxDelaySeconds))
			{
				reason = "max delay exceeded";
				return false;
			}
			if (TryGetCriticalReason(out var reason2))
			{
				if (IsMemoryPressure(out var snapshot))
				{
					reason = $"memory pressure overrides critical window ({snapshot})";
					return false;
				}
				reason = reason2;
				return true;
			}
			if (IsMemoryPlentiful(out var snapshot2))
			{
				reason = $"memory plentiful ({snapshot2})";
				return true;
			}
			return false;
		}

		private static bool TryGetCriticalReason(out string reason)
		{
			reason = null;
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			double num = 0.0;
			ClientCriticalWindow clientCriticalWindow = ClientCriticalWindow.InitialSync;
			foreach (KeyValuePair<ClientCriticalWindow, double> item in CriticalUntil)
			{
				if (item.Value > realtimeSinceStartupAsDouble && item.Value > num)
				{
					clientCriticalWindow = item.Key;
					num = item.Value;
				}
			}
			if (num <= realtimeSinceStartupAsDouble)
			{
				return false;
			}
			reason = $"{clientCriticalWindow} for {num - realtimeSinceStartupAsDouble:F1}s";
			return true;
		}

		private static bool IsMemoryPlentiful(out MemoryPressureSnapshot snapshot)
		{
			snapshot = GetMemorySnapshot();
			if (!snapshot.Known)
			{
				return false;
			}
			if (snapshot.LoadPercent < Math.Max(1, EffectiveConfig.ClientStutterMemoryPressureThresholdPercent))
			{
				return snapshot.AvailableMB >= (ulong)Math.Max(0, EffectiveConfig.ClientStutterMinimumFreeMemoryMB);
			}
			return false;
		}

		private static bool IsMemoryPressure(out MemoryPressureSnapshot snapshot)
		{
			snapshot = GetMemorySnapshot();
			if (!snapshot.Known)
			{
				return false;
			}
			if (snapshot.LoadPercent < Math.Max(1, EffectiveConfig.ClientStutterMemoryPressureThresholdPercent))
			{
				return snapshot.AvailableMB < (ulong)Math.Max(0, EffectiveConfig.ClientStutterMinimumFreeMemoryMB);
			}
			return true;
		}

		private static MemoryPressureSnapshot GetMemorySnapshot()
		{
			if (TryGetWindowsMemory(out var snapshot))
			{
				return snapshot;
			}
			if (TryGetProcMemInfo(out var snapshot2))
			{
				return snapshot2;
			}
			return new MemoryPressureSnapshot
			{
				Known = false
			};
		}

		private static bool TryGetWindowsMemory(out MemoryPressureSnapshot snapshot)
		{
			snapshot = default(MemoryPressureSnapshot);
			try
			{
				MEMORYSTATUSEX lpBuffer = new MEMORYSTATUSEX
				{
					dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX))
				};
				if (!GlobalMemoryStatusEx(ref lpBuffer))
				{
					return false;
				}
				snapshot.Known = true;
				snapshot.TotalMB = lpBuffer.ullTotalPhys / 1024 / 1024;
				snapshot.AvailableMB = lpBuffer.ullAvailPhys / 1024 / 1024;
				snapshot.LoadPercent = (int)lpBuffer.dwMemoryLoad;
				return snapshot.TotalMB != 0;
			}
			catch
			{
				return false;
			}
		}

		private static bool TryGetProcMemInfo(out MemoryPressureSnapshot snapshot)
		{
			snapshot = default(MemoryPressureSnapshot);
			try
			{
				if (!File.Exists("/proc/meminfo"))
				{
					return false;
				}
				ulong num = 0uL;
				ulong num2 = 0uL;
				string[] array = File.ReadAllLines("/proc/meminfo");
				foreach (string text in array)
				{
					if (text.StartsWith("MemTotal:", StringComparison.Ordinal))
					{
						num = ParseKb(text);
					}
					else if (text.StartsWith("MemAvailable:", StringComparison.Ordinal))
					{
						num2 = ParseKb(text);
					}
				}
				if (num == 0L || num2 == 0L)
				{
					return false;
				}
				snapshot.Known = true;
				snapshot.TotalMB = num / 1024;
				snapshot.AvailableMB = num2 / 1024;
				snapshot.LoadPercent = (int)Math.Round(100.0 * (1.0 - (double)num2 / (double)num));
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static ulong ParseKb(string line)
		{
			string[] array = line.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 1; i < array.Length; i++)
			{
				if (ulong.TryParse(array[i], out var result))
				{
					return result;
				}
			}
			return 0uL;
		}

		[DllImport("kernel32.dll", SetLastError = true)]
		private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
	}
	[HarmonyPatch]
	internal static class ClientStutterGuardGcCollectPatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			MethodInfo[] methods = typeof(GC).GetMethods(BindingFlags.Static | BindingFlags.Public);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name == "Collect")
				{
					yield return methodInfo;
				}
			}
		}

		private static bool Prefix()
		{
			return ClientStutterGuard.TryHandleGcCollect();
		}
	}
	[HarmonyPatch]
	internal static class ClientStutterGuardZNetConnectionPatch
	{
		private static MethodBase TargetMethod()
		{
			Type type = ReflectionCache.ZNetType ?? AccessTools.TypeByName("ZNet");
			Type type2 = ReflectionCache.ZNetPeerType ?? AccessTools.TypeByName("ZNetPeer");
			return AccessTools.Method(type, "OnNewConnection", (!(type2 != null)) ? null : new Type[1] { type2 }, (Type[])null) ?? AccessTools.Method(type, "OnNewConnection", (Type[])null, (Type[])null);
		}

		private static void Postfix()
		{
			ClientStutterGuard.MarkInitialSyncWindow();
		}
	}
	[HarmonyPatch]
	internal static class ClientStutterGuardZdoDataPatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type type = ReflectionCache.ZDOManType ?? AccessTools.TypeByName("ZDOMan");
			if (type == null)
			{
				yield break;
			}
			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name == "RPC_ZDOData")
				{
					yield return methodInfo;
				}
			}
		}

		private static void Prefix(object[] __args)
		{
			try
			{
				if (NetReflection.IsServer() || __args == null || ReflectionCache.ZPackageType == null)
				{
					return;
				}
				foreach (object obj in __args)
				{
					if (obj != null && ReflectionCache.ZPackageType.IsInstanceOfType(obj) && ZPackageTools.Size(obj) >= 32768)
					{
						ClientStutterGuard.MarkFullSnapshotBurstWindow();
						break;
					}
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch]
	internal static class ClientStutterGuardLoadingScreenPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(AccessTools.TypeByName("ZNetScene"), "InLoadingScreen", (Type[])null, (Type[])null);
		}

		private static void Postfix(bool __result)
		{
			if (__result)
			{
				ClientStutterGuard.MarkTeleportWindow();
			}
		}
	}
	[HarmonyPatch]
	internal static class ClientStutterGuardShipTravelPatch
	{
		private static FieldInfo _bodyField;

		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("Ship");
			_bodyField = ReflectionCache.SilentField(type, "m_body");
			return AccessTools.Method(type, "CustomFixedUpdate", (Type[])null, (Type[])null) ?? AccessTools.Method(type, "FixedUpdate", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			if (__instance == null)
			{
				return;
			}
			try
			{
				object? obj = _bodyField?.GetValue(__instance);
				Rigidbody val = (Rigidbody)((obj is Rigidbody) ? obj : null);
				if ((Object)(object)val != (Object)null)
				{
					Vector3 linearVelocity = val.linearVelocity;
					if (((Vector3)(ref linearVelocity)).sqrMagnitude > 4f)
					{
						ClientStutterGuard.MarkShipTravelWindow();
					}
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch]
	internal static class MonsterAISetTargetOwnershipPatch
	{
		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("MonsterAI");
			Type type2 = AccessTools.TypeByName("Character");
			return AccessTools.Method(type, "SetTarget", (!(type2 != null)) ? null : new Type[1] { type2 }, (Type[])null) ?? AccessTools.Method("MonsterAI:SetTarget", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance, object[] __args)
		{
			if (__args != null && __args.Length != 0)
			{
				ClientStutterGuard.MarkCombatWindow();
				OwnershipManager.TryTransferCombatOwnership(__instance, __args[0]);
			}
		}
	}
	internal enum OwnershipCandidateReason
	{
		Generic,
		CombatTarget,
		DisconnectedOwner,
		LongUnownedPersistent
	}
	internal sealed class OwnerState
	{
		public double LastOwnerChangeTime;

		public double LastCombatOwnerChangeTime;

		public double LastSeenUnownedTime;

		public double LastTouchedTime;

		public long CombatTargetUid;

		public double CombatTargetHintTime;
	}
	internal sealed class OwnerCandidate
	{
		public long Uid;

		public float Distance;

		public float Quality;

		public float Score;
	}
	internal static class OwnershipManager
	{
		private const int MaxOwnerStates = 50000;

		private const double OwnerStateTtlSeconds = 600.0;

		private const double OwnerStatePruneIntervalSeconds = 30.0;

		private const bool AllowShipOwnership = false;

		private const bool AllowHealthyOwnerChallenge = false;

		private const bool AllowServerFallbackForPersistentRecovery = true;

		private static readonly Dictionary<ZdoIdKey, OwnerState> ByZdoId = new Dictionary<ZdoIdKey, OwnerState>();

		private static double _nextProfileAScan;

		private static double _nextOwnerStatePrune;

		private static int _sectorCursor;

		private static int _bucketCursor;

		private static int _scanPhase;

		private static bool _loggedUnsupportedSectorShape;

		private static int _lastScanPeerCount;

		internal static bool ProfileAEnabled
		{
			get
			{
				if (!EffectiveConfig.AdaptiveOwnershipEnabled || !ZdoKeyPolicy.Ready || !NetReflection.IsServer())
				{
					return false;
				}
				return true;
			}
		}

		internal static void Initialize()
		{
			ByZdoId.Clear();
			_nextProfileAScan = 0.0;
			_nextOwnerStatePrune = 0.0;
			_sectorCursor = 0;
			_bucketCursor = 0;
			_scanPhase = 0;
			_loggedUnsupportedSectorShape = false;
			_lastScanPeerCount = 0;
		}

		internal static bool TryTransferCombatOwnership(object monsterAI, object target)
		{
			if (!ProfileAEnabled || !EffectiveConfig.OwnerHintsEnabled)
			{
				return false;
			}
			if (!GameplayReflection.LooksLikePlayer(target))
			{
				return false;
			}
			if (!GameplayReflection.TryGetPlayerId(target, out var id) || id == 0L)
			{
				return false;
			}
			object zdoFromCharacterLike = GameplayReflection.GetZdoFromCharacterLike(monsterAI);
			if (zdoFromCharacterLike == null)
			{
				return false;
			}
			OwnerState ownerState = GetOwnerState(zdoFromCharacterLike);
			ownerState.CombatTargetUid = id;
			ownerState.CombatTargetHintTime = Time.realtimeSinceStartupAsDouble;
			return TryMaybeImproveOwner(zdoFromCharacterLike, OwnershipCandidateReason.CombatTarget);
		}

		internal static void ClearPeer(long uid)
		{
			if (uid == 0L)
			{
				return;
			}
			foreach (OwnerState value in ByZdoId.Values)
			{
				if (value.CombatTargetUid == uid)
				{
					value.CombatTargetUid = 0L;
					value.CombatTargetHintTime = 0.0;
				}
			}
		}

		internal static void TickLightweight(object zdoMan)
		{
			if (!ProfileAEnabled)
			{
				return;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			if (!(realtimeSinceStartupAsDouble < _nextProfileAScan))
			{
				_nextProfileAScan = realtimeSinceStartupAsDouble + (double)Math.Max(0.1f, EffectiveConfig.OwnershipScanIntervalSeconds);
				if (!HasAnyZdoPeer(zdoMan, out var peerCount))
				{
					_lastScanPeerCount = 0;
					return;
				}
				PruneOwnerStatesIfDue(realtimeSinceStartupAsDouble);
				_lastScanPeerCount = peerCount;
				TryProfileAScan(zdoMan);
			}
		}

		private static bool HasAnyZdoPeer(object zdoMan, out int peerCount)
		{
			peerCount = 0;
			try
			{
				if (zdoMan == null || ReflectionCache.ZDOManPeersField == null)
				{
					return false;
				}
				object value = ReflectionCache.ZDOManPeersField.GetValue(zdoMan);
				if (value is ICollection collection)
				{
					peerCount = collection.Count;
					return peerCount > 0;
				}
				if (value is IEnumerable enumerable)
				{
					{
						IEnumerator enumerator = enumerable.GetEnumerator();
						try
						{
							if (enumerator.MoveNext())
							{
								_ = enumerator.Current;
								peerCount++;
								return true;
							}
						}
						finally
						{
							IDisposable disposable = enumerator as IDisposable;
							if (disposable != null)
							{
								disposable.Dispose();
							}
						}
					}
				}
			}
			catch
			{
			}
			return false;
		}

		private static void TryProfileAScan(object zdoMan)
		{
			try
			{
				double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
				object obj = ReflectionCache.ZDOObjectsBySectorField?.GetValue(zdoMan);
				if (obj == null)
				{
					return;
				}
				int visited = 0;
				int acted = 0;
				int num = Math.Max(1, EffectiveConfig.OwnershipScanBudget);
				ScanSectorBuckets(obj, num, ref visited, ref acted);
				double num2 = Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble;
				if (ModConfig.DebugLogging.Value && (acted > 0 || visited > 0))
				{
					bool flag = num2 >= 0.008;
					if (acted > 0 || flag)
					{
						Plugin.Log.LogInfo((object)$"Adaptive ownership scan: elapsed={num2 * 1000.0:F2}ms slow={flag} visited={visited}/{num}, ownerChanges={acted}, zdoPeers={_lastScanPeerCount}, sectorCursor={_sectorCursor}, stride={EffectiveConfig.OwnershipScanStride}");
					}
				}
			}
			catch (Exception ex)
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("Adaptive ownership scan failed: " + ex.Message));
				}
			}
		}

		private static void ScanSectorBuckets(object sectors, int budget, ref int visited, ref int acted)
		{
			if (!(sectors is IList list))
			{
				if (!_loggedUnsupportedSectorShape)
				{
					_loggedUnsupportedSectorShape = true;
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("Adaptive ownership scan disabled: unsupported m_objectsBySector type " + (sectors?.GetType().FullName ?? "null") + "."));
					}
				}
			}
			else
			{
				if (list == null || list.Count <= 0)
				{
					return;
				}
				if (_sectorCursor < 0 || _sectorCursor >= list.Count)
				{
					_sectorCursor = 0;
				}
				int num = Math.Max(1, EffectiveConfig.OwnershipScanStride);
				_scanPhase = Math.Max(0, _scanPhase % num);
				int num2 = 0;
				while (num2 < list.Count && visited < budget)
				{
					if (list[_sectorCursor] is IList { Count: not 0 } list2)
					{
						if (_bucketCursor < 0 || _bucketCursor >= list2.Count)
						{
							_bucketCursor = 0;
						}
						while (_bucketCursor < list2.Count && visited < budget)
						{
							int num3 = _bucketCursor++;
							object obj = list2[num3];
							if (obj != null && (num3 + _scanPhase) % num == 0)
							{
								visited++;
								if (TryMaybeImproveOwner(obj, OwnershipCandidateReason.Generic))
								{
									acted++;
								}
							}
						}
						if (_bucketCursor >= list2.Count)
						{
							_bucketCursor = 0;
							AdvanceSector(list.Count, num);
							num2++;
						}
					}
					else
					{
						_bucketCursor = 0;
						AdvanceSector(list.Count, num);
						num2++;
					}
				}
			}
		}

		private static void AdvanceSector(int sectorCount, int stride)
		{
			_sectorCursor++;
			if (_sectorCursor >= sectorCount)
			{
				_sectorCursor = 0;
				_scanPhase = (_scanPhase + 1) % Math.Max(1, stride);
			}
		}

		private static bool TryMaybeImproveOwner(object zdo, OwnershipCandidateReason reason)
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: 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)
			if (zdo == null || ReflectionCache.ZDOGetPositionMethod == null)
			{
				return false;
			}
			if (!ZdoReflection.TryGetOwner(zdo, out var owner))
			{
				owner = 0L;
			}
			bool persistent;
			bool flag = ZdoReflection.TryGetPersistent(zdo, out persistent) && persistent;
			OwnerState ownerState = GetOwnerState(zdo);
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			bool flag2 = owner != 0L && IsUidCurrentlyConnected(owner);
			long uid;
			bool flag3 = owner != 0L && ZdoReflection.TryGetServerSessionId(out uid) && owner == uid;
			if (owner == 0L)
			{
				if (ownerState.LastSeenUnownedTime <= 0.0)
				{
					ownerState.LastSeenUnownedTime = realtimeSinceStartupAsDouble;
				}
				if (flag && realtimeSinceStartupAsDouble - ownerState.LastSeenUnownedTime >= 2.0)
				{
					reason = OwnershipCandidateReason.LongUnownedPersistent;
				}
			}
			else
			{
				ownerState.LastSeenUnownedTime = 0.0;
			}
			if (!flag2 && owner != 0L && !flag3)
			{
				reason = OwnershipCandidateReason.DisconnectedOwner;
			}
			if (flag && reason != OwnershipCandidateReason.LongUnownedPersistent && reason != OwnershipCandidateReason.DisconnectedOwner)
			{
				return false;
			}
			if (reason == OwnershipCandidateReason.Generic && flag2)
			{
				return false;
			}
			ZdoKeyPolicy.ClassifyOwnership(zdo, out var playerLike, out var shipLike);
			if (playerLike)
			{
				return false;
			}
			if (shipLike && reason != OwnershipCandidateReason.DisconnectedOwner)
			{
				return false;
			}
			Vector3 position = ZdoReflection.GetPosition(zdo, Vector3.zero);
			OwnerCandidate ownerCandidate = FindBestCandidate(zdo, position, ownerState, reason);
			if (ownerCandidate == null)
			{
				if (reason == OwnershipCandidateReason.LongUnownedPersistent)
				{
					return RecoverToServerOwner(zdo, ownerState, owner, reason);
				}
				return false;
			}
			if (ownerCandidate.Uid == owner)
			{
				return false;
			}
			float num = ComputeCurrentOwnerScore(owner, position, ownerState, reason, flag2, flag3);
			if (!IsCandidateBetter(ownerCandidate.Score, num, reason))
			{
				return false;
			}
			float num2 = ((reason == OwnershipCandidateReason.CombatTarget) ? 5f : 3f);
			if (shipLike)
			{
				num2 = Math.Max(num2, 8f);
			}
			if (realtimeSinceStartupAsDouble - ownerState.LastOwnerChangeTime < (double)num2)
			{
				return false;
			}
			if (reason == OwnershipCandidateReason.CombatTarget && realtimeSinceStartupAsDouble - ownerState.LastCombatOwnerChangeTime < 5.0)
			{
				return false;
			}
			if (!ZdoReflection.TrySetOwner(zdo, ownerCandidate.Uid))
			{
				return false;
			}
			ownerState.LastOwnerChangeTime = realtimeSinceStartupAsDouble;
			if (reason == OwnershipCandidateReason.CombatTarget)
			{
				ownerState.LastCombatOwnerChangeTime = realtimeSinceStartupAsDouble;
			}
			ZdoReflection.ForceSend(zdo);
			if (ModConfig.DebugLogging.Value)
			{
				Plugin.Log.LogDebug((object)$"ProfileA owner {reason}: {owner}->{ownerCandidate.Uid}, best={ownerCandidate.Score:F1}, current={num:F1}, q={ownerCandidate.Quality:F1}, dist={ownerCandidate.Distance:F1}");
			}
			return true;
		}

		private static OwnerCandidate FindBestCandidate(object zdo, Vector3 zdoPosition, OwnerState state, OwnershipCandidateReason reason)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: 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)
			OwnerCandidate ownerCandidate = null;
			float num = ((reason == OwnershipCandidateReason.CombatTarget) ? Math.Max(EffectiveConfig.OwnershipCandidateRadius, EffectiveConfig.OwnerHintCandidateRadius) : EffectiveConfig.OwnershipCandidateRadius);
			foreach (object item in ZdoReflection.EnumeratePeers(ZdoReflection.ZDOManInstance))
			{
				if (!NetReflection.TryGetPeerUid(item, out var uid) || uid == 0L)
				{
					continue;
				}
				Vector3 peerRefPos = NetReflection.GetPeerRefPos(item);
				float num2 = Vector3.Distance(zdoPosition, peerRefPos);
				if (num2 > num)
				{
					continue;
				}
				PeerQualityState peerQualityState = PeerQualityMeter.GetByUid(uid) ?? PeerQualityMeter.GetByPeer(item);
				if (CandidateConnectionAllowed(peerQualityState))
				{
					float num3 = ComputeCandidateScore(uid, num2, peerQualityState, state);
					if (ownerCandidate == null || num3 < ownerCandidate.Score)
					{
						ownerCandidate = new OwnerCandidate
						{
							Uid = uid,
							Distance = num2,
							Quality = (peerQualityState?.ConnectionQualityMs ?? 999f),
							Score = num3
						};
					}
				}
			}
			return ownerCandidate;
		}

		private static float ComputeCandidateScore(long uid, float distance, PeerQualityState quality, OwnerState state)
		{
			float num = quality?.ConnectionQualityMs ?? 999f;
			num += distance * Math.Max(0f, EffectiveConfig.OwnershipDistanceScoreWeight);
			if (uid == state.CombatTargetUid && IsCombatHintFresh(state))
			{
				num -= Math.Max(0f, EffectiveConfig.OwnerHintScoreBonusMs);
			}
			return num;
		}

		private static float ComputeCurrentOwnerScore(long currentOwner, Vector3 zdoPosition, OwnerState state, OwnershipCandidateReason reason, bool currentConnected, bool currentIsServer)
		{
			//IL_0039: 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)
			if (currentOwner == 0L)
			{
				return 999f;
			}
			if (!currentConnected && !currentIsServer)
			{
				return 999f;
			}
			if (currentIsServer)
			{
				return EffectiveConfig.ServerFallbackPenaltyMs;
			}
			PeerQualityState byUid = PeerQualityMeter.GetByUid(currentOwner);
			object obj = FindZdoPeerByUid(currentOwner);
			float distance = ((obj != null) ? Vector3.Distance(zdoPosition, NetReflection.GetPeerRefPos(obj)) : 0f);
			return ComputeCandidateScore(currentOwner, distance, byUid, state);
		}

		private static bool RecoverToServerOwner(object zdo, OwnerState state, long currentOwner, OwnershipCandidateReason reason)
		{
			if (!ZdoReflection.TryGetServerSessionId(out var uid) || uid == 0L)
			{
				return false;
			}
			if (!ZdoReflection.TrySetOwner(zdo, uid))
			{
				return false;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			state.LastOwnerChangeTime = realtimeSinceStartupAsDouble;
			ZdoReflection.ForceSend(zdo);
			if (ModConfig.DebugLogging.Value)
			{
				Plugin.Log.LogDebug((object)$"ProfileA recovery to server owner {reason}: {currentOwner}->{uid}");
			}
			return true;
		}

		private static bool CandidateConnectionAllowed(PeerQualityState candidate)
		{
			if (!EffectiveConfig.PeerQualityEnabled)
			{
				return true;
			}
			if (candidate == null || !candidate.HasAnySample)
			{
				return false;
			}
			if (candidate.PingEmaMs > EffectiveConfig.MaxCandidatePingMs)
			{
				return false;
			}
			if (candidate.PingJitterMs > EffectiveConfig.MaxCandidateJitterMs)
			{
				return false;
			}
			return true;
		}

		private static bool IsCandidateBetter(float candidateScore, float currentScore, OwnershipCandidateReason reason)
		{
			if (currentScore <= 0f || currentScore >= 900f)
			{
				return true;
			}
			float num = Math.Max(0f, 0.15f);
			float val = Math.Max(0f, 20f);
			if (reason == OwnershipCandidateReason.DisconnectedOwner || reason == OwnershipCandidateReason.LongUnownedPersistent)
			{
				val = Math.Min(val, 5f);
			}
			float num2 = Math.Max(val, currentScore * num);
			return currentScore - candidateScore >= num2;
		}

		private static OwnerState GetOwnerState(object zdo)
		{
			if (!ZdoReflection.TryGetIdKey(zdo, out var key))
			{
				key = ZdoIdKey.FromRuntimeObject(zdo);
			}
			if (!ByZdoId.TryGetValue(key, out var value))
			{
				value = new OwnerState();
				ByZdoId[key] = value;
			}
			value.LastTouchedTime = Time.realtimeSinceStartupAsDouble;
			return value;
		}

		private static void PruneOwnerStatesIfDue(double now)
		{
			if (now < _nextOwnerStatePrune)
			{
				return;
			}
			_nextOwnerStatePrune = now + 30.0;
			List<ZdoIdKey> list = new List<ZdoIdKey>();
			foreach (KeyValuePair<ZdoIdKey, OwnerState> item in ByZdoId)
			{
				OwnerState value = item.Value;
				if (value == null || (value.LastTouchedTime > 0.0 && now - value.LastTouchedTime >= 600.0))
				{
					list.Add(item.Key);
				}
			}
			foreach (ZdoIdKey item2 in list)
			{
				ByZdoId.Remove(item2);
			}
			TrimOwnerStatesToLimit();
		}

		private static void TrimOwnerStatesToLimit()
		{
			int num = ByZdoId.Count - 50000;
			if (num > 0)
			{
				List<KeyValuePair<ZdoIdKey, OwnerState>> list = new List<KeyValuePair<ZdoIdKey, OwnerState>>(ByZdoId);
				list.Sort((KeyValuePair<ZdoIdKey, OwnerState> left, KeyValuePair<ZdoIdKey, OwnerState> right) => (left.Value?.LastTouchedTime ?? 0.0).CompareTo(right.Value?.LastTouchedTime ?? 0.0));
				for (int num2 = 0; num2 < num; num2++)
				{
					ByZdoId.Remove(list[num2].Key);
				}
			}
		}

		private static bool IsCombatHintFresh(OwnerState state)
		{
			if (state == null || state.CombatTargetUid == 0L)
			{
				return false;
			}
			return Time.realtimeSinceStartupAsDouble - state.CombatTargetHintTime <= (double)Math.Max(0.1f, EffectiveConfig.OwnerHintLifetimeSeconds);
		}

		private static bool IsUidCurrentlyConnected(long uid)
		{
			return FindZdoPeerByUid(uid) != null;
		}

		private static object FindZdoPeerByUid(long uid)
		{
			if (uid == 0L)
			{
				return null;
			}
			foreach (object item in ZdoReflection.EnumeratePeers(ZdoReflection.ZDOManInstance))
			{
				if (NetReflection.TryGetPeerUid(item, out var uid2) && uid2 == uid)
				{
					return item;
				}
			}
			return null;
		}
	}
	internal static class CompressionDiagnostics
	{
		private const double SummaryIntervalSeconds = 10.0;

		private static int _encoded;

		private static int _decoded;

		private static long _rawEncodeBytes;

		private static long _compressedEncodeBytes;

		private static long _compressedDecodeBytes;

		private static long _rawDecodeBytes;

		private static double _encodeSeconds;

		private static double _decodeSeconds;

		private static double _maxEncodeSeconds;

		private static double _maxDecodeSeconds;

		private static double _nextSummaryTime;

		internal static void RecordEncode(int rawBytes, int compressedBytes, double seconds)
		{
			if (ModConfig.DebugLogging.Value)
			{
				_encoded++;
				_rawEncodeBytes += Math.Max(0, rawBytes);
				_compressedEncodeBytes += Math.Max(0, compressedBytes);
				_encodeSeconds += Math.Max(0.0, seconds);
				if (seconds > _maxEncodeSeconds)
				{
					_maxEncodeSeconds = seconds;
				}
				LogIfDue();
			}
		}

		internal static void RecordDecode(int compressedBytes, int rawBytes, double seconds)
		{
			if (ModConfig.DebugLogging.Value)
			{
				_decoded++;
				_compressedDecodeBytes += Math.Max(0, compressedBytes);
				_rawDecodeBytes += Math.Max(0, rawBytes);
				_decodeSeconds += Math.Max(0.0, seconds);
				if (seconds > _maxDecodeSeconds)
				{
					_maxDecodeSeconds = seconds;
				}
				LogIfDue();
			}
		}

		private static void LogIfDue()
		{
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			if (!(realtimeSinceStartupAsDouble < _nextSummaryTime))
			{
				_nextSummaryTime = realtimeSinceStartupAsDouble + 10.0;
				if (_encoded > 0 || _decoded > 0)
				{
					float num = ((_rawEncodeBytes > 0) ? ((float)_compressedEncodeBytes / (float)_rawEncodeBytes) : 0f);
					Plugin.Log.LogDebug((object)($"Compression summary {10.0:F0}s: encoded={_encoded} raw={FormatBytes(_rawEncodeBytes)} compressed={FormatBytes(_compressedEncodeBytes)} ratio={num:F2} " + $"encodeAvgMs={_encodeSeconds / (double)Math.Max(1, _encoded) * 1000.0:F2} encodeMaxMs={_maxEncodeSeconds * 1000.0:F2} " + $"decoded={_decoded} compressedIn={FormatBytes(_compressedDecodeBytes)} rawOut={FormatBytes(_rawDecodeBytes)} " + $"decodeAvgMs={_decodeSeconds / (double)Math.Max(1, _decoded) * 1000.0:F2} decodeMaxMs={_maxDecodeSeconds * 1000.0:F2}"));
				}
				_encoded = 0;
				_decoded = 0;
				_rawEncodeBytes = 0L;
				_compressedEncodeBytes = 0L;
				_compressedDecodeBytes = 0L;
				_rawDecodeBytes = 0L;
				_encodeSeconds = 0.0;
				_decodeSeconds = 0.0;
				_maxEncodeSeconds = 0.0;
				_maxDecodeSeconds = 0.0;
			}
		}

		private static string FormatBytes(long bytes)
		{
			if (bytes >= 1048576)
			{
				return $"{(float)bytes / 1048576f:F1}MB";
			}
			if (bytes >= 1024)
			{
				return $"{(float)bytes / 1024f:F1}KB";
			}
			return $"{bytes}B";
		}
	}
	[HarmonyPatch]
	internal static class ZSteamSocketSendCompressionPatch
	{
		private static MethodBase TargetMethod()
		{
			return ReflectionCache.ZSteamSocketSendMethod ?? AccessTools.Method("ZSteamSocket:Send", (Type[])null, (Type[])null);
		}

		private static void Prefix(object __instance, ref object __0)
		{
			if (!EffectiveConfig.CompressionEnabled || __instance == null || __0 == null || !FeatureNegotiation.IsCompressionActiveForSocket(__instance))
			{
				return;
			}
			try
			{
				int rawBytes = ZPackageTools.Size(__0);
				double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
				if (ZPackageTools.TryBuildCompressedPackage(__0, out var compressedPackage))
				{
					__0 = compressedPackage;
					CompressionDiagnostics.RecordEncode(rawBytes, ZPackageTools.Size(compressedPackage), Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble);
				}
			}
			catch (Exception ex)
			{
				FeatureNegotiation.RecordCompressionFailure(__instance);
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("ZSteamSocket.Send compression failed: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class ZSteamSocketRecvCompressionPatch
	{
		private static MethodBase TargetMethod()
		{
			return ReflectionCache.ZSteamSocketRecvMethod ?? AccessTools.Method("ZSteamSocket:Recv", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance, ref object __result)
		{
			if (__result == null)
			{
				return;
			}
			try
			{
				int compressedBytes = ZPackageTools.Size(__result);
				double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
				if (ZPackageTools.TryDecompressPackage(__result, out var rawPackage))
				{
					__result = rawPackage;
					CompressionDiagnostics.RecordDecode(compressedBytes, ZPackageTools.Size(rawPackage), Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble);
				}
			}
			catch (Exception ex)
			{
				FeatureNegotiation.RecordCompressionFailure(__instance);
				__result = null;
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("ZSteamSocket.Recv decompression failed: " + ex.Message));
				}
			}
		}
	}
	[Flags]
	internal enum PeerFeatureFlags
	{
		None = 0,
		Compression = 1,
		RpcAoi = 4
	}
	internal sealed class PeerFeatureState
	{
		public object Rpc;

		public object Socket;

		public long Uid;

		public bool RegisteredRpc;

		public bool HandshakeSent;

		public bool HandshakeReceived;

		public PeerFeatureFlags RemoteCapabilities;

		public int CompressionFailures;
	}
	internal static class FeatureNegotiation
	{
		internal const int ProtocolVersion = 2;

		internal const int FeatureMagic = 1179536211;

		internal const string RpcName = "SkadiNet_Features";

		private static readonly object Lock = new object();

		private static readonly Dictionary<object, PeerFeatureState> ByRpc = new Dictionary<object, PeerFeatureState>();

		private static readonly Dictionary<object, PeerFeatureState> BySocket = new Dictionary<object, PeerFeatureState>();

		private static readonly Dictionary<long, PeerFeatureState> ByUid = new Dictionary<long, PeerFeatureState>();

		internal static PeerFeatureFlags LocalCapabilities => PeerFeatureFlags.Compression | PeerFeatureFlags.RpcAoi;

		internal static void Initialize()
		{
			lock (Lock)
			{
				ByRpc.Clear();
				BySocket.Clear();
				ByUid.Clear();
			}
		}

		internal static PeerFeatureState GetOrCreateByRpc(object rpc, long uid = 0L)
		{
			if (rpc == null)
			{
				return null;
			}
			lock (Lock)
			{
				if (!ByRpc.TryGetValue(rpc, out var value))
				{
					value = new PeerFeatureState
					{
						Rpc = rpc,
						Uid = uid
					};
					ByRpc[rpc] = value;
				}
				if (uid != 0L)
				{
					value.Uid = uid;
					ByUid[uid] = value;
				}
				object socketFromRpc = NetReflection.GetSocketFromRpc(rpc);
				if (socketFromRpc != null)
				{
					value.Socket = socketFromRpc;
					BySocket[socketFromRpc] = value;
				}
				return value;
			}
		}

		internal static PeerFeatureState GetBySocket(object socket)
		{
			if (socket == null)
			{
				return null;
			}
			lock (Lock)
			{
				BySocket.TryGetValue(socket, out var value);
				return value;
			}
		}

		internal static PeerFeatureState GetByUid(long uid)
		{
			lock (Lock)
			{
				ByUid.TryGetValue(uid, out var value);
				return value;
			}
		}

		internal static void ClearPeer(object peerOrRpc, long uid)
		{
			object obj = NetReflection.GetPeerRpc(peerOrRpc);
			if (obj == null && peerOrRpc != null && ReflectionCache.ZRpcType != null && ReflectionCache.ZRpcType.IsInstanceOfType(peerOrRpc))
			{
				obj = peerOrRpc;
			}
			object socketFromRpc = NetReflection.GetSocketFromRpc(obj);
			lock (Lock)
			{
				PeerFeatureState value = null;
				if (uid != 0L)
				{
					ByUid.TryGetValue(uid, out value);
				}
				if (value == null && obj != null)
				{
					ByRpc.TryGetValue(obj, out value);
				}
				if (value == null && socketFromRpc != null)
				{
					BySocket.TryGetValue(socketFromRpc, out value);
				}
				if (uid != 0L)
				{
					ByUid.Remove(uid);
				}
				if (obj != null)
				{
					ByRpc.Remove(obj);
				}
				if (socketFromRpc != null)
				{
					BySocket.Remove(socketFromRpc);
				}
				if (value != null)
				{
					if (value.Uid != 0L)
					{
						ByUid.Remove(value.Uid);
					}
					if (value.Rpc != null)
					{
						ByRpc.Remove(value.Rpc);
					}
					if (value.Socket != null)
					{
						BySocket.Remove(value.Socket);
					}
					RemoveState(ByRpc, value);
					RemoveState(BySocket, value);
					RemoveState(ByUid, value);
				}
			}
		}

		private static void RemoveState<TKey>(Dictionary<TKey, PeerFeatureState> map, PeerFeatureState state)
		{
			if (state == null || map.Count == 0)
			{
				return;
			}
			List<TKey> list = new List<TKey>();
			foreach (KeyValuePair<TKey, PeerFeatureState> item in map)
			{
				if (item.Value == state)
				{
					list.Add(item.Key);
				}
			}
			foreach (TKey item2 in list)
			{
				map.Remove(item2);
			}
		}

		internal static bool IsCompressionActiveForSocket(object socket)
		{
			if (!EffectiveConfig.CompressionEnabled)
			{
				return false;
			}
			PeerFeatureState bySocket = GetBySocket(socket);
			if (bySocket == null)
			{
				return false;
			}
			if (bySocket.CompressionFailures >= 1)
			{
				return false;
			}
			if (bySocket.HandshakeReceived)
			{
				return Supports(bySocket, PeerFeatureFlags.Compression);
			}
			return false;
		}

		internal static bool IsRpcAoiActiveForUid(long uid)
		{
			if (!EffectiveConfig.RpcAoiEnabled)
			{
				return false;
			}
			PeerFeatureState byUid = GetByUid(uid);
			if (byUid == null)
			{
				return true;
			}
			if (byUid.HandshakeReceived)
			{
				return Supports(byUid, PeerFeatureFlags.RpcAoi);
			}
			return true;
		}

		internal static void RecordCompressionFailure(object socket)
		{
			PeerFeatureState bySocket = GetBySocket(socket);
			if (bySocket != null)
			{
				bySocket.CompressionFailures++;
			}
		}

		internal static void OnNewConnection(object znetPeer)
		{
			if (ModConfig.Enabled.Value)
			{
				object peerRpc = NetReflection.GetPeerRpc(znetPeer);
				if (peerRpc != null)
				{
					NetReflection.TryGetPeerUid(znetPeer, out var uid);
					PeerFeatureState orCreateByRpc = GetOrCreateByRpc(peerRpc, uid);
					RegisterRpc(peerRpc, orCreateByRpc);
					SendHello(peerRpc, orCreateByRpc);
				}
			}
		}

		private static void RegisterRpc(object rpc, PeerFeatureState state)
		{
			if (rpc == null || state == null || state.RegisteredRpc)
			{
				return;
			}
			try
			{
				if (!(ReflectionCache.ZRpcRegisterGenericPackageMethod == null) && !(ReflectionCache.ZRpcType == null) && !(ReflectionCache.ZPackageType == null))
				{
					MethodInfo method = typeof(FeatureNegotiation).GetMethod("RPC_Features_Generic", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(ReflectionCache.ZRpcType, ReflectionCache.ZPackageType);
					Delegate obj = Delegate.CreateDelegate(typeof(Action<, >).MakeGenericType(ReflectionCache.ZRpcType, ReflectionCache.ZPackageType), method);
					ReflectionCache.ZRpcRegisterGenericPackageMethod.Invoke(rpc, new object[2] { "SkadiNet_Features", obj });
					state.RegisteredRpc = true;
				}
			}
			catch (Exception ex)
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("Could not register SkadiNet_Features: " + ex.Message));
				}
			}
		}

		private static void SendHello(object rpc, PeerFeatureState state)
		{
			if (rpc == null || state == null || state.HandshakeSent)
			{
				return;
			}
			try
			{
				object obj = ZPackageTools.NewPackage();
				ZPackageTools.WriteInt(obj, 1179536211);
				ZPackageTools.WriteInt(obj, 2);
				ZPackageTools.WriteInt(obj, (int)LocalCapabilities);
				ZPackageTools.WriteString(obj, "1.0.4");
				ReflectionCache.ZRpcInvokeMethod?.Invoke(rpc, new object[2]
				{
					"SkadiNet_Features",
					new object[1] { obj }
				});
				state.HandshakeSent = true;
			}
			catch (Exception ex)
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("Could not send SkadiNet feature handshake: " + ex.Message));
				}
			}
		}

		private static void RPC_Features_Generic<TRpc, TPkg>(TRpc rpc, TPkg pkg)
		{
			RPC_Features(rpc, pkg);
		}

		private static void RPC_Features(object rpc, object pkg)
		{
			try
			{
				PeerFeatureState orCreateByRpc = GetOrCreateByRpc(rpc, 0L);
				if (orCreateByRpc == null || pkg == null)
				{
					return;
				}
				int pos = ZPackageTools.GetPos(pkg);
				ZPackageTools.SetPos(pkg, 0);
				if (ZPackageTools.ReadInt(pkg) != 1179536211)
				{
					ZPackageTools.SetPos(pkg, pos);
					return;
				}
				int num = ZPackageTools.ReadInt(pkg);
				int remoteCapabilities = ZPackageTools.ReadInt(pkg);
				string text = ZPackageTools.ReadString(pkg);
				orCreateByRpc.RemoteCapabilities = (PeerFeatureFlags)remoteCapabilities;
				orCreateByRpc.HandshakeReceived = num >= 1;
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogDebug((object)$"SkadiNet feature handshake: protocol={num}, capabilities={orCreateByRpc.RemoteCapabilities}, version={text}, compression={EffectiveConfig.CompressionEnabled && Supports(orCreateByRpc, PeerFeatureFlags.Compression)}, rpcAoi={EffectiveConfig.RpcAoiEnabled && Supports(orCreateByRpc, PeerFeatureFlags.RpcAoi)}");
				}
				if (!orCreateByRpc.HandshakeSent)
				{
					SendHello(rpc, orCreateByRpc);
				}
			}
			catch (Exception ex)
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("SkadiNet feature handshake receive failed: " + ex.Message));
				}
			}
		}

		private static bool Supports(PeerFeatureState state, PeerFeatureFlags flag)
		{
			if (state != null)
			{
				return (state.RemoteCapabilities & flag) != 0;
			}
			return false;
		}
	}
	[HarmonyPatch]
	internal static class ZNetOnNewConnectionFeatureHandshakePatch
	{
		private static MethodBase TargetMethod()
		{
			Type type = ReflectionCache.ZNetType ?? AccessTools.TypeByName("ZNet");
			Type type2 = ReflectionCache.ZNetPeerType ?? AccessTools.TypeByName("ZNetPeer");
			return AccessTools.Method(type, "OnNewConnection", (!(type2 != null)) ? null : new Type[1] { type2 }, (Type[])null) ?? AccessTools.Method(type, "OnNewConnection", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __0)
		{
			FeatureNegotiation.OnNewConnection(__0);
		}
	}
	internal static class PeerLifecycle
	{
		internal static void ClearDisconnectedPeer(object zdoPeer)
		{
			long uid = 0L;
			NetReflection.TryGetPeerUid(zdoPeer, out uid);
			FeatureNegotiation.ClearPeer(zdoPeer, uid);
			PeerQualityMeter.ClearPeer(zdoPeer, uid);
			OwnershipManager.ClearPeer(uid);
			ZDOManSendSchedulerPatch.ClearPeer(uid);
		}
	}
	[HarmonyPatch]
	internal static class PeerLifecycleDisconnectPatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			HashSet<MethodBase> hashSet = new HashSet<MethodBase>();
			AddNamedMethods(hashSet, ReflectionCache.ZNetType ?? AccessTools.TypeByName("ZNet"), "Disconnect", "OnDisconnected", "OnPeerDisconnected", "RemovePeer", "RPC_Disconnect");
			AddNamedMethods(hashSet, ReflectionCache.ZDOManType ?? AccessTools.TypeByName("ZDOMan"), "RemovePeer", "RemoveZdoPeer", "DisconnectPeer");
			foreach (MethodBase item in hashSet)
			{
				yield return item;
			}
		}

		private static void AddNamedMethods(HashSet<MethodBase> targets, Type type, params string[] names)
		{
			if (targets == null || type == null || names == null)
			{
				return;
			}
			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				for (int j = 0; j < names.Length; j++)
				{
					if (methodInfo.Name == names[j])
					{
						targets.Add(methodInfo);
						break;
					}
				}
			}
		}

		private static void Postfix(object[] __args)
		{
			if (__args != null)
			{
				for (int i = 0; i < __args.Length; i++)
				{
					TryClear(__args[i]);
				}
			}
		}

		private static void TryClear(object candidate)
		{
			if (candidate != null)
			{
				long uid;
				bool num = NetReflection.TryGetPeerUid(candidate, out uid) && uid != 0;
				bool flag = NetReflection.GetPeerRpc(candidate) != null;
				bool flag2 = ReflectionCache.ZRpcType != null && ReflectionCache.ZRpcType.IsInstanceOfType(candidate);
				if (num || flag || flag2)
				{
					PeerLifecycle.ClearDisconnectedPeer(candidate);
				}
			}
		}
	}
	internal sealed class PeerQualityState
	{
		public object Rpc;

		public long Uid;

		public readonly Queue<float> Samples = new Queue<float>();

		public float LastPingMs;

		public float PingEmaMs;

		public float PingMeanMs;

		public float PingStdDevMs;

		public float PingJitterMs;

		public float ConnectionQualityMs;

		public double LastUpdateTime;

		public bool HasAnySample;
	}
	internal static class PeerQualityMeter
	{
		private static readonly Dictionary<object, PeerQualityState> ByRpc = new Dictionary<object, PeerQualityState>();

		private static readonly Dictionary<long, PeerQualityState> ByUid = new Dictionary<long, PeerQualityState>();

		private static readonly Dictionary<Type, Func<object, int>> SocketPingReaders = new Dictionary<Type, Func<object, int>>();

		private static readonly object Lock = new object();

		private static Func<object, float> _zrpcPingGetter;

		internal static void Initialize()
		{
			lock (Lock)
			{
				ByRpc.Clear();
				ByUid.Clear();
				SocketPingReaders.Clear();
			}
			if (ReflectionCache.ZRpcType != null)
			{
				_zrpcPingGetter = ReflectionDelegateFactory.SingleFieldGetter(ReflectionCache.SilentField(ReflectionCache.ZRpcType, "m_ping"));
			}
		}

		internal static PeerQualityState GetOrCreateByRpc(object rpc, long uid = 0L)
		{
			if (rpc == null)
			{
				return null;
			}
			lock (Lock)
			{
				if (!ByRpc.TryGetValue(rpc, out var value))
				{
					value = new PeerQualityState
					{
						Rpc = rpc,
						Uid = uid,
						PingEmaMs = 999f,
						ConnectionQualityMs = 999f
					};
					ByRpc[rpc] = value;
				}
				else if (value.Rpc == null)
				{
					value.Rpc = rpc;
				}
				if (uid != 0L)
				{
					value.Uid = uid;
					ByUid[uid] = value;
				}
				return value;
			}
		}

		internal static void ClearPeer(object peerOrRpc, long uid)
		{
			object obj = NetReflection.GetPeerRpc(peerOrRpc);
			if (obj == null && peerOrRpc != null && ReflectionCache.ZRpcType != null && ReflectionCache.ZRpcType.IsInstanceOfType(peerOrRpc))
			{
				obj = peerOrRpc;
			}
			lock (Lock)
			{
				PeerQualityState value = null;
				if (uid != 0L)
				{
					ByUid.TryGetValue(uid, out value);
				}
				if (value == null && obj != null)
				{
					ByRpc.TryGetValue(obj, out value);
				}
				if (uid != 0L)
				{
					ByUid.Remove(uid);
				}
				if (obj != null)
				{
					ByRpc.Remove(obj);
				}
				if (value != null)
				{
					if (value.Uid != 0L)
					{
						ByUid.Remove(value.Uid);
					}
					if (value.Rpc != null)
					{
						ByRpc.Remove(value.Rpc);
					}
					RemoveState(ByRpc, value);
					RemoveState(ByUid, value);
				}
			}
		}

		private static void RemoveState<TKey>(Dictionary<TKey, PeerQualityState> map, PeerQualityState state)
		{
			if (state == null || map.Count == 0)
			{
				return;
			}
			List<TKey> list = new List<TKey>();
			foreach (KeyValuePair<TKey, PeerQualityState> item in map)
			{
				if (item.Value == state)
				{
					list.Add(item.Key);
				}
			}
			foreach (TKey item2 in list)
			{
				map.Remove(item2);
			}
		}

		internal static PeerQualityState GetByUid(long uid)
		{
			lock (Lock)
			{
				ByUid.TryGetValue(uid, out var value);
				return value;
			}
		}

		internal static PeerQualityState GetByPeer(object zdoPeer)
		{
			object peerRpc = NetReflection.GetPeerRpc(zdoPeer);
			NetReflection.TryGetPeerUid(zdoPeer, out var uid);
			return GetOrCreateByRpc(peerRpc, uid);
		}

		internal static void UpdateFromRpc(object rpc)
		{
			UpdateFromRpcCore(rpc, 0L);
		}

		private static PeerQualityState UpdateFromRpcCore(object rpc, long uid)
		{
			if (!EffectiveConfig.PeerQualityEnabled || rpc == null)
			{
				return null;
			}
			float num = TryReadPingMs(rpc);
			if (num <= 0f || float.IsNaN(num) || float.IsInfinity(num))
			{
				return GetOrCreateByRpc(rpc, uid);
			}
			if (uid == 0L)
			{
				NetReflection.TryGetUidFromPeerObject(rpc, out uid);
			}
			PeerQualityState orCreateByRpc = GetOrCreateByRpc(rpc, uid);
			if (orCreateByRpc == null)
			{
				return null;
			}
			double num2 = Time.realtimeSinceStartup;
			lock (Lock)
			{
				double num3 = ((orCreateByRpc.LastUpdateTime > 0.0) ? Math.Max(0.001, num2 - orCreateByRpc.LastUpdateTime) : 0.05);
				orCreateByRpc.LastUpdateTime = num2;
				float lastPingMs = orCreateByRpc.LastPingMs;
				orCreateByRpc.LastPingMs = num;
				double num4 = (double)Math.Max(0.1f, EffectiveConfig.PeerPingEmaHalfLifeSeconds) / Math.Log(2.0);
				float num5 = (float)(1.0 - Math.Exp((0.0 - num3) / num4));
				orCreateByRpc.PingEmaMs = (orCreateByRpc.HasAnySample ? (num5 * num + (1f - num5) * orCreateByRpc.PingEmaMs) : num);
				orCreateByRpc.HasAnySample = true;
				orCreateByRpc.Samples.Enqueue(num);
				while (orCreateByRpc.Samples.Count > Math.Max(4, EffectiveConfig.PeerPingSampleWindow))
				{
					orCreateByRpc.Samples.Dequeue();
				}
				RecalculateWindowStats(orCreateByRpc, lastPingMs);
				orCreateByRpc.ConnectionQualityMs = orCreateByRpc.PingMeanMs * EffectiveConfig.PeerQualityMeanWeight + orCreateByRpc.PingStdDevMs * EffectiveConfig.PeerQualityStdDevWeight + orCreateByRpc.PingJitterMs * EffectiveConfig.PeerQualityJitterWeight + orCreateByRpc.PingEmaMs * EffectiveConfig.PeerQualityEmaWeight;
				return orCreateByRpc;
			}
		}

		private static float TryReadPingMs(object rpc)
		{
			if (TryReadSocketPingMs(NetReflection.GetSocketFromRpc(rpc), out var pingMs))
			{
				return pingMs;
			}
			try
			{
				if (_zrpcPingGetter == null)
				{
					return -1f;
				}
				float num = _zrpcPingGetter(rpc);
				return (num < 10f) ? (num * 1000f) : num;
			}
			catch
			{
			}
			return -1f;
		}

		private static bool TryReadSocketPingMs(object socket, out float pingMs)
		{
			pingMs = 0f;
			if (socket == null)
			{
				return false;
			}
			try
			{
				Func<object, int> socketPingReader = GetSocketPingReader(socket.GetType());
				if (socketPingReader == null)
				{
					return false;
				}
				int num = socketPingReader(socket);
				if (num > 0)
				{
					pingMs = num;
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static Func<object, int> GetSocketPingReader(Type socketType)
		{
			if (socketType == null)
			{
				return null;
			}
			lock (Lock)
			{
				if (SocketPingReaders.TryGetValue(socketType, out var value))
				{
					return value;
				}
				Func<object, int> func = null;
				MethodInfo[] methods = socketType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					if (!(methodInfo.Name != "GetConnectionQuality"))
					{
						func = CreateSocketPingReader(methodInfo);
						if (func != null)
						{
							break;
						}
					}
				}
				SocketPingReaders[socketType] = func;
				return func;
			}
		}

		private static Func<object, int> CreateSocketPingReader(MethodInfo method)
		{
			try
			{
				if (method == null || method.IsStatic || method.ContainsGenericParameters || method.ReturnType != typeof(void))
				{
					return null;
				}
				ParameterInfo[] parameters = method.GetParameters();
				Type type = typeof(float).MakeByRefType();
				Type type2 = typeof(int).MakeByRefType();
				if (parameters.Length != 5 || parameters[0].ParameterType != type || parameters[1].ParameterType != type || parameters[2].ParameterType != type2 || parameters[3].ParameterType != type || parameters[4].ParameterType != type)
				{
					return null;
				}
				ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "socket");
				ParameterExpression parameterExpression2 = Expression.Variable(typeof(float), "localQuality");
				ParameterExpression parameterExpression3 = Expression.Variable(typeof(float), "remoteQuality");
				ParameterExpression parameterExpression4 = Expression.Variable(typeof(int), "ping");
				ParameterExpression parameterExpression5 = Expression.Variable(typeof(float), "outByteSec");
				ParameterExpression parameterExpression6 = Expression.Variable(typeof(float), "inByteSec");
				MethodCallExpression methodCallExpression = Expression.Call(Expression.Convert(parameterExpression, method.DeclaringType), method, parameterExpression2, parameterExpression3, parameterExpression4, parameterExpression5, parameterExpression6);
				return Expression.Lambda<Func<object, int>>(Expression.Block(new ParameterExpression[5] { parameterExpression2, parameterExpression3, parameterExpression4, parameterExpression5, parameterExpression6 }, methodCallExpression, parameterExpression4), new ParameterExpression[1] { parameterExpression }).Compile();
			}
			catch
			{
				return null;
			}
		}

		private static void RecalculateWindowStats(PeerQualityState state, float previousLast)
		{
			int count = state.Samples.Count;
			if (count == 0)
			{
				return;
			}
			float num = 0f;
			foreach (float sample in state.Samples)
			{
				num += sample;
			}
			float num2 = num / (float)count;
			float num3 = 0f;
			foreach (float sample2 in state.Samples)
			{
				float num4 = sample2 - num2;
				num3 += num4 * num4;
			}
			state.PingMeanMs = num2;
			state.PingStdDevMs = (float)Math.Sqrt(num3 / (float)Math.Max(1, count));
			state.PingJitterMs = ((previousLast > 0f) ? Math.Abs(state.LastPingMs - previousLast) : 0f);
		}
	}
	[HarmonyPatch]
	internal static class ZRpcReceivePingPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(ReflectionCache.ZRpcType ?? AccessTools.TypeByName("ZRpc"), "ReceivePing", (Type[])null, (Type[])null);
		}

		private static void Postfix(object __instance)
		{
			PeerQualityMeter.UpdateFromRpc(__instance);
		}
	}
	internal enum RpcAoiKind
	{
		Visual,
		StateCritical,
		Unknown
	}
	internal static class RpcAoiRouter
	{
		private static readonly Dictionary<int, RpcAoiKind> Known = new Dictionary<int, RpcAoiKind>();

		private static int _damageTextHash;

		private static int _talkerSayHash;

		private static bool _initialized;

		internal static void Initialize()
		{
			Known.Clear();
			_damageTextHash = StableHash("DamageText");
			_talkerSayHash = StableHash("TalkerSay");
			Add(_damageTextHash, RpcAoiKind.Visual);
			Add(_talkerSayHash, RpcAoiKind.Visual);
			Add("HealthChanged", RpcAoiKind.StateCritical);
			Add("WNTHealthChanged", RpcAoiKind.StateCritical);
			Add("SetTarget", RpcAoiKind.StateCritical);
			Add("TriggerOnDeath", RpcAoiKind.StateCritical);
			Add("SpawnedZone", RpcAoiKind.StateCritical);
			_initialized = true;
		}

		private static void Add(string name, RpcAoiKind kind)
		{
			Add(StableHash(name), kind);
		}

		private static void Add(int methodHash, RpcAoiKind kind)
		{
			Known[methodHash] = kind;
		}

		internal static bool TryRoute(object routedRpc, object package)
		{
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			if (!EffectiveConfig.RpcAoiEnabled || !NetReflection.IsServer())
			{
				return false;
			}
			if (routedRpc == null || package == null || RpcReflection.RoutedRPCDataType == null)
			{
				return false;
			}
			if (!_initialized)
			{
				Initialize();
			}
			try
			{
				int pos = ZPackageTools.GetPos(package);
				ZPackageTools.SetPos(package, 0);
				object routedData = RpcReflection.CreateRoutedRpcData();
				bool num = RpcReflection.DeserializeRoutedRpcData(routedData, package);
				ZPackageTools.SetPos(package, pos);
				if (!num)
				{
					return false;
				}
				int methodHash = RpcReflection.GetMethodHash(routedData);
				RpcAoiKind value;
				RpcAoiKind rpcAoiKind = (Known.TryGetValue(methodHash, out value) ? value : RpcAoiKind.Unknown);
				switch (rpcAoiKind)
				{
				case RpcAoiKind.Unknown:
					return false;
				case RpcAoiKind.StateCritical:
					return false;
				default:
				{
					if (!IsKindEnabled(rpcAoiKind, methodHash))
					{
						return false;
					}
					if (RpcReflection.GetTargetPeerId(routedData) != 0L)
					{
						return false;
					}
					float num2 = RadiusFor(rpcAoiKind);
					if (num2 <= 0f)
					{
						return false;
					}
					if (!TryResolveZdoOrigin(routedData, out var origin))
					{
						return false;
					}
					List<long> list = new List<long>();
					int num3 = 0;
					foreach (object item in ZdoReflection.EnumeratePeers(ZdoReflection.ZDOManInstance))
					{
						if (!NetReflection.TryGetPeerUid(item, out var uid) || uid == 0L)
						{
							continue;
						}
						num3++;
						Vector3 val = NetReflection.GetPeerRefPos(item) - origin;
						if (!(((Vector3)(ref val)).sqrMagnitude > num2 * num2))
						{
							if (!FeatureNegotiation.IsRpcAoiActiveForUid(uid))
							{
								return false;
							}
							list.Add(uid);
						}
					}
					if (list.Count <= 0)
					{
						return false;
					}
					if (list.Count >= num3)
					{
						return false;
					}
					int num4 = 0;
					foreach (long item2 in list)
					{
						RpcReflection.SetTargetPeerId(routedData, item2);
						if (RpcReflection.RouteRpc(routedRpc, routedData))
						{
							num4++;
						}
					}
					if (num4 <= 0)
					{
						return false;
					}
					if (num4 < list.Count && ModConfig.DebugLogging.Value)
					{
						Plugin.Log.LogDebug((object)$"RPC AoI routed partial hash={methodHash} kind={rpcAoiKind} recipients={num4}/{list.Count} radius={num2}; vanilla fallback is suppressed to avoid duplicates for already-routed visual RPCs.");
					}
					if (ModConfig.DebugLogging.Value)
					{
						Plugin.Log.LogDebug((object)$"RPC AoI routed hash={methodHash} kind={rpcAoiKind} recipients={num4} radius={num2}");
					}
					return true;
				}
				}
			}
			catch (Exception ex)
			{
				if (ModConfig.DebugLogging.Value)
				{
					Plugin.Log.LogWarning((object)("RPC AoI failed; vanilla route used: " + ex.Message));
				}
				return false;
			}
		}

		private static bool IsKindEnabled(RpcAoiKind kind, int methodHash)
		{
			if (kind != RpcAoiKind.Visual)
			{
				return false;
			}
			if (methodHash != _damageTextHash)
			{
				return methodHash == _talkerSayHash;
			}
			return true;
		}

		private static float RadiusFor(RpcAoiKind kind)
		{
			return Math.Max(1f, EffectiveConfig.RpcAoiVisualRadius);
		}

		private static bool TryResolveZdoOrigin(object routedData, out Vector3 origin)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			origin = Vector3.zero;
			try
			{
				object targetZdo = RpcReflection.GetTargetZdo(routedData);
				if (targetZdo != null && !ZdoReflection.IsIdNone(targetZdo))
				{
					object byId = ZdoReflection.GetById(targetZdo);
					if (byId != null)
					{
						origin = ZdoReflection.GetPosition(byId, Vector3.zero);
						return true;
					}
				}
			}
			catch
			{
			}
			return false;
		}

		internal static int StableHash(string text)
		{
			int num = 5381;
			int num2 = num;
			for (int i = 0; i < text.Length; i += 2)
			{
				num = ((num << 5) + num) ^ text[i];
				if (i == text.Length - 1)
				{
					break;
				}
				num2 = ((num2 << 5) + num2) ^ text[i + 1];
			}
			return num + num2 * 1566083941;
		}
	}
	[HarmonyPatch]
	internal static class ZRoutedRpcAoiPatch
	{
		private static MethodBase TargetMethod()
		{
			Type type = ReflectionCache.ZRoutedRpcType ?? AccessTools.TypeByName("ZRoutedRpc");
			Type type2 = ReflectionCache.ZRpcType ?? AccessTools.TypeByName("ZRpc");
			Type type3 = ReflectionCache.ZPackageType ?? AccessTools.TypeByName("ZPackage");
			return AccessTools.Method(type, "RPC_RoutedRPC", (!(type2 != null) || !(type3 != null)) ? null : new Type[2] { type2, type3 }, (Type[])null) ?? AccessTools.Method(type, "RPC_RoutedRPC", (Type[])null, (Type[])null);
		}

		private static bool Prefix(object __instance, object __0, object __1)
		{
			return !RpcAoiRouter.TryRoute(__instance, __1);
		}
	}
	[HarmonyPatch]
	internal static class ZSteamSocketRegisterGlobalCallbacksRatePatch
	{
		private const int VanillaSteamSendRateBytes = 153600;

		public static int SteamSendRateBytes
		{
			get
			{
				if (!ModConfig.Enabled.Value)
				{
					return 153600;
				}
				return 36000000;
			}
		}

		private static MethodBase TargetMethod()
		{
			return AccessTools.Method("ZSteamSocket:RegisterGlobalCallbacks", (Type[])null, (Type[])null);
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			int num = 0;
			foreach (CodeInstruction item in list)
			{
				if (item.opcode == OpCodes.Ldc_I4 && item.operand is int num2 && num2 == 153600)
				{
					num++;
				}
			}
			if (num != 1)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)$"Skipped Steam send-rate transpiler: expected one {153600} constant, found {num}.");
				}
				return list;
			}
			for (int i = 0; i < list.Count; i++)
			{
				CodeInstruction val = list[i];
				if (val.opcode == OpCodes.Ldc_I4 && val.operand is int num3 && num3 == 153600)
				{
					list[i] = Replacement(val);
				}
			}
			return list;
		}

		private static CodeInstruction Replacement(CodeInstruction original)
		{
			//IL_0019: 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_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			CodeInstruction val = new CodeInstruction(OpCodes.Call, (object)AccessTools.PropertyGetter(typeof(ZSteamSocketRegisterGlobalCallbacksRatePatch), "SteamSendRateBytes"));
			val.labels.AddRange(original.labels);
			val.blocks.AddRange(original.blocks);
			return val;
		}
	}
	internal static class ZPackageTools
	{
		internal const int CompressionMagic = 1129204563;

		internal const int CompressionProtocol = 1;

		internal const int CompressionAlgoDeflate = 1;

		private const int MaxDecompressedPackageBytes = 67108864;

		private const int InflateBufferBytes = 81920;

		private static ConstructorInfo _ctorEmpty;

		private static ConstructorInfo _ctorBytes;

		private static MethodInfo _writeInt;

		private static MethodInfo _writeString;

		private static MethodInfo _writeByteArray;

		private static MethodInfo _readInt;

		private static MethodInfo _readByteArray;

		private static MethodInfo _readString;

		private static MethodInfo _getArray;

		private static MethodInfo _size;

		private static MethodInfo _setPos;

		private static MethodInfo _getPos;

		private static MethodInfo _loadBytes;

		internal static void Initialize()
		{
			Type zPackageType = ReflectionCache.ZPackageType;
			if (!(zPackageType == null))
			{
				_ctorEmpty = AccessTools.Constructor(zPackageType, Type.EmptyTypes, false);
				_ctorBytes = AccessTools.Constructor(zPackageType, new Type[1] { typeof(byte[]) }, false);
				_writeInt = AccessTools.Method(zPackageType, "Write", new Type[1] { typeof(int) }, (Type[])null);
				_writeString = AccessTools.Method(zPackageType, "Write", new Type[1] { typeof(string) }, (Type[])null);
				_writeByteArray = AccessTools.Method(zPackageType, "Write", new Type[1] { typeof(byte[]) }, (Type[])null);
				_readInt = AccessTools.Method(zPackageType, "ReadInt", (Type[])null, (Type[])null);
				_readByteArray = AccessTools.Method(zPackageType, "ReadByteArray", Type.EmptyTypes, (Type[])null);
				_readString = AccessTools.Method(zPackageType, "ReadString", (Type[])null, (Type[])null);
				_getArray = AccessTools.Method(zPackageType, "GetArray", (Type[])null, (Type[])null);
				_size = AccessTools.Method(zPackageType, "Size", (Type[])null, (Type[])null);
				_setPos = AccessTools.Method(zPackageType, "SetPos", new Type[1] { typeof(int) }, (Type[])null);
				_getPos = AccessTools.Method(zPackageType, "GetPos", (Type[])null, (Type[])null);
				_loadBytes = AccessTools.Method(zPackageType, "Load", new Type[1] { typeof(byte[]) }, (Type[])null);
			}
		}

		internal static object NewPackage()
		{
			return _ctorEmpty?.Invoke(null) ?? Activator.CreateInstance(ReflectionCache.ZPackageType);
		}

		internal static object NewPackage(byte[] bytes)
		{
			if (_ctorBytes != null)
			{
				return _ctorBytes.Invoke(new object[1] { bytes });
			}
			object obj = NewPackage();
			_loadBytes?.Invoke(obj, new object[1] { bytes });
			return obj;
		}

		internal static int Size(object pkg)
		{
			try
			{
				return (_size != null && pkg != null) ? ((int)_size.Invoke(pkg, null)) : 0;
			}
			catch
			{
				return 0;
			}
		}

		internal static byte[] GetArray(object pkg)
		{
			try
			{
				return (_getArray?.Invoke(pkg, null) as byte[]) ?? Array.Empty<byte>();
			}
			catch
			{
				return Array.Empty<byte>();
			}
		}

		internal static int GetPos(object pkg)
		{
			try
			{
				return (_getPos != null) ? ((int)_getPos.Invoke(pkg, null)) : 0;
			}
			catch
			{
				return 0;
			}
		}

		internal static void SetPos(object pkg, int pos)
		{
			try
			{
				_setPos?.Invoke(pkg, new object[1] { pos });
			}
			catch
			{
			}
		}

		internal static void WriteInt(object pkg, int v)
		{
			_writeInt.Invoke(pkg, new object[1] { v });
		}

		internal static void WriteString(object pkg, string v)
		{
			_writeString.Invoke(pkg, new object[1] { v ?? string.Empty });
		}

		internal static void WriteByteArray(object pkg, byte[] v)
		{
			_writeByteArray.Invoke(pkg, new object[1] { v ?? Array.Empty<byte>() });
		}

		internal static int ReadInt(object pkg)
		{
			return (int)_readInt.Invoke(pkg, null);
		}

		internal static string ReadString(object pkg)
		{
			return (string)_readString.Invoke(pkg, null);
		}

		internal static byte[] ReadByteArray(object pkg)
		{
			return (_readByteArray.Invoke(pkg, null) as byte[]) ?? Array.Empty<byte>();
		}

		internal static bool TryBuildCompressedPackage(object original, out object compressedPackage)
		{
			compressedPackage = null;
			byte[] array = GetArray(original);
			if (array == null || array.Length < Math.Max(64, EffectiveConfig.CompressionThresholdBytes) || array.Length > 67108864)
			{
				return false;
			}
			if (LooksCompressed(original))
			{
				return false;
			}
			byte[] array2 = Deflate(array);
			if (array2 == null || array2.Length == 0)
			{
				return false;
			}
			if ((float)array2.Length / (float)Math.Max(1, array.Length) >= EffectiveConfig.CompressionMinUsefulRatio)
			{
				return false;
			}
			object obj = NewPackage();
			WriteInt(obj, 1129204563);
			WriteInt(obj, 1);
			WriteInt(obj, 1);
			WriteInt(obj, array.Length);
			WriteByteArray(obj, array2);
			compressedPackage = obj;
			return true;
		}

		internal static bool TryDecompressPackage(object maybeCompressed, out object rawPackage)
		{
			rawPackage = maybeCompressed;
			if (maybeCompressed == null)
			{
				return false;
			}
			if (Size(maybeCompressed) < 4)
			{
				return false;
			}
			int pos = GetPos(maybeCompressed);
			try
			{
				SetPos(maybeCompressed, 0);
				if (ReadInt(maybeCompressed) != 1129204563)
				{
					return false;
				}
				int num = ReadInt(maybeCompressed);
				int num2 = ReadInt(maybeCompressed);
				int num3 = ReadInt(maybeCompressed);
				byte[] array = ReadByteArray(maybeCompressed);
				if (num != 1 || num2 != 1 || num3 <= 0 || num3 > 67108864)
				{
					throw new InvalidDataException($"Unsupported SkadiNet compression header protocol={num}, algo={num2}, size={num3}");
				}
				if (array == null || array.Length == 0)
				{
					throw new InvalidDataException("SkadiNet compression payload is empty.");
				}
				byte[] bytes = Inflate(array, num3);
				rawPackage = NewPackage(bytes);
				return true;
			}
			finally
			{
				SetPos(maybeCompressed, pos);
			}
		}

		internal static bool LooksCompressed(object pkg)
		{
			if (pkg == null)
			{
				return false;
			}
			int pos = GetPos(pkg);
			try
			{
				if (Size(pkg) < 16)
				{
					return false;
				}
				SetPos(pkg, 0);
				return ReadInt(pkg) == 1129204563;
			}
			catch
			{
				return false;
			}
			finally
			{
				SetPos(pkg, pos);
			}
		}

		private static byte[] Deflate(byte[] raw)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Fastest, leaveOpen: true))
			{
				deflateStream.Write(raw, 0, raw.Length);
			}
			return memoryStream.ToArray();
		}

		private static byte[] Inflate(byte[] compressed, int expectedSize)
		{
			using MemoryStream stream = new MemoryStream(compressed);
			using DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress);
			using MemoryStream memoryStream = new MemoryStream(Math.Min(expectedSize, 1048576));
			byte[] array = new byte[81920];
			int num = 0;
			int num2;
			while ((num2 = deflateStream.Read(array, 0, array.Length)) > 0)
			{
				num += num2;
				if (num > expectedSize || num > 67108864)
				{
					throw new InvalidDataException($"Inflated payload exceeds declared size {expectedSize}.");
				}
				memoryStream.Write(array, 0, num2);
			}
			byte[] array2 = memoryStream.ToArray();
			if (array2.Length != expectedSize)
			{
				throw new InvalidDataException($"Inflated size mismatch expected={expectedSize}, actual={array2.Length}");
			}
			return array2;
		}
	}
	[BepInPlugin("sighsorry.SkadiNet", "SkadiNet", "1.0.4")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "sighsorry.SkadiNet";

		public const string PluginName = "SkadiNet";

		public const string ModVersion = "1.0.4";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		internal static Plugin Instance;

		private void Awake()
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigSyncManager.Initialize(((BaseUnityPlugin)this).Config);
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			ReflectionCache.Initialize();
			FeatureNegotiation.Initialize();
			PeerQualityMeter.Initialize();
			ZdoKeyPolicy.Initialize();
			OwnershipManager.Initialize();
			RpcAoiRouter.Initialize();
			ClientStutterGuard.Initialize(this);
			if (!ModConfig.Enabled.Value)
			{
				Log.LogInfo((object)"SkadiNet is disabled by configuration.");
				return;
			}
			_harmony = new Harmony("sighsorry.SkadiNet");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			Log.LogInfo((object)("SkadiNet 1.0.4 loaded. Stable core: adaptive scheduler, queue patch, micro-update reducer, peer quality, adaptive client ownership, optional client stutter guard. ServerSync config lock=" + (ModConfig.LockServerConfig.Value ? "on" : "off") + ". Slider-gated features: scheduler=" + (EffectiveConfig.SchedulerEnabled ? "on" : "off") + ", payload reducer=" + (EffectiveConfig.PayloadReducerEnabled ? "on" : "off") + ", compression=" + (EffectiveConfig.CompressionEnabled ? "on" : "off") + ", RPC AoI=" + (EffectiveConfig.RpcAoiEnabled ? "on" : "off") + ", ClientStutterGuard=" + (EffectiveConfig.ClientStutterGuardEnabled ? "on" : "off") + "."));
			LogDebugConfigSnapshot();
		}

		private static void LogDebugConfigSnapshot()
		{
			if (ModConfig.DebugLogging.Value)
			{
				Log.LogDebug((object)($"SkadiNet sliders: SchedulerThroughput={ModConfig.SchedulerThroughput.Value}, PayloadReducerStrength={ModConfig.PayloadReducerStrength.Value}, " + $"CompressionAggression={ModConfig.CompressionAggression.Value}, OwnershipIntensity={ModConfig.OwnershipIntensity.Value}, " + $"RpcAoiAggression={ModConfig.RpcAoiAggression.Value}, ClientStutterGuardStrength={ModConfig.ClientStutterGuardStrength.Value}"));
				Log.LogDebug((object)($"SkadiNet scheduler effective: interval={EffectiveConfig.SendInterval:F3}s min={EffectiveConfig.MinSendInterval:F3}s max={EffectiveConfig.MaxSendInterval:F3}s " + $"peersPerTick={EffectiveConfig.BasePeersPerTick}-{EffectiveConfig.MaxPeersPerTick} " + "zdoQueueLimit=" + FormatBytes(EffectiveConfig.ZdoQueueLimitBytes) + " minPackage=" + FormatBytes(EffectiveConfig.ZdoQueueMinPackageBytes) + " peerQueueSoft=" + FormatBytes(EffectiveConfig.PeerQueueSoftLimitBytes) + " peerQueueHard=" + FormatBytes(EffectiveConfig.PeerQueueHardLimitBytes) + " " + $"lagBackfill={EffectiveConfig.LaggingPeerMaxSkipSeconds:F2}s fixedSteamRate={FormatBytes(36000000)}/s"));
				Log.LogDebug((object)($"SkadiNet other effective: payloadVec3={EffectiveConfig.PayloadVec3CullSize:F4} payloadQuatDot={EffectiveConfig.PayloadQuaternionDotThreshold:F4} payloadRefresh={EffectiveConfig.PayloadForceRefreshSeconds:F2}s " + $"compressionThreshold={FormatBytes(EffectiveConfig.CompressionThresholdBytes)} compressionRatio={EffectiveConfig.CompressionMinUsefulRatio:F2} " + $"ownershipScanBudget={EffectiveConfig.OwnershipScanBudget} ownershipScanInterval={EffectiveConfig.OwnershipScanIntervalSeconds:F2}s ownershipCandidateRadius={EffectiveConfig.OwnershipCandidateRadius:F0} " + $"rpcAoiRadius={EffectiveConfig.RpcAoiVisualRadius:F0}"));
			}
		}

		private static string FormatBytes(int bytes)
		{
			if (bytes >= 1048576)
			{
				return $"{(float)bytes / 1048576f:F1}MB";
			}
			if (bytes >= 1024)
			{
				return $"{(float)bytes / 1024f:F1}KB";
			}
			return $"{bytes}B";
		}

		private void Update()
		{
			OwnershipManager.TickLightweight(ZdoReflection.ZDOManInstance);
		}

		private void OnDestroy()
		{
			try
			{
				ClientStutterGuard.Shutdown();
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)$"Failed to unpatch cleanly: {arg}");
				}
			}
			finally
			{
				if (Instance == this)
				{
					Instance = null;
				}
			}
		}
	}
	internal static class GameplayReflection
	{
		internal static object GetZdoFromNView(object nview)
		{
			if (nview == null)
			{
				return null;
			}
			try
			{
				return ReflectionCache.ZNetViewGetZDOMethod?.Invoke(nview, null) ?? ReflectionCache.ZNetViewZdoField?.GetValue(nview);
			}
			catch
			{
				return null;
			}
		}

		internal static object GetNViewFromCharacterLike(object instance)
		{
			if (instance == null)
			{
				return null;
			}
			try
			{
				return ReflectionCache.CharacterNViewField?.GetValue(instance);
			}
			catch
			{
			}
			try
			{
				return ReflectionCache.MonsterAINViewField?.GetValue(instance);
			}
			catch
			{
			}
			try
			{
				return ReflectionCache.CachedField(instance.GetType(), "m_nview")?.GetValue(instance);
			}
			catch
			{
			}
			return null;
		}

		internal static object GetZdoFromCharacterLike(object instance)
		{
			return GetZdoFromNView(GetNViewFromCharacterLike(instance));
		}

		internal static bool TryGetPlayerId(object player, out long id)
		{
			id = 0L;
			if (player == null || ReflectionCache.PlayerGetPlayerIDMethod == null)
			{
				return false;
			}
			try
			{
				return ReflectionCache.TryConvertToLong(ReflectionCache.PlayerGetPlayerIDMethod.Invoke(player, null), out id);
			}
			catch
			{
			}
			return false;
		}

		internal static bool LooksLikePlayer(object obj)
		{
			if (obj == null)
			{
				return false;
			}
			Type type = obj.GetType();
			if (!(type.Name == "Player"))
			{
				if (ReflectionCache.PlayerType != null)
				{
					return ReflectionCache.PlayerType.IsAssignableFrom(type);
				}
				return false;
			}
			return true;
		}
	}
	internal static class NetReflection
	{
		private static Func<object, object> _peerRpcGetter;

		private static Func<object, object> _peerUidGetter;

		private static Func<object, Vector3> _peerRefPosGetter;

		private static Func<object, object> _rpcSocketGetter;

		private static Func<object, object> _socketSendQueueSizeGetter;

		internal static Type ZNetType => ReflectionCache.ZNetType;

		internal static Type ZNetPeerType => ReflectionCache.ZNetPeerType;

		internal static Type ZRpcType => ReflectionCache.ZRpcType;

		internal static Type ZPackageType => ReflectionCache.ZPackageType;

		internal static object ZNetInstance => ReflectionCache.ZNetInstanceField?.GetValue(null);

		internal static void Initialize()
		{
			_peerRpcGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.PeerRpcField);
			_peerUidGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.PeerUidField);
			_peerRefPosGetter = ReflectionDelegateFactory.Vector3FieldGetter(ReflectionCache.PeerRefPosField);
			_rpcSocketGetter = ReflectionDelegateFactory.BoxedInstanceMethod(ReflectionCache.ZRpcGetSocketMethod);
			_socketSendQueueSizeGetter = ReflectionDelegateFactory.BoxedInstanceMethod(ReflectionCache.ZSteamSocketGetSendQueueSizeMethod);
		}

		internal static bool IsServer()
		{
			try
			{
				object zNetInstance = ZNetInstance;
				if (zNetInstance == null || ReflectionCache.ZNetIsServerMethod == null)
				{
					return false;
				}
				return (bool)ReflectionCache.ZNetIsServerMethod.Invoke(zNetInstance, null);
			}
			catch
			{
				return false;
			}
		}

		internal static bool IsDedicatedServer()
		{
			try
			{
				object zNetInstance = ZNetInstance;
				if (zNetInstance == null || ReflectionCache.ZNetIsDedicatedMethod == null)
				{
					return false;
				}
				return (bool)ReflectionCache.ZNetIsDedicatedMethod.Invoke(zNetInstance, null);
			}
			catch
			{
				return false;
			}
		}

		internal static bool TryGetPeerUid(object peerOrRpc, out long uid)
		{
			uid = 0L;
			if (peerOrRpc == null)
			{
				return false;
			}
			try
			{
				if (ReflectionCache.TryConvertToLong(TryGet(_peerUidGetter, peerOrRpc), out uid))
				{
					return true;
				}
				if (ReflectionCache.TryConvertToLong(ReflectionCache.CachedField(peerOrRpc.GetType(), "m_uid")?.GetValue(peerOrRpc), out uid))
				{
					return true;
				}
				return TryGetUidFromPeerObject(GetPeerRpc(peerOrRpc), out uid);
			}
			catch
			{
				return false;
			}
		}

		internal static bool TryGetUidFromPeerObject(object peerOrRpc, out long uid)
		{
			uid = 0L;
			if (peerOrRpc == null)
			{
				return false;
			}
			try
			{
				return ReflectionCache.TryConvertToLong(ReflectionCache.CachedField(peerOrRpc.GetType(), "m_uid")?.GetValue(peerOrRpc), out uid);
			}
			catch
			{
				return false;
			}
		}

		internal static object GetPeerRpc(object peer)
		{
			if (peer == null)
			{
				return null;
			}
			try
			{
				object obj = ReflectionCache.CachedField(peer.GetType(), "m_rpc")?.GetValue(peer);
				if (obj != null)
				{
					return obj;
				}
				object obj2 = TryGet(_peerRpcGetter, peer);
				if (obj2 == null)
				{
					return null;
				}
				return ReflectionCache.CachedField(obj2.GetType(), "m_rpc")?.GetValue(obj2) ?? obj2;
			}
			catch
			{
				return null;
			}
		}

		internal static object GetSocketFromRpc(object rpc)
		{
			if (rpc == null)
			{
				return null;
			}
			try
			{
				return TryGet(_rpcSocketGetter, rpc) ?? ReflectionCache.ZRpcGetSocketMethod?.Invoke(rpc, null);
			}
			catch
			{
				return null;
			}
		}

		internal static Vector3 GetPeerRefPos(object peer)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: 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_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			if (peer == null)
			{
				return Vector3.zero;
			}
			try
			{
				if (TryGetVector3(_peerRefPosGetter, peer, out var value))
				{
					return value;
				}
				if (ReflectionCache.CachedField(peer.GetType(), "m_refPos")?.GetValue(peer) is Vector3 result)
				{
					return result;
				}
				object obj = ReflectionCache.CachedField(peer.GetType(), "m_peer")?.GetValue(peer);
				if (obj != null && obj != peer && ReflectionCache.CachedField(obj.GetType(), "m_refPos")?.GetValue(obj) is Vector3 result2)
				{
					return result2;
				}
			}
			catch
			{
			}
			return Vector3.zero;
		}

		internal static int GetSendQueueSizeForPeer(object zdoPeer)
		{
			try
			{
				object peerRpc = GetPeerRpc(zdoPeer);
				if (peerRpc == null)
				{
					return 0;
				}
				object socketFromRpc = GetSocketFromRpc(peerRpc);
				if (socketFromRpc == null)
				{
					return 0;
				}
				object obj = TryGet(_socketSendQueueSizeGetter, socketFromRpc) ?? ReflectionCache.ZSteamSocketGetSendQueueSizeMethod?.Invoke(socketFromRpc, null