Decompiled source of FiresGhettoNetworking v1.5.72

plugins/FiresGhettoNetworking/VAGhettoNetworking.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using FiresGhettoNetworkMod.Api;
using FiresGhettoNetworkMod.AutoTune;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PlayFab.MultiplayerModels;
using PlayFab.Party;
using Steamworks;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("VAGhettoNetworking")]
[assembly: AssemblyProduct("VAGhettoNetworking")]
[assembly: AssemblyCopyright("Copyright 2025")]
[assembly: ComVisible(false)]
[assembly: Guid("f234bd11-429c-45e2-b280-1fb9121d050d")]
[assembly: AssemblyFileVersion("1.5.26.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.4.67.0")]
[module: UnverifiableCode]
[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 FiresGhettoNetworkMod
{
	[BepInPlugin("com.Fire.FiresGhettoNetworkMod", "FiresGhettoNetworkMod", "1.5.72")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class FiresGhettoNetworkMod : BaseUnityPlugin
	{
		[HarmonyPatch]
		public static class WackyDatabaseCompatibilityPatch
		{
			public static void Init(Harmony harmony)
			{
				//IL_0053: Unknown result type (might be due to invalid IL or missing references)
				//IL_0061: Expected O, but got Unknown
				Type type = Type.GetType("wackydatabase.Util.Functions, WackysDatabase");
				if (type == null)
				{
					LoggerOptions.LogInfo("WackyDatabase not detected — skipping compatibility patch.");
					return;
				}
				MethodInfo method = type.GetMethod("SnapshotItem", BindingFlags.Static | BindingFlags.Public);
				if (method == null)
				{
					LoggerOptions.LogWarning("WackyDatabase detected but SnapshotItem method not found — patch skipped.");
					return;
				}
				harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(WackyDatabaseCompatibilityPatch), "SnapshotItem_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				LoggerOptions.LogInfo("WackyDatabase compatibility patch applied — will skip snapshots for invalid/broken clones.");
			}

			[HarmonyPrefix]
			public static bool SnapshotItem_Prefix(ref ItemDrop item)
			{
				if ((Object)(object)item == (Object)null)
				{
					LoggerOptions.LogWarning("WDB: Skipping snapshot for null ItemDrop (likely broken clone).");
					return false;
				}
				if ((Object)(object)((Component)item).gameObject == (Object)null)
				{
					LoggerOptions.LogWarning("WDB: Skipping snapshot for " + ((Object)item).name + " — gameObject is null (missing prefab from removed mod).");
					return false;
				}
				bool num = ((Component)item).GetComponentsInChildren<Renderer>(true).Length != 0;
				bool flag = ((Component)item).GetComponentsInChildren<MeshFilter>(true).Length != 0;
				if (!num && !flag)
				{
					LoggerOptions.LogWarning("WDB: Skipping snapshot for " + ((Object)item).name + " — no renderers or meshes (broken model).");
					return false;
				}
				return true;
			}
		}

		public const string PluginGUID = "com.Fire.FiresGhettoNetworkMod";

		public const string PluginName = "FiresGhettoNetworkMod";

		public const string PluginVersion = "1.5.72";

		public const string ValheimPerformanceOptimizationsGuid = "dev.ontrigger.vpo";

		public static ManualLogSource Log;

		public static ConfigEntry<LogLevel> ConfigLogLevel;

		public static ConfigEntry<bool> ConfigEnableCompression;

		public static ConfigEntry<UpdateRateOptions> ConfigUpdateRate;

		public static ConfigEntry<int> ConfigSendRateMin;

		public static ConfigEntry<int> ConfigSendRateMax;

		public const int DefaultSendRateMinKb = 512;

		public const int DefaultSendRateMaxKb = 2048;

		public const int SendRateKbLow = 4;

		public const int SendRateKbHigh = 131072;

		public static ConfigEntry<bool> ConfigAdaptiveUpload;

		public static ConfigEntry<QueueSizeOptions> ConfigQueueSize;

		public static ConfigEntry<ForceCrossplayOptions> ConfigForceCrossplay;

		public static ConfigEntry<int> ConfigPlayerLimit;

		public static ConfigEntry<int> ConfigAdvertisedPlayerLimit;

		public static ConfigEntry<bool> ConfigEnableShipFixes;

		public static ConfigEntry<bool> ConfigEnableServerSideShipSimulation;

		public static ConfigEntry<int> ConfigExtendedZoneRadius;

		public static ConfigEntry<bool> ConfigEnableZDOThrottling;

		public static ConfigEntry<float> ConfigZDOThrottleDistance;

		public static ConfigEntry<bool> ConfigEnableAILOD;

		public static ConfigEntry<float> ConfigAILODNearDistance;

		public static ConfigEntry<float> ConfigAILODFarDistance;

		public static ConfigEntry<float> ConfigAILODThrottleFactor;

		public static ConfigEntry<bool> ConfigEnableAdaptiveThrottling;

		public static ConfigEntry<int> ConfigSendCongestionThresholdPct;

		public static ConfigEntry<bool> ConfigEnableSendHeartbeatLog;

		public static ConfigEntry<bool> ConfigEnableFallThroughDiagnostics;

		public static ConfigEntry<bool> ConfigEnableCapeCrashDiagnostics;

		public static ConfigEntry<int> ConfigZoneLoadBatchSize;

		public static ConfigEntry<int> ConfigZPackageReceiveBufferSize;

		public static ConfigEntry<bool> ConfigEnableTimeSliceInstantiation;

		public static ConfigEntry<int> ConfigInstantiationBudgetMs;

		public static ConfigEntry<int> ConfigMaxInstancesPerFrame;

		public static ConfigEntry<int> ConfigServerInstantiationBudgetMs;

		public static ConfigEntry<int> ConfigServerPassHz;

		public static ConfigEntry<bool> ConfigSafetyFallbackEnabled;

		public static ConfigEntry<int> ConfigSafetyFallbackThreshold;

		public static ConfigEntry<bool> ConfigEnablePredictiveZoneStreaming;

		public static ConfigEntry<float> ConfigPredictionLookaheadSec;

		public static ConfigEntry<float> ConfigPredictionMinVelocity;

		public static ConfigEntry<int> ConfigPredictionMaxLookaheadZones;

		public static ConfigEntry<bool> ConfigEnableInvulnerableSupportSkip;

		public static ConfigEntry<bool> ConfigEnableInstanceOrphanPrune;

		public static ConfigEntry<bool> ConfigFixTeleportGhosts;

		public static ConfigEntry<bool> ConfigFixSlowSleep;

		public static ConfigEntry<bool> ConfigKeepaliveFirst;

		public static ConfigEntry<bool> ConfigKeepWorldClockAtRealTime;

		public static ConfigEntry<bool> ConfigSmoothServerClockCorrections;

		public static ConfigEntry<bool> ConfigFixGroundSnapThroughFloors;

		public static ConfigEntry<bool> ConfigEnableRpcRouter;

		public static ConfigEntry<bool> ConfigEnableRpcAoI;

		public static ConfigEntry<float> ConfigRpcAoIRadius;

		public static ConfigEntry<bool> ConfigEnableZDODelta;

		public static ConfigEntry<bool> ConfigAllocationFreeZdoWrites;

		public static ConfigEntry<bool> ConfigEnableWNTServerOptimization;

		public static ConfigEntry<bool> ConfigEnableServerAuthority;

		public static ConfigEntry<bool> ConfigEnableServerOwnership;

		public static ConfigEntry<bool> ConfigEnableServerOwnershipSelective;

		public static ConfigEntry<bool> ConfigShowAILODInServerStatus;

		public static ConfigEntry<bool> ConfigEnableBootPatchVerification;

		public static ConfigEntry<bool> ConfigEnableLargeZdoDiagnostics;

		public static ConfigEntry<int> ConfigClientMaxDestroysPerFrame;

		public static ConfigEntry<string> ConfigDediFellOutRescueLayers;

		public static ConfigEntry<float> ConfigDiagnosticIntervalSec;

		public static ConfigEntry<bool> ConfigEnableBulkTransferBoost;

		public static ConfigEntry<int> ConfigBulkTransferBudgetPercent;

		public static ConfigEntry<bool> ConfigHyperBoost;

		private const float JoinAddressWaitSeconds = 60f;

		internal static Harmony Harmony { get; private set; }

		public static FiresGhettoNetworkMod Instance { get; private set; }

		private void Awake()
		{
			try
			{
				AwakeCore();
			}
			catch (Exception ex) when (ex is TypeLoadException || ex is MissingMemberException || ex is FileNotFoundException)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("FiresGhettoNetworkMod could not finish loading on this game build (" + ex.GetType().Name + ": " + ex.Message + "). Please report it with this log."));
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void AwakeCore()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Harmony = new Harmony("com.Fire.FiresGhettoNetworkMod");
			try
			{
				VAGhettoBanner.PrintBig();
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[FiresGhettoNetworkMod] VAGhettoBanner.PrintBig failed: " + ex.Message));
			}
			try
			{
				FiresLogColorPatch.Install(Harmony);
			}
			catch (Exception ex2)
			{
				Debug.LogWarning((object)("[FiresGhettoNetworkMod] FiresLogColorPatch.Install failed: " + ex2.Message));
			}
			BindConfigs();
			try
			{
				((BaseUnityPlugin)this).Config.Save();
			}
			catch (Exception ex3)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to save config file immediately: " + ex3.Message));
			}
			LoggerOptions.Init(((BaseUnityPlugin)this).Logger);
			TierPresets.ValidateVanillaFloors();
			ServerClientUtils.Detect(((BaseUnityPlugin)this).Logger);
			bool isDedicatedServerDetected = ServerClientUtils.IsDedicatedServerDetected;
			if (isDedicatedServerDetected)
			{
				LoggerOptions.LogMessage("FiresGhettoNetworkMod v1.5.72 — Running on DEDICATED SERVER, enabling server-side features.");
			}
			else
			{
				LoggerOptions.LogMessage("FiresGhettoNetworkMod v1.5.72 — Running on CLIENT or SINGLE-PLAYER/LISTEN SERVER, only client-safe features will be applied.");
			}
			ValheimCommunityPatchCompat.Detect();
			InvokeStaticInitByTypeName("FiresGhettoNetworkMod.CompressionGroup", "InitConfig", new object[1] { ((BaseUnityPlugin)this).Config });
			InvokeStaticInitByTypeName("FiresGhettoNetworkMod.NetworkingRatesGroup", "Init", new object[1] { ((BaseUnityPlugin)this).Config });
			InvokeStaticInitByTypeName("FiresGhettoNetworkMod.DedicatedServerGroup", "Init", new object[1] { ((BaseUnityPlugin)this).Config });
			SendQueueHeadroomMonitor.InitConfig(((BaseUnityPlugin)this).Config);
			AdaptiveSendRate.InitConfig(((BaseUnityPlugin)this).Config);
			LinkController.InitConfig(((BaseUnityPlugin)this).Config);
			SendScheduler.InitConfig(((BaseUnityPlugin)this).Config);
			StationRouter.InitConfig(((BaseUnityPlugin)this).Config);
			CreatureOwnership.InitConfig(((BaseUnityPlugin)this).Config);
			HelmOwnership.InitConfig(((BaseUnityPlugin)this).Config);
			TargetSync.InitConfig(((BaseUnityPlugin)this).Config);
			SectorChangeTracker.InitConfig(((BaseUnityPlugin)this).Config);
			FireplaceFuelTicks.InitConfig(((BaseUnityPlugin)this).Config);
			TransformWriteRate.InitConfig(((BaseUnityPlugin)this).Config);
			SyncListRefPosPatches.InitConfig(((BaseUnityPlugin)this).Config);
			JoinGrace.InitConfig(((BaseUnityPlugin)this).Config);
			PlayFabZlibWorker.InitConfig(((BaseUnityPlugin)this).Config);
			LagFairDodge.InitConfig(((BaseUnityPlugin)this).Config);
			LogoutHold.InitConfig(((BaseUnityPlugin)this).Config);
			RemoteMotion.InitConfig(((BaseUnityPlugin)this).Config);
			RemoteArrows.InitConfig(((BaseUnityPlugin)this).Config);
			FgnVersionGate.InitConfig(((BaseUnityPlugin)this).Config);
			OwnerSafeRevisions.InitConfig(((BaseUnityPlugin)this).Config);
			PickupClaimGuard.InitConfig(((BaseUnityPlugin)this).Config);
			FgnApi.InitConfig(((BaseUnityPlugin)this).Config);
			DropStaleConfigKeys();
			try
			{
				((BaseUnityPlugin)this).Config.Save();
			}
			catch (Exception ex4)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to save the tidied config: " + ex4.Message));
			}
			if (!SteamSide.Present)
			{
				LoggerOptions.LogMessage("Steam networking not present: FGN's Steam features off, the rest on");
			}
			TryPatchAll(typeof(CompressionGroup));
			TryPatchAll(typeof(NetworkingRatesGroup));
			if (SteamSide.Present)
			{
				TryPatchAll(typeof(NetworkingRatesGroup.SteamConnectPatch));
			}
			TryPatchAll(typeof(DedicatedServerGroup));
			if (SteamSide.Present)
			{
				TryPatchAll(typeof(DedicatedServerGroup.SteamPatches));
			}
			TryPatchAll(typeof(PlayFabJoinCodeGuard));
			TryPatchAll(typeof(ServerSafePick));
			TryPatchAll(typeof(SyncListRefPosPatches));
			TryPatchAll(typeof(UploadBreakdown));
			TryPatchAll(typeof(DownloadBreakdown));
			TryPatchAll(typeof(SendZDOsHeartbeatDiagnostic));
			if (ConfigEnableFallThroughDiagnostics.Value)
			{
				TryPatchAll(typeof(FallThroughProbe));
				TryPatchAll(typeof(PieceTypeAudit));
			}
			TryPatchAll(typeof(ServerDisconnectDiagnostics));
			if (SteamSide.Present)
			{
				TryPatchAll(typeof(CloseWithoutSleep));
			}
			PlayFabZlibWorker.Apply();
			TryPatchAll(typeof(BulkTransferGatePatches));
			TryPatchAll(typeof(BigZdoDiagnostic));
			TryPatchAll(typeof(FireplaceFuelTicks));
			ZSyncHookGate.Init(TransformWriteRate.ConfigEnabled, TransformWriteRate.ConfigPrefabs, TransformWriteRate.ConfigWritesPerSecond, RemoteMotion.ConfigSendStamps);
			ZdoWireWriter.Initialize();
			TryPatchAll(typeof(ZdoWireWriter));
			if (ConfigEnableZDODelta.Value)
			{
				TryPatchAll(typeof(ZDODeltaPatches));
				LoggerOptions.LogInfo("ZDO delta compression enabled.");
			}
			TryPatchAll(typeof(DisarmOverloadTest));
			if (SteamSide.Present)
			{
				TryPatchAll(typeof(DisarmOverloadTest.SteamPatch));
			}
			TryPatchAll(typeof(CompressionRoundTripTest));
			TryPatchAll(typeof(SocketStressTests));
			TryPatchAll(typeof(SendQueueHeadroomMonitor));
			TryPatchAll(typeof(LinkController));
			TryPatchAll(typeof(NetSim));
			TryPatchAll(typeof(LogoutHold));
			TryPatchAll(typeof(RemoteArrows));
			TryPatchAll(typeof(SendScheduler));
			TryPatchAll(typeof(ConnectionEcho));
			TryPatchAll(typeof(CreatureOwnership));
			TryPatchAll(typeof(HelmOwnership));
			TryPatchAll(typeof(TargetSync));
			if (SteamSide.Present)
			{
				TryPatchAll(typeof(KeepaliveFirst));
			}
			TryPatchAll(typeof(JoinGrace));
			try
			{
				TryPatchAll(typeof(RoundTripTrace));
			}
			catch (Exception ex5)
			{
				LoggerOptions.LogWarning("[RoundTrip] could not be attached; round trips are still timed, without the per-stage breakdown. " + ex5.Message);
			}
			TryPatchAll(typeof(ZdoFloodTest));
			WackyDatabaseCompatibilityPatch.Init(Harmony);
			TryPatchAll(typeof(PlayerPositionSyncPatches));
			TryPatchAll(typeof(WearNTearClientSupportPatches));
			TryPatchAll(typeof(ClientCleanupThrottle));
			TryPatchAll(typeof(GroundSnapPatches));
			TryPatchAll(typeof(ZSyncHookGate));
			TryPatchAll(typeof(OwnedPositionEcho));
			TryPatchAll(typeof(OwnerSafeRevisions));
			TryPatchAll(typeof(PickupClaimGuard));
			TryPatchAll(typeof(PendingServerKeys));
			OwnerSafeRevisions.MainThreadId = Thread.CurrentThread.ManagedThreadId;
			try
			{
				TeleportGhostFix.Init();
				TryPatchAll(typeof(TeleportGhostFix));
			}
			catch (Exception ex6)
			{
				LoggerOptions.LogWarning("[TeleportGhostFix] could not be attached; vanilla behaviour is unchanged. " + ex6.Message);
			}
			try
			{
				TryPatchAll(typeof(TerrainCompInitRace));
			}
			catch (Exception ex7)
			{
				LoggerOptions.LogWarning("[TerrainComp] could not be attached; vanilla behaviour is unchanged. " + ex7.Message);
			}
			try
			{
				TryPatchAll(typeof(SpawnHeightmapGuard));
			}
			catch (Exception ex8)
			{
				LoggerOptions.LogWarning("[Spawn] heightmap guard could not be attached; vanilla behaviour is unchanged. " + ex8.Message);
			}
			TryPatchAll(typeof(SleepTimeSkipFix));
			if (!isDedicatedServerDetected)
			{
				TryPatchAll(typeof(WorldClock));
			}
			if (!isDedicatedServerDetected && ConfigEnableCapeCrashDiagnostics.Value)
			{
				TryPatchAll(typeof(CapeCrashDiagnostics));
				TryPatchAll(typeof(MagicaColliderRegistrationDiagnostics));
				TryPatchAll(typeof(MagicaClothLifecycleDiagnostics));
				LoggerOptions.LogWarning("[CapeDiag] Cape crash diagnostics are ON; turn 'Enable Cape Crash Diagnostics' off once the crash is found.");
			}
			TryPatchAll(typeof(AutoTuneProbeHooks));
			TryPatchAll(typeof(ZoneLoadPatches));
			ServerAutoTune.InitServerSide();
			if (isDedicatedServerDetected)
			{
				ApplyServerTrafficPatches();
				bool simulationActive = false;
				if (!ConfigEnableServerAuthority.Value)
				{
					LoggerOptions.LogInfo("Server-side simulation disabled via ConfigEnableServerAuthority = false.");
				}
				else if (ValheimCommunityPatchCompat.SchedulesSceneObjects)
				{
					LoggerOptions.LogWarning("Server-Side Simulation is OFF for this session: ValheimCommunityPatch replaces the server's object create/destroy pass with its own, built around world origin, and destroys every object FGN creates for players. Remove ValheimCommunityPatch from the server to use Server-Side Simulation, or turn 'Enable Server-Side Simulation' off to silence this warning.");
				}
				else if (Chainloader.PluginInfos.ContainsKey("dev.ontrigger.vpo"))
				{
					LoggerOptions.LogWarning("Server-Side Simulation (and ZDO ownership transfer) is OFF for this session: ValheimPerformanceOptimizations replaces the server's object create/destroy pass and its ownership pass with its own, built around the server's reference position, so it would undo what FGN does for players. Remove ValheimPerformanceOptimizations from the server to use Server-Side Simulation, or turn 'Enable Server-Side Simulation' off to silence this warning.");
				}
				else
				{
					ApplyServerSideSimulationPatches();
					ServerAuthorityPatches.SimulationActive = true;
					simulationActive = true;
					ZSyncHookGate.Refresh("Server-Side Simulation started");
				}
				EmitServerFeatureSummary(simulationActive);
			}
			else
			{
				LoggerOptions.LogInfo("Server-side features skipped — not running on a dedicated server.");
			}
			Type[] array = FgnHooks.ReadAttachedFeatures();
			for (int i = 0; i < array.Length; i++)
			{
				TryPatchAll(array[i]);
			}
			((MonoBehaviour)this).StartCoroutine(EmitCompactBannerWhenZNetReady());
			TmlGuard.Capture();
			((MonoBehaviour)this).StartCoroutine(TmlGuard.Run((MonoBehaviour)(object)this));
		}

		private static void ApplyServerTrafficPatches()
		{
			TryPatchAll(typeof(ServerFrameProfile));
			TryPatchAll(typeof(SectorChangeTracker));
			TryPatchAll(typeof(ZDOThrottlingPatches));
			TryPatchAll(typeof(AILODPatches));
			TryPatchAll(typeof(RpcRouterPatches));
			TryPatchAll(typeof(StationRouter));
			DamageTextHandler.Register();
			if (ConfigEnableRpcRouter.Value || StationRouter.ConfigEnabled.Value)
			{
				VAGhettoLoadSummary.EmitRpcRouter(RoutedRpcManager.HandlerCount, RoutedRpcManager.PositionRadius(), RoutedRpcManager.FilteringEnabled());
				if (VAGhettoLoadSummary.VerboseEnabled)
				{
					LoggerOptions.LogInfo("RPC Router enabled — handlers: " + string.Join(", ", RoutedRpcManager.HandlerMethodNames) + ".");
				}
			}
			if (ConfigEnableWNTServerOptimization.Value)
			{
				TryPatchAll(typeof(WearNTearServerPatches));
				LoggerOptions.LogInfo("WearNTear server optimization enabled.");
			}
		}

		private static void ApplyServerSideSimulationPatches()
		{
			if (ConfigEnableShipFixes.Value)
			{
				TryPatchAll(typeof(ShipFixesGroup));
			}
			TryPatchAll(typeof(ServerShipSimulationPatches));
			TryPatchAll(typeof(ServerAuthorityPatches));
			TryPatchAll(typeof(SssQuietPass));
			TryPatchAll(typeof(ServerStabilityPatches));
			TryPatchAll(typeof(MonsterAIPatches));
			if (ConfigEnableServerOwnershipSelective.Value)
			{
				if (ConfigEnableServerOwnership.Value)
				{
					LoggerOptions.LogWarning("Both 'Server ZDO Ownership Transfer' flags are ENABLED — selective (V3) takes precedence; broad (V2) is being ignored. Disable one to silence this warning.");
				}
				TryPatchAll(typeof(ServerOwnershipPatchesV3));
				CreatureOwnership.ServerOwnsCreatures = true;
				HelmOwnership.ServerOwnsShips = ConfigEnableServerSideShipSimulation.Value;
				LoggerOptions.LogMessage("Server ZDO ownership (V3 SELECTIVE) ENABLED — Character/Ship only; drops/voxel/interactables/carts stay peer-owned.");
			}
			else if (ConfigEnableServerOwnership.Value)
			{
				TryPatchAll(typeof(ServerOwnershipPatches));
				CreatureOwnership.ServerOwnsCreatures = true;
				HelmOwnership.ServerOwnsShips = true;
				LoggerOptions.LogMessage("Server ZDO ownership (V2 BROAD SSS-exact) ENABLED — every persistent ZDO in any peer's active area will be claimed by the server.");
				if (!ConfigEnableServerSideShipSimulation.Value)
				{
					LoggerOptions.LogWarning("V2 BROAD claims SHIPS as well, regardless of 'Server-Side Ship Simulation' being off — it is a deliberate verbatim port with no per-prefab exclusions. A server-owned hull runs its own physics, and ImpactEffect only fires for the owner, so boats can take phantom damage on calm water. Use the Selective (V3) toggle instead if your players sail; it honours that setting.");
				}
			}
			else
			{
				LoggerOptions.LogInfo("Server ZDO ownership transfer disabled (both V2 and V3 flags = false). Vanilla peer ownership in effect.");
			}
			if (ConfigEnableBootPatchVerification.Value)
			{
				DumpPatchInfo(typeof(BaseAI), "UpdateAI");
				DumpPatchInfo(typeof(BaseAI), "Awake");
				DumpPatchInfo(typeof(MonsterAI), "UpdateAI");
				DumpPatchInfo(typeof(Character), "CustomFixedUpdate");
				DumpPatchInfo(typeof(MonoUpdaters), "FixedUpdate");
				DumpPatchInfo(typeof(ZNetScene), "CreateDestroyObjects");
				DumpPatchInfo(typeof(ZNetScene), "CreateObject");
				DumpPatchInfo(typeof(ZoneSystem), "IsActiveAreaLoaded");
				DumpPatchInfo(typeof(SpawnSystem), "UpdateSpawning");
				DumpPatchInfo(typeof(ShieldDomeImageEffect), "Awake");
				DumpPatchInfo(typeof(Heightmap), "RebuildRenderMesh");
			}
			if (VAGhettoLoadSummary.VerboseEnabled)
			{
				LoggerOptions.LogInfo("All server-side simulation patches enabled.");
			}
		}

		private static void EmitServerFeatureSummary(bool simulationActive)
		{
			string ownership = (ConfigEnableServerOwnershipSelective.Value ? "V3 selective" : (ConfigEnableServerOwnership.Value ? "V2 broad" : "vanilla peer"));
			VAGhettoLoadSummary.EmitServerAuthority(simulationActive, ownership, ConfigEnableZDODelta?.Value ?? false, ConfigEnableZDOThrottling?.Value ?? false, ConfigEnableAILOD?.Value ?? false, ConfigEnableWNTServerOptimization?.Value ?? false);
		}

		private IEnumerator EmitCompactBannerWhenZNetReady()
		{
			while ((Object)(object)ZNetScene.instance == (Object)null || ZNetScene.instance.m_prefabs == null || ZNetScene.instance.m_prefabs.Count == 0)
			{
				yield return null;
			}
			yield return (object)new WaitForEndOfFrame();
			string address = null;
			bool dedicated = ServerClientUtils.IsDedicatedServerDetected;
			if (dedicated)
			{
				float giveUpAt = Time.realtimeSinceStartup + 60f;
				while (!TryGetJoinAddress(out address) && Time.realtimeSinceStartup < giveUpAt)
				{
					yield return (object)new WaitForSecondsRealtime(0.5f);
				}
			}
			try
			{
				VAGhettoBanner.Print();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("FiresGhettoNetworkMod v1.5.72 loaded. (banner failed: " + ex.Message + ")"));
			}
			if (dedicated)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)((address != null) ? ("Join address: " + address) : $"Join address: this server's public IP, port {ServerPort()} (Steam did not report the public IP within {60f:0} s)."));
			}
		}

		private static bool TryGetJoinAddress(out string address)
		{
			address = null;
			int num = ServerPort();
			if (num <= 0)
			{
				return false;
			}
			if (SteamSide.Present)
			{
				return TryGetSteamJoinAddress(num, out address);
			}
			return false;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static bool TryGetSteamJoinAddress(int port, out string address)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			address = null;
			try
			{
				if (!SteamGameServer.BLoggedOn())
				{
					return false;
				}
				SteamIPAddress_t publicIP = SteamGameServer.GetPublicIP();
				if (!((SteamIPAddress_t)(ref publicIP)).IsSet())
				{
					return false;
				}
				IPAddress iPAddress = ((SteamIPAddress_t)(ref publicIP)).ToIPAddress();
				address = ((iPAddress.AddressFamily == AddressFamily.InterNetworkV6) ? $"[{iPAddress}]:{port}" : $"{iPAddress}:{port}");
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static int ServerPort()
		{
			string[] commandLineArgs = Environment.GetCommandLineArgs();
			for (int i = 0; i < commandLineArgs.Length - 1; i++)
			{
				if (string.Equals(commandLineArgs[i], "-port", StringComparison.OrdinalIgnoreCase) && int.TryParse(commandLineArgs[i + 1], out var result) && result > 0)
				{
					return result;
				}
			}
			return 2456;
		}

		private static void TryPatchAll(Type type)
		{
			if (type == null)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)"Tried to patch a null type!");
				}
				return;
			}
			try
			{
				Harmony.PatchAll(type);
				FgnHooks.NoteAttached(type);
			}
			catch (Exception ex)
			{
				Exception ex2 = ex.InnerException ?? ex;
				ManualLogSource log2 = Log;
				if (log2 != null)
				{
					log2.LogError((object)("[FiresGhettoNetworkMod] couldn't attach " + type.Name + " (" + ex2.GetType().Name + ": " + ex2.Message + "); that feature is off, the rest of FGN loads normally."));
				}
			}
		}

		private static void DumpPatchInfo(Type type, string methodName)
		{
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				LoggerOptions.LogMessage("[PatchVerify] " + type.FullName + "." + methodName + " → method NOT FOUND by AccessTools.");
				return;
			}
			Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo);
			if (patchInfo == null)
			{
				LoggerOptions.LogMessage("[PatchVerify] " + type.FullName + "." + methodName + " → NO patches attached (info=null).");
				return;
			}
			int num = (patchInfo.Prefixes?.Count ?? 0) + (patchInfo.Postfixes?.Count ?? 0) + (patchInfo.Transpilers?.Count ?? 0) + (patchInfo.Finalizers?.Count ?? 0);
			LoggerOptions.LogMessage($"[PatchVerify] {type.FullName}.{methodName} → total={num} (prefixes={patchInfo.Prefixes?.Count ?? 0}, postfixes={patchInfo.Postfixes?.Count ?? 0}, transpilers={patchInfo.Transpilers?.Count ?? 0}, finalizers={patchInfo.Finalizers?.Count ?? 0}).");
			DumpList("Prefix", patchInfo.Prefixes);
			DumpList("Postfix", patchInfo.Postfixes);
			DumpList("Transpiler", patchInfo.Transpilers);
			DumpList("Finalizer", patchInfo.Finalizers);
			static void DumpList(string kind, IEnumerable<Patch> ps)
			{
				if (ps == null)
				{
					return;
				}
				foreach (Patch p in ps)
				{
					LoggerOptions.LogMessage($"[PatchVerify]   [{kind}] owner='{p.owner}' method={p.PatchMethod.DeclaringType?.FullName}.{p.PatchMethod.Name} prio={p.priority}");
				}
			}
		}

		private void InvokeStaticInitByTypeName(string typeName, string methodName, object[] args)
		{
			try
			{
				Type type = Type.GetType(typeName);
				if (type == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Type " + typeName + " not found; skipping " + methodName + "."));
					return;
				}
				MethodInfo method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (method == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Method " + methodName + " not found on " + typeName + "."));
				}
				else
				{
					method.Invoke(null, args);
				}
			}
			catch (TypeLoadException arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"TypeLoadException while invoking {typeName}.{methodName}: {arg}");
			}
			catch (Exception arg2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Exception while invoking {typeName}.{methodName}: {arg2}");
			}
		}

		private int MigrateLegacyKb(string section, string key, int defaultKb)
		{
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Expected O, but got Unknown
			int num = -1;
			try
			{
				string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
				if (!string.IsNullOrEmpty(configFilePath) && File.Exists(configFilePath))
				{
					string text = null;
					string[] array = File.ReadAllLines(configFilePath);
					for (int i = 0; i < array.Length; i++)
					{
						string text2 = array[i].Trim();
						if (text2.StartsWith("[") && text2.EndsWith("]"))
						{
							text = text2.Substring(1, text2.Length - 2).Trim();
						}
						else
						{
							if (text != section || text2.Length == 0 || text2.StartsWith("#"))
							{
								continue;
							}
							int num2 = text2.IndexOf('=');
							if (num2 > 0 && !(text2.Substring(0, num2).Trim() != key))
							{
								Match match = Regex.Match(text2.Substring(num2 + 1).Trim(), "^_(\\d+)KB$");
								if (match.Success)
								{
									num = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
								}
								break;
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[Config] Could not read " + key + " for migration: " + ex.Message));
			}
			if (num <= 0)
			{
				return defaultKb;
			}
			try
			{
				if (typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(((BaseUnityPlugin)this).Config) is Dictionary<ConfigDefinition, string> dictionary)
				{
					ConfigDefinition key2 = new ConfigDefinition(section, key);
					if (dictionary.ContainsKey(key2))
					{
						dictionary[key2] = num.ToString(CultureInfo.InvariantCulture);
					}
				}
			}
			catch
			{
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"[Config] {key}: migrated from the old option _{num}KB to {num} KB/s.");
			return Mathf.Clamp(num, 4, 131072);
		}

		private void DropStaleConfigKeys()
		{
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Expected O, but got Unknown
			try
			{
				if (!(typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(((BaseUnityPlugin)this).Config) is Dictionary<ConfigDefinition, string> dictionary))
				{
					return;
				}
				List<ConfigDefinition> list = new List<ConfigDefinition>();
				foreach (ConfigDefinition key in dictionary.Keys)
				{
					bool num = key.Section == "10 - Server Authority" && key.Key.IndexOf('â') >= 0;
					bool flag = key.Section == "12 - Advanced" && (key.Key == "Fix Boat Damage From Server Time Sync" || key.Key == "Max Active ZDOs");
					bool flag2 = key.Section == "10 - Diagnostics";
					if (num || flag || flag2)
					{
						list.Add(key);
					}
				}
				foreach (ConfigDefinition item in list)
				{
					ConfigDefinition val = new ConfigDefinition("14 - Diagnostics", item.Key);
					if (item.Section == "10 - Diagnostics" && ((BaseUnityPlugin)this).Config.ContainsKey(val))
					{
						ConfigEntryBase val2 = ((BaseUnityPlugin)this).Config[val];
						if (val2 != null)
						{
							string text = dictionary[item];
							if (text != val2.GetSerializedValue())
							{
								val2.SetSerializedValue(text);
								((BaseUnityPlugin)this).Logger.LogInfo((object)("[Config] '" + item.Key + "' moved from [10 - Diagnostics] to [14 - Diagnostics]; your value '" + text + "' was kept."));
							}
						}
					}
					dictionary.Remove(item);
				}
				if (list.Count > 0)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)$"[Config] removed {list.Count} setting(s) FGN no longer uses from the cfg file.");
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[Config] could not tidy old settings: " + ex.Message));
			}
		}

		private void BindConfigs()
		{
			ConfigLogLevel = ((BaseUnityPlugin)this).Config.Bind<LogLevel>("01 - General", "Log Level", LogLevel.Message, Cfg.Basic("How much FGN writes to the log. Message is normal; Info and Debug are for troubleshooting."));
			ConfigZoneLoadBatchSize = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Zone Load Batch Size", 1, Cfg.Adv("How many objects load per frame when an area loads in (1 = vanilla). Higher = faster, bigger hitches.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8)));
			PlayerPositionSyncPatches.Init(((BaseUnityPlugin)this).Config);
			ConfigEnableCompression = ((BaseUnityPlugin)this).Config.Bind<bool>("04 - Networking", "Enable Compression", true, Cfg.Basic("Compresses network traffic. Works when both server and player run FGN."));
			ConfigUpdateRate = ((BaseUnityPlugin)this).Config.Bind<UpdateRateOptions>("04 - Networking", "ZDO Send Rate", UpdateRateOptions._100, Cfg.Adv("How often world updates are sent (100% = vanilla). Auto-Tune overwrites this while on."));
			ConfigSendRateMin = ((BaseUnityPlugin)this).Config.Bind<int>("05 - Networking - Steamworks", "Send Rate Min", MigrateLegacyKb("05 - Networking - Steamworks", "Send Rate Min", 512), Cfg.Adv("Lowest Steam send rate in KB/s. Auto-Tune overwrites this while on.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(4, 131072)));
			ConfigSendRateMax = ((BaseUnityPlugin)this).Config.Bind<int>("05 - Networking - Steamworks", "Send Rate Max", MigrateLegacyKb("05 - Networking - Steamworks", "Send Rate Max", 2048), Cfg.Adv("Highest Steam send rate in KB/s (your upload ceiling). Auto-Tune overwrites this while on.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(4, 131072)));
			ConfigAdaptiveUpload = ((BaseUnityPlugin)this).Config.Bind<bool>("04 - Networking", "Adaptive Upload", true, Cfg.Adv("Backs your send rate off when your connection struggles instead of flooding it. Steam only."));
			ConfigHyperBoost = ((BaseUnityPlugin)this).Config.Bind<bool>("05 - Networking - Steamworks", "HYPERBOOST", false, Cfg.Adv("Max-throughput mode for very fast links and benchmarks. Leave off for normal play."));
			AutoTuneConfig.Init(((BaseUnityPlugin)this).Config);
			ConfigQueueSize = ((BaseUnityPlugin)this).Config.Bind<QueueSizeOptions>("04 - Networking", "Queue Size", QueueSizeOptions._32KB, Cfg.Adv("Biggest batch of world updates sent to one player at once. Auto-Tune overwrites this while on."));
			ConfigForceCrossplay = ((BaseUnityPlugin)this).Config.Bind<ForceCrossplayOptions>("09 - Dedicated Server", "Force Crossplay", ForceCrossplayOptions.vanilla, Cfg.Basic("Server backend: vanilla follows -crossplay, steamworks = Steam only, playfab = crossplay. Restart."));
			ConfigPlayerLimit = ((BaseUnityPlugin)this).Config.Bind<int>("09 - Dedicated Server", "Player Limit", 10, Cfg.Basic("Max players on a dedicated server. Restart to apply.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 999)));
			ConfigAdvertisedPlayerLimit = ((BaseUnityPlugin)this).Config.Bind<int>("09 - Dedicated Server", "Advertised Player Limit", 0, Cfg.Basic("Player cap shown in server lists (0 = same as Player Limit). Restart to apply.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 9999)));
			ConfigZPackageReceiveBufferSize = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "ZPackage Receive Buffer Bytes", 262144, Cfg.Adv("Steam receive buffer in bytes when Auto-Tune is off (raised to at least 2 MB).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(65536, 4194304)));
			ConfigEnableTimeSliceInstantiation = ((BaseUnityPlugin)this).Config.Bind<bool>("02 - Client Performance", "Enable Time-Slice Instantiation", false, Cfg.Adv("Spreads loading a busy area over several frames instead of one big hitch."));
			ConfigInstantiationBudgetMs = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Instantiation Budget Ms", 3, Cfg.Adv("Milliseconds per frame spent loading objects with time-slicing. Lower = smoother, higher = faster.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16)));
			ConfigMaxInstancesPerFrame = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Max Instances Per Frame", 100, Cfg.Adv("Most objects loaded in one frame with time-slicing.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 500)));
			ConfigSafetyFallbackEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("02 - Client Performance", "Safety Fallback Enabled", true, Cfg.Adv("Loads faster when a huge backlog is waiting, like after teleporting into a big base."));
			ConfigSafetyFallbackThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Safety Fallback Threshold", 5000, Cfg.Adv("Backlog size at which the safety fallback kicks in.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 50000)));
			ConfigEnableRpcRouter = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable RPC Router", true, Cfg.Adv("Server relays hits and effects instead of everyone sending to everyone. Restart."));
			ConfigEnableShipFixes = ((BaseUnityPlugin)this).Config.Bind<bool>("11 - Ship Fixes", "Enable Universal Ship Fixes", true, Cfg.Adv("Keeps a server-run ship's speed and rudder as the helmsman set them. Restart."));
			ConfigEnableServerSideShipSimulation = ((BaseUnityPlugin)this).Config.Bind<bool>("11 - Ship Fixes", "Server-Side Ship Simulation", false, Cfg.Adv("Lets the server run ship physics too (with Selective ownership). Restart."));
			ConfigEnableRpcAoI = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable RPC Area-of-Interest", true, Cfg.Adv("Only sends object messages and damage numbers to players close enough to see them."));
			ConfigRpcAoIRadius = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "RPC AoI Radius", 256f, Cfg.Adv("Meters around a player for damage numbers and similar messages.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(64f, 1024f)));
			ConfigClientMaxDestroysPerFrame = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Max Destroys Per Frame", 200, Cfg.Adv("Most objects removed per frame when leaving an area, avoids freezes (0 = vanilla).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5000)));
			ConfigDediFellOutRescueLayers = ((BaseUnityPlugin)this).Config.Bind<string>("10 - Server Authority", "Dedi Fell-Out Rescue Layers", "Default,static_solid,Default_small,piece,terrain,vehicle", Cfg.Adv("Layers that count as solid ground when rescuing things that fell out of the world."));
			ConfigFixGroundSnapThroughFloors = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Fix Ground Snap Through Floors", true, Cfg.Adv("Stops creatures and tombstones popping up through floors built below ground."));
			ConfigShowAILODInServerStatus = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "Show AILOD in ServerStatus", true, Cfg.Adv("Adds AI LOD stats to the server status log line."));
			ConfigDiagnosticIntervalSec = ((BaseUnityPlugin)this).Config.Bind<float>("01 - General", "Diagnostic Rollup Interval (sec)", 60f, Cfg.Adv("Seconds between server status log lines.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 3600f)));
			ConfigEnableBulkTransferBoost = ((BaseUnityPlugin)this).Config.Bind<bool>("04 - Networking", "Enable Bulk Transfer Queue Boost", true, Cfg.Adv("Speeds up ServerSync config syncing on join and stops it kicking slow players. Restart."));
			ConfigBulkTransferBudgetPercent = ((BaseUnityPlugin)this).Config.Bind<int>("04 - Networking", "Bulk Transfer Budget Percent", 40, Cfg.Adv("Share of the send buffer ServerSync mods may use with the boost on.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 80)));
			ConfigEnableServerAuthority = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Server-Side Simulation", false, Cfg.Basic("Dedicated server runs the world around every player so it can run creatures and ships. Restart."));
			ConfigServerInstantiationBudgetMs = ((BaseUnityPlugin)this).Config.Bind<int>("10 - Server Authority", "Server Instantiation Budget Ms", 8, Cfg.Adv("Milliseconds per frame the server spends loading objects near players (0 = vanilla).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 50)));
			ConfigServerPassHz = ((BaseUnityPlugin)this).Config.Bind<int>("10 - Server Authority", "Server Object Pass Hz", 10, Cfg.Adv("How often per second the server refreshes objects around players (0 = every frame).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 30)));
			ConfigEnableServerOwnership = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Server ZDO Ownership Transfer (EXPERIMENTAL)", false, Cfg.Adv("Server takes over almost everything near players. Can break pickups; prefer Selective. Restart."));
			ConfigEnableServerOwnershipSelective = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Server ZDO Ownership Transfer — Selective (EXPERIMENTAL)", false, Cfg.Adv("Server runs wild creatures near players; everything else stays with players. Restart."));
			ConfigExtendedZoneRadius = ((BaseUnityPlugin)this).Config.Bind<int>("10 - Server Authority", "Extended Zone Radius", 0, Cfg.Adv("Extra zone rings the server loads around each player (0 = vanilla). Costs server CPU and RAM.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3)));
			ConfigEnablePredictiveZoneStreaming = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Predictive Zone Streaming", true, Cfg.Adv("Server loads the area ahead of a moving player so it is ready when they arrive."));
			ConfigPredictionLookaheadSec = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "Prediction Lookahead Sec", 3f, Cfg.Adv("Seconds of travel ahead the prediction looks.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f)));
			ConfigPredictionMinVelocity = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "Prediction Min Velocity", 2f, Cfg.Adv("Speed in m/s below which prediction is not used.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 20f)));
			ConfigPredictionMaxLookaheadZones = ((BaseUnityPlugin)this).Config.Bind<int>("10 - Server Authority", "Prediction Max Lookahead Zones", 9, Cfg.Adv("Furthest ahead the prediction may reach, in zones.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 25)));
			ConfigEnableZDOThrottling = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable ZDO Throttling", true, Cfg.Adv("On a backed-up connection, far loose objects are sent after nearer ones."));
			ConfigZDOThrottleDistance = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "ZDO Throttle Distance", 500f, Cfg.Adv("Meters beyond which loose objects are sent last (0 = off).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f)));
			ConfigEnableAILOD = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable AI LOD Throttling", true, Cfg.Adv("Updates creatures far from every player less often to save server CPU."));
			ConfigAILODNearDistance = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "AI LOD Near Distance", 100f, Cfg.Adv("Stats only: distance counted as near in the status log.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(50f, 200f)));
			ConfigAILODFarDistance = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "AI LOD Far Distance", 300f, Cfg.Adv("Meters from the nearest player beyond which creatures update less often.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(200f, 600f)));
			ConfigAILODThrottleFactor = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "AI LOD Throttle Factor", 0.5f, Cfg.Adv("Share of updates far creatures still get (0.5 = half).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 0.75f)));
			ConfigEnableAdaptiveThrottling = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Adaptive Throttling", true, Cfg.Adv("Only throttles while a player's connection is backing up; otherwise plays like vanilla."));
			ConfigSendCongestionThresholdPct = ((BaseUnityPlugin)this).Config.Bind<int>("10 - Server Authority", "Congestion Threshold", 50, Cfg.Adv("How full (percent) a send window must be to count as backed up.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 100)));
			ConfigEnableSendHeartbeatLog = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Log Send Queue Heartbeat", false, Cfg.Adv("Debug log: each player's send queue health every 10 s."));
			ConfigEnableBootPatchVerification = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Boot Patch Verification", false, Cfg.Adv("Debug log: lists other mods' patches on methods FGN relies on at startup."));
			ConfigEnableLargeZdoDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Large ZDO Diagnostics", false, Cfg.Adv("Debug log: names the object behind vanilla's 'Writing a lot of data' warning."));
			ConfigEnableZDODelta = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable ZDO Delta Compression", true, Cfg.Adv("Only resends values that changed, saving lots of bandwidth. Restart."));
			ConfigAllocationFreeZdoWrites = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Allocation-free ZDO Writes", true, Cfg.Adv("Writes network data without garbage, cutting stutter. Falls back to vanilla if anything differs."));
			ConfigEnableWNTServerOptimization = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable WearNTear Server Optimization", true, Cfg.Adv("Server skips wear updates on fully invulnerable build pieces. Restart."));
			ConfigEnableInvulnerableSupportSkip = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Invulnerable Support Skip", true, Cfg.Adv("Skips support checks on fully invulnerable pieces; big CPU save in large bases."));
			ConfigEnableInstanceOrphanPrune = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Instance Orphan Prune", true, Cfg.Adv("Cleans up broken object entries left by other mods instead of erroring."));
			ConfigFixTeleportGhosts = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Fix Teleport Ghost Players", true, Cfg.Adv("Fixes players staying visible, frozen, after they teleport away."));
			ConfigFixSlowSleep = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Fix Slow Sleep On Busy Servers", true, Cfg.Adv("Morning arrives on time when sleeping on a laggy server."));
			ConfigKeepaliveFirst = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Keep Busy Connections Alive", true, Cfg.Adv("Stops big transfers, like a large base loading, from timing players out."));
			ConfigKeepWorldClockAtRealTime = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Keep World Clock At Real Time", true, Cfg.Adv("Stops the world clock falling behind and jumping back on a laggy server."));
			ConfigSmoothServerClockCorrections = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Smooth Server Clock Corrections", true, Cfg.Adv("Eases in clock corrections so waves, ships and timers do not jerk."));
			ConfigEnableCapeCrashDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "Enable Cape Crash Diagnostics", false, Cfg.Adv("Debug log for crashes during cape setup. Very noisy. Restart."));
			ConfigEnableFallThroughDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "Enable Fall-Through Diagnostics", false, Cfg.Adv("Debug log for items and tombstones sinking through floors. Costs performance. Restart."));
			ConfigEntryBase[] array = (ConfigEntryBase[])(object)new ConfigEntryBase[53]
			{
				(ConfigEntryBase)ConfigLogLevel,
				(ConfigEntryBase)ConfigEnableCompression,
				(ConfigEntryBase)ConfigUpdateRate,
				(ConfigEntryBase)ConfigSendRateMin,
				(ConfigEntryBase)ConfigSendRateMax,
				(ConfigEntryBase)ConfigQueueSize,
				(ConfigEntryBase)ConfigForceCrossplay,
				(ConfigEntryBase)ConfigPlayerLimit,
				(ConfigEntryBase)ConfigAdvertisedPlayerLimit,
				(ConfigEntryBase)ConfigEnableShipFixes,
				(ConfigEntryBase)ConfigEnableServerSideShipSimulation,
				(ConfigEntryBase)ConfigEnableRpcRouter,
				(ConfigEntryBase)ConfigEnableRpcAoI,
				(ConfigEntryBase)ConfigRpcAoIRadius,
				(ConfigEntryBase)ConfigEnableServerAuthority,
				(ConfigEntryBase)ConfigExtendedZoneRadius,
				(ConfigEntryBase)ConfigEnableZDOThrottling,
				(ConfigEntryBase)ConfigZDOThrottleDistance,
				(ConfigEntryBase)ConfigEnableAILOD,
				(ConfigEntryBase)ConfigAILODNearDistance,
				(ConfigEntryBase)ConfigAILODFarDistance,
				(ConfigEntryBase)ConfigAILODThrottleFactor,
				(ConfigEntryBase)ConfigEnableZDODelta,
				(ConfigEntryBase)ConfigAllocationFreeZdoWrites,
				(ConfigEntryBase)ConfigShowAILODInServerStatus,
				(ConfigEntryBase)ConfigEnableBootPatchVerification,
				(ConfigEntryBase)ConfigEnableLargeZdoDiagnostics,
				(ConfigEntryBase)ConfigEnableWNTServerOptimization,
				(ConfigEntryBase)ConfigZoneLoadBatchSize,
				(ConfigEntryBase)ConfigZPackageReceiveBufferSize,
				(ConfigEntryBase)ConfigEnableTimeSliceInstantiation,
				(ConfigEntryBase)ConfigInstantiationBudgetMs,
				(ConfigEntryBase)ConfigMaxInstancesPerFrame,
				(ConfigEntryBase)ConfigServerInstantiationBudgetMs,
				(ConfigEntryBase)ConfigServerPassHz,
				(ConfigEntryBase)ConfigSafetyFallbackEnabled,
				(ConfigEntryBase)ConfigSafetyFallbackThreshold,
				(ConfigEntryBase)ConfigEnablePredictiveZoneStreaming,
				(ConfigEntryBase)ConfigPredictionLookaheadSec,
				(ConfigEntryBase)ConfigPredictionMinVelocity,
				(ConfigEntryBase)ConfigPredictionMaxLookaheadZones,
				(ConfigEntryBase)ConfigEnableInvulnerableSupportSkip,
				(ConfigEntryBase)ConfigEnableInstanceOrphanPrune,
				(ConfigEntryBase)ConfigFixTeleportGhosts,
				(ConfigEntryBase)ConfigFixSlowSleep,
				(ConfigEntryBase)ConfigKeepaliveFirst,
				(ConfigEntryBase)ConfigKeepWorldClockAtRealTime,
				(ConfigEntryBase)ConfigSmoothServerClockCorrections,
				(ConfigEntryBase)ConfigEnableFallThroughDiagnostics,
				(ConfigEntryBase)ConfigEnableBulkTransferBoost,
				(ConfigEntryBase)ConfigBulkTransferBudgetPercent,
				(ConfigEntryBase)ConfigEnableServerOwnership,
				(ConfigEntryBase)ConfigEnableServerOwnershipSelective
			};
			foreach (ConfigEntryBase val in array)
			{
				EventInfo eventInfo = ((object)val).GetType().GetEvent("SettingChanged");
				if (eventInfo != null)
				{
					EventHandler handler = delegate(object sender, EventArgs __)
					{
						//IL_0027: Unknown result type (might be due to invalid IL or missing references)
						//IL_002d: Expected O, but got Unknown
						string text = (((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) ? "SERVER" : "CLIENT");
						ConfigEntryBase val2 = (ConfigEntryBase)sender;
						LoggerOptions.LogInfo($"[{text}] Config changed: {val2.Definition.Section} → {val2.Definition.Key} = {val2.BoxedValue}");
					};
					eventInfo.AddEventHandler(val, handler);
				}
			}
			if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer())
			{
				ConfigEnableServerAuthority.Value = false;
			}
		}
	}
	public enum LogLevel
	{
		[Description("Errors/Warnings only")]
		Warning,
		[Description("Errors/Warnings/Messages [default]")]
		Message,
		[Description("Everything including Info")]
		Info,
		[Description("Info plus the timed reports")]
		Debug
	}
	public enum UpdateRateOptions
	{
		[Description("150% - 30 network sends/sec [smoother, more bandwidth]")]
		_150,
		[Description("100% - 20 network sends/sec [default, vanilla]")]
		_100,
		[Description("75% - 15 network sends/sec")]
		_75,
		[Description("50% - 10 network sends/sec")]
		_50
	}
	public enum QueueSizeOptions
	{
		[Description("80 KB")]
		_80KB,
		[Description("64 KB")]
		_64KB,
		[Description("48 KB")]
		_48KB,
		[Description("32 KB [default]")]
		_32KB,
		[Description("Vanilla (~10 KB)")]
		_vanilla
	}
	public enum ForceCrossplayOptions
	{
		[Description("Vanilla behaviour - respect -crossplay flag [default]")]
		vanilla,
		[Description("Force crossplay ENABLED (use PlayFab backend)")]
		playfab,
		[Description("Force crossplay DISABLED (use Steamworks backend)")]
		steamworks
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public bool? Browsable;

		public bool? IsAdvanced;

		public int? Order;
	}
	internal static class Cfg
	{
		internal static ConfigDescription Basic(string text, AcceptableValueBase range = null)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Expected O, but got Unknown
			return new ConfigDescription(text, range, Array.Empty<object>());
		}

		internal static ConfigDescription Adv(string text, AcceptableValueBase range = null)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			return new ConfigDescription(text, range, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					IsAdvanced = true
				}
			});
		}

		internal static ConfigDescription Hidden(string text, AcceptableValueBase range = null)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			return new ConfigDescription(text, range, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Browsable = false
				}
			});
		}
	}
	[HarmonyPatch]
	public static class AILODPatches
	{
		private const float PerInstanceHashJitterMultiplier = 0.001f;

		[HarmonyPatch(typeof(Character), "CustomFixedUpdate")]
		[HarmonyPrefix]
		public static bool CustomFixedUpdate_Prefix(Character __instance, float dt)
		{
			long start = FgnPerf.Begin();
			bool result = DecideTick(__instance, dt);
			FgnPerf.End(FgnPerf.Point.CharacterFixed, start);
			return result;
		}

		private static bool DecideTick(Character __instance, float dt)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			if (!IsAILODActiveOnDedicatedServer())
			{
				return true;
			}
			if ((FiresGhettoNetworkMod.ConfigEnableAdaptiveThrottling == null || FiresGhettoNetworkMod.ConfigEnableAdaptiveThrottling.Value) && !SendCongestion.AnyPeerCongested())
			{
				return true;
			}
			ServerStatusDiagnostics.s_aiLod_examined++;
			if (__instance.IsPlayer() || __instance.IsTamed())
			{
				ServerStatusDiagnostics.s_aiLod_playerOrTamed++;
				return true;
			}
			int peerCount;
			float num = ComputeDistanceToNearestPeer(((Component)__instance).transform.position, out peerCount);
			ServerStatusDiagnostics.s_aiLod_peersLastSeen = peerCount;
			UpdateNearestDistanceObservedRange(num);
			float num2 = EffectiveConfig.AILODNearDistance();
			float num3 = EffectiveConfig.AILODFarDistance();
			if (num <= num2)
			{
				ServerStatusDiagnostics.s_aiLod_decidedNear++;
				return true;
			}
			if (num > num3)
			{
				return DecideFarBandTickOrSkip(__instance, dt);
			}
			ServerStatusDiagnostics.s_aiLod_decidedMidBand++;
			return true;
		}

		private static bool IsAILODActiveOnDedicatedServer()
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated())
			{
				return FiresGhettoNetworkMod.ConfigEnableAILOD.Value;
			}
			return false;
		}

		private static float ComputeDistanceToNearestPeer(Vector3 origin, out int peerCount)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: 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)
			float num = float.MaxValue;
			peerCount = 0;
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer != null)
				{
					peerCount++;
					Vector3 refPos = peer.GetRefPos();
					float num2 = origin.x - refPos.x;
					float num3 = origin.y - refPos.y;
					float num4 = origin.z - refPos.z;
					float num5 = num2 * num2 + num3 * num3 + num4 * num4;
					if (num5 < num)
					{
						num = num5;
					}
				}
			}
			if (!(num < float.MaxValue))
			{
				return float.MaxValue;
			}
			return Mathf.Sqrt(num);
		}

		private static void UpdateNearestDistanceObservedRange(float nearestDist)
		{
			if (nearestDist < ServerStatusDiagnostics.s_aiLod_minNearestDist)
			{
				ServerStatusDiagnostics.s_aiLod_minNearestDist = nearestDist;
			}
			if (nearestDist > ServerStatusDiagnostics.s_aiLod_maxNearestDist && nearestDist < float.MaxValue)
			{
				ServerStatusDiagnostics.s_aiLod_maxNearestDist = nearestDist;
			}
		}

		private static bool DecideFarBandTickOrSkip(Character mob, float dt)
		{
			float num = 1f / EffectiveConfig.AILODThrottleFactor();
			if ((Time.time + (float)((object)mob).GetHashCode() * 0.001f) % num > dt)
			{
				ServerStatusDiagnostics.s_aiLod_decidedFarSkipped++;
				return false;
			}
			ServerStatusDiagnostics.s_aiLod_decidedFarRan++;
			return true;
		}
	}
	[HarmonyPatch]
	public static class BigZdoDiagnostic
	{
		private struct BucketSnapshot
		{
			public int Floats;

			public int Vector3s;

			public int Quaternions;

			public int Ints;

			public int Longs;

			public int Strings;

			public int ByteArrays;

			public bool AnyExceeds(int threshold)
			{
				if (Floats <= threshold && Vector3s <= threshold && Quaternions <= threshold && Ints <= threshold && Longs <= threshold && Strings <= threshold)
				{
					return ByteArrays > threshold;
				}
				return true;
			}

			public (string name, int count)[] AsTuples()
			{
				return new(string, int)[7]
				{
					("float", Floats),
					("Vector3", Vector3s),
					("Quaternion", Quaternions),
					("int", Ints),
					("long", Longs),
					("string", Strings),
					("byte[]", ByteArrays)
				};
			}
		}

		private const int VanillaWarningThreshold = 100;

		private const int WireFormatByteCountCeiling = 255;

		private static bool s_bulkSaveInProgress;

		[HarmonyPatch(typeof(ZDOMan), "SaveChunk")]
		[HarmonyPrefix]
		public static void ZDOMan_SaveChunk_Prefix()
		{
			s_bulkSaveInProgress = true;
		}

		[HarmonyPatch(typeof(ZDOMan), "SaveChunk")]
		[HarmonyFinalizer]
		public static void ZDOMan_SaveChunk_Finalizer()
		{
			s_bulkSaveInProgress = false;
		}

		[HarmonyPatch(typeof(ZDO), "Save")]
		[HarmonyPrefix]
		public static void ZDO_Save_Prefix(ZDO __instance)
		{
			if (!s_bulkSaveInProgress && __instance != null && LargeZdoDiagnosticIsEnabled())
			{
				TryReportOversizedBuckets(__instance, "Save", canTruncateOnWire: false, SnapshotSaveBuckets);
			}
		}

		internal static void ReportLiveBuckets(ZDO zdo)
		{
			if (zdo != null && LargeZdoDiagnosticIsEnabled())
			{
				TryReportOversizedBuckets(zdo, "Serialize", canTruncateOnWire: true, SnapshotLiveBuckets);
			}
		}

		private static bool LargeZdoDiagnosticIsEnabled()
		{
			return FiresGhettoNetworkMod.ConfigEnableLargeZdoDiagnostics?.Value ?? false;
		}

		private static void TryReportOversizedBuckets(ZDO zdo, string sourceLabel, bool canTruncateOnWire, Func<ZDOID, BucketSnapshot> snapshotter)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				BucketSnapshot bucketSnapshot = snapshotter(zdo.m_uid);
				if (bucketSnapshot.AnyExceeds(100))
				{
					string context = FormatZdoContext(zdo, sourceLabel);
					(string, int)[] array = bucketSnapshot.AsTuples();
					for (int i = 0; i < array.Length; i++)
					{
						(string, int) tuple = array[i];
						LogIfOverThreshold(context, tuple.Item1, tuple.Item2, canTruncateOnWire);
					}
				}
			}
			catch (Exception ex)
			{
				LoggerOptions.LogWarning("[BigZdoDiag] " + sourceLabel + " prefix threw: " + ex.Message);
			}
		}

		private static BucketSnapshot SnapshotSaveBuckets(ZDOID uid)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			List<KeyValuePair<int, float>> floats = default(List<KeyValuePair<int, float>>);
			List<KeyValuePair<int, Vector3>> vec3s = default(List<KeyValuePair<int, Vector3>>);
			List<KeyValuePair<int, Quaternion>> quats = default(List<KeyValuePair<int, Quaternion>>);
			List<KeyValuePair<int, int>> ints = default(List<KeyValuePair<int, int>>);
			List<KeyValuePair<int, long>> longs = default(List<KeyValuePair<int, long>>);
			List<KeyValuePair<int, string>> strings = default(List<KeyValuePair<int, string>>);
			List<KeyValuePair<int, byte[]>> byteArrays = default(List<KeyValuePair<int, byte[]>>);
			ZDOConnectionHashData val = default(ZDOConnectionHashData);
			ZDOExtraData.GetSaveData(uid, ref floats, ref vec3s, ref quats, ref ints, ref longs, ref strings, ref byteArrays, ref val);
			return Count(floats, vec3s, quats, ints, longs, strings, byteArrays);
		}

		private static BucketSnapshot SnapshotLiveBuckets(ZDOID uid)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			List<KeyValuePair<int, float>> floats = default(List<KeyValuePair<int, float>>);
			List<KeyValuePair<int, Vector3>> vec3s = default(List<KeyValuePair<int, Vector3>>);
			List<KeyValuePair<int, Quaternion>> quats = default(List<KeyValuePair<int, Quaternion>>);
			List<KeyValuePair<int, int>> ints = default(List<KeyValuePair<int, int>>);
			List<KeyValuePair<int, long>> longs = default(List<KeyValuePair<int, long>>);
			List<KeyValuePair<int, string>> strings = default(List<KeyValuePair<int, string>>);
			List<KeyValuePair<int, byte[]>> byteArrays = default(List<KeyValuePair<int, byte[]>>);
			ZDOConnection val = default(ZDOConnection);
			ZDOExtraData.GetData(uid, ref floats, ref vec3s, ref quats, ref ints, ref longs, ref strings, ref byteArrays, ref val);
			return Count(floats, vec3s, quats, ints, longs, strings, byteArrays);
		}

		private static BucketSnapshot Count<TF, TV, TQ, TI, TL, TS, TB>(List<TF> floats, List<TV> vec3s, List<TQ> quats, List<TI> ints, List<TL> longs, List<TS> strings, List<TB> byteArrays)
		{
			return new BucketSnapshot
			{
				Floats = (floats?.Count ?? 0),
				Vector3s = (vec3s?.Count ?? 0),
				Quaternions = (quats?.Count ?? 0),
				Ints = (ints?.Count ?? 0),
				Longs = (longs?.Count ?? 0),
				Strings = (strings?.Count ?? 0),
				ByteArrays = (byteArrays?.Count ?? 0)
			};
		}

		private static string FormatZdoContext(ZDO zdo, string sourceLabel)
		{
			//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_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = zdo.GetPosition();
			string text = ResolvePrefabNameFromHash(zdo.GetPrefab());
			return $"src={sourceLabel} uid={zdo.m_uid} prefab='{text}'({zdo.GetPrefab()}) " + $"pos=({position.x:F1},{position.z:F1},{position.y:F1}) owner={zdo.GetOwner()}";
		}

		private static void LogIfOverThreshold(string context, string bucketName, int count, bool canTruncateOnWire)
		{
			if (count > 100)
			{
				if (canTruncateOnWire && count > 255)
				{
					LoggerOptions.LogWarning($"[BigZdoDiag TRUNCATING] {context} bucket={bucketName,-10} count={count} " + $"(>{255} — receiver will read wrapped count, payload corrupt)");
				}
				else
				{
					LoggerOptions.LogWarning($"[BigZdoDiag] {context} bucket={bucketName,-10} count={count}");
				}
			}
		}

		private static string ResolvePrefabNameFromHash(int prefabHash)
		{
			try
			{
				ZNetScene instance = ZNetScene.instance;
				GameObject val = ((instance != null) ? instance.GetPrefab(prefabHash) : null);
				if ((Object)(object)val != (Object)null)
				{
					return ((Object)val).name;
				}
			}
			catch
			{
			}
			return "<unresolved>";
		}
	}
	[HarmonyPatch]
	public static class BulkTransferGatePatches
	{
		private readonly struct Target
		{
			public readonly string TypeName;

			public readonly string GateMethodName;

			public readonly int VanillaConstant;

			public Target(string typeName, string gateMethodName, int vanillaConstant)
			{
				TypeName = typeName;
				GateMethodName = gateMethodName;
				VanillaConstant = vanillaConstant;
			}
		}

		private static readonly Target[] Targets = new Target[2]
		{
			new Target("ServerSync.ConfigSync", "GetSendQueueSize", 20000),
			new Target("ServerCharacters.Shared", "GetSendQueueSize", 20000)
		};

		public static float DisconnectTimeoutSeconds = 86400f;

		private static readonly FieldInfo TimeoutField = AccessTools.Field(typeof(BulkTransferGatePatches), "DisconnectTimeoutSeconds");

		private static FieldInfo s_jotunnTimeoutField;

		private static bool s_jotunnResolved;

		private static int s_currentVanillaConstant;

		private static int s_lastTimeoutSitesDisarmed;

		private static bool s_applied;

		private static int s_serverSyncCopyCount = 1;

		private static int s_budgetedGate;

		private const int ThirdPartyGateFloorBytes = 20000;

		internal static void ApplyOnZNetStart()
		{
			if (s_applied)
			{
				return;
			}
			s_applied = true;
			ConfigEntry<bool> configEnableBulkTransferBoost = FiresGhettoNetworkMod.ConfigEnableBulkTransferBoost;
			if (configEnableBulkTransferBoost != null && !configEnableBulkTransferBoost.Value)
			{
				LoggerOptions.LogMessage("Bulk-transfer gate scan skipped — ConfigEnableBulkTransferBoost = false.");
				return;
			}
			try
			{
				ApplyAll(FiresGhettoNetworkMod.Harmony);
			}
			catch (Exception ex)
			{
				LoggerOptions.LogWarning("Bulk-transfer gate scan threw: " + ex.Message);
			}
		}

		private static void ApplyAll(Harmony harmony)
		{
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_029f: Expected O, but got Unknown
			int targetQueueSize = GetTargetQueueSize();
			s_serverSyncCopyCount = CountServerSyncCopies();
			s_budgetedGate = GetBudgetedGate();
			LoggerOptions.LogInfo($"Bulk-transfer gate budget: {s_serverSyncCopyCount} ServerSync.ConfigSync copy/ies, " + $"Steam buffer ceiling {GetEffectiveSendBufferCeilingBytes() / 1024}KB, " + $"{FiresGhettoNetworkMod.ConfigBulkTransferBudgetPercent?.Value ?? 40}% budget → per-mod gate " + $"{s_budgetedGate} bytes (Queue Size target {targetQueueSize}).");
			if (s_budgetedGate <= 20000)
			{
				LoggerOptions.LogInfo("Bulk-transfer budget floored the per-mod gate at the vanilla 20 KB (many ServerSync mods on the current Steam send buffer), so the gate is NOT raised — but the 30s self-disconnect is disarmed regardless, so slow/high-ping peers are never dropped. Install FiresSteamworksPatcher or lower 'Queue Size' only if you also want the gate raise.");
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			Target[] targets = Targets;
			for (int i = 0; i < targets.Length; i++)
			{
				Target target = targets[i];
				bool flag = s_budgetedGate > target.VanillaConstant;
				int num4 = 0;
				int num5 = 0;
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					Type type = null;
					try
					{
						type = assembly.GetType(target.TypeName, throwOnError: false);
					}
					catch
					{
						continue;
					}
					if (type == null)
					{
						continue;
					}
					num4++;
					foreach (Type item in WalkAllNestedTypes(type))
					{
						MethodInfo[] methods = item.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
						foreach (MethodInfo methodInfo in methods)
						{
							if (methodInfo.IsAbstract || methodInfo.ContainsGenericParameters || !IsGateLoop(methodInfo, target.GateMethodName, out var unreadable))
							{
								continue;
							}
							if (unreadable != null)
							{
								num3++;
								LoggerOptions.LogInfo("Bulk-transfer gate left as shipped in " + assembly.GetName().Name + " → " + item.FullName + "." + methodInfo.Name + ": its code is obfuscated (" + unreadable + "), so it cannot be patched. That mod keeps ServerSync's own 20 KB gate and 30s self-disconnect; nothing else changes.");
								continue;
							}
							s_currentVanillaConstant = target.VanillaConstant;
							string text = ValidateRewrite(methodInfo);
							s_lastTimeoutSitesDisarmed = 0;
							if (text != null)
							{
								num3++;
								LoggerOptions.LogInfo("Bulk-transfer gate left as shipped in " + assembly.GetName().Name + " → " + item.FullName + "." + methodInfo.Name + ": its rewritten code would not be valid (" + text + "). That mod keeps ServerSync's own 20 KB gate and 30s self-disconnect.");
								continue;
							}
							try
							{
								harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(BulkTransferGatePatches), "RaiseGateAndDisarmTimeout", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
								num5++;
								LoggerOptions.LogInfo("Bulk-transfer patched: " + assembly.GetName().Name + " → " + item.FullName + "." + methodInfo.Name + " " + $"(gate {target.VanillaConstant} → {(flag ? s_budgetedGate : target.VanillaConstant)} bytes, " + $"30s self-disconnect disarmed at {s_lastTimeoutSitesDisarmed} site(s)).");
							}
							catch (Exception ex)
							{
								LoggerOptions.LogWarning("Failed to patch bulk-transfer gate at " + assembly.GetName().Name + "." + item.FullName + "." + methodInfo.Name + ": " + ex.Message);
							}
						}
					}
				}
				num += num4;
				num2 += num5;
				LoggerOptions.LogInfo($"Bulk-transfer scan for {target.TypeName}: {num4} copies found, {num5} gate sites patched.");
			}
			LoggerOptions.LogMessage($"Bulk-transfer scan complete: {num} third-party copies found across loaded assemblies, " + $"{num2} gate sites patched (per-mod gate {s_budgetedGate} bytes, 30s self-disconnect disarmed)" + ((num3 > 0) ? $", {num3} obfuscated copy/ies left as shipped." : "."));
			ApplyJotunnTimeout();
			LoggerOptions.LogInfo((s_jotunnTimeoutField != null) ? $"Jotunn CustomRPC self-disconnect disarmed (Timeout -> {DisconnectTimeoutSeconds}s)." : "Jotunn CustomRPC not present — no Jotunn timeout to disarm.");
		}

		public static void ApplyJotunnTimeout()
		{
			try
			{
				if (!s_jotunnResolved)
				{
					s_jotunnResolved = true;
					if (IsAssemblyLoaded("Jotunn"))
					{
						Type type = AccessTools.TypeByName("Jotunn.Entities.CustomRPC");
						if (type != null)
						{
							s_jotunnTimeoutField = AccessTools.Field(type, "Timeout");
						}
					}
				}
				s_jotunnTimeoutField?.SetValue(null, DisconnectTimeoutSeconds);
			}
			catch (Exception ex)
			{
				LoggerOptions.LogWarning("Jotunn CustomRPC.Timeout disarm failed: " + ex.Message);
			}
		}

		private static bool IsAssemblyLoaded(string assemblyName)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			for (int i = 0; i < assemblies.Length; i++)
			{
				if (string.Equals(assemblies[i].GetName().Name, assemblyName, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private static IEnumerable<Type> WalkAllNestedTypes(Type root)
		{
			yield return root;
			Type[] nestedTypes;
			try
			{
				nestedTypes = root.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
			}
			catch
			{
				yield break;
			}
			Type[] array = nestedTypes;
			foreach (Type root2 in array)
			{
				foreach (Type item in WalkAllNestedTypes(root2))
				{
					yield return item;
				}
			}
		}

		private static bool IsGateLoop(MethodBase m, string gateMethodName, out string unreadable)
		{
			unreadable = null;
			List<CodeInstruction> currentInstructions;
			try
			{
				currentInstructions = PatchProcessor.GetCurrentInstructions(m, int.MaxValue, (ILGenerator)null);
			}
			catch
			{
				return false;
			}
			bool flag = false;
			bool flag2 = false;
			foreach (CodeInstruction item in currentInstructions)
			{
				if (unreadable == null && item.operand == null && item.opcode.OperandType != OperandType.InlineNone)
				{
					unreadable = "a " + item.opcode.Name + " with no resolvable operand";
				}
				if ((item.opcode == OpCodes.Call || item.opcode == OpCodes.Callvirt) && item.operand is MethodInfo methodInfo)
				{
					if (methodInfo.Name == gateMethodName)
					{
						flag = true;
					}
					else if (methodInfo.Name == "Disconnect")
					{
						flag2 = true;
					}
				}
			}
			return flag && flag2;
		}

		public static IEnumerable<CodeInstruction> RaiseGateAndDisarmTimeout(IEnumerable<CodeInstruction> instructions)
		{
			int num = s_currentVanillaConstant;
			int num2 = s_budgetedGate;
			int num3 = 0;
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			List<int> list2 = new List<int>();
			for (int i = 0; i < list.Count; i++)
			{
				if ((list[i].opcode == OpCodes.Call || list[i].opcode == OpCodes.Callvirt) && list[i].operand is MethodInfo { Name: "GetSendQueueSize" })
				{
					list2.Add(i);
				}
			}
			if (list2.Count == 0)
			{
				s_lastTimeoutSitesDisarmed = 0;
				return list;
			}
			for (int j = 0; j < list.Count; j++)
			{
				if (num2 > num && (list[j].opcode == OpCodes.Ldc_I4 || list[j].opcode == OpCodes.Ldc_I4_S) && list[j].operand is int num4 && num4 == num && NearAnyCall(list2, j))
				{
					list[j].opcode = OpCodes.Ldc_I4;
					list[j].operand = num2;
				}
				else if (list[j].opcode == OpCodes.Ldc_R4 && list[j].operand is float num5 && num5 == 30f)
				{
					list[j].opcode = OpCodes.Ldsfld;
					list[j].operand = TimeoutField;
					num3++;
				}
				else if (list[j].opcode == OpCodes.Ldc_R8 && list[j].operand is double num6 && num6 == 30.0)
				{
					list[j].operand = 86400.0;
					num3++;
				}
			}
			s_lastTimeoutSitesDisarmed = num3;
			return list;
		}

		private static string ValidateRewrite(MethodBase m)
		{
			List<CodeInstruction> list;
			try
			{
				list = RaiseGateAndDisarmTimeout(PatchProcessor.GetCurrentInstructions(m, int.MaxValue, (ILGenerator)null)).ToList();
			}
			catch (Exception ex)
			{
				return "reading it failed: " + ex.GetType().Name;
			}
			HashSet<Label> hashSet = new HashSet<Label>();
			foreach (CodeInstruction item3 in list)
			{
				foreach (Label label in item3.labels)
				{
					hashSet.Add(label);
				}
			}
			for (int i = 0; i < list.Count; i++)
			{
				CodeInstruction val = list[i];
				if (val.opcode.OperandType == OperandType.InlineNone)
				{
					continue;
				}
				if (val.operand == null)
				{
					return $"instruction {i} {val.opcode.Name} has no operand";
				}
				if (val.operand is Label item && !hashSet.Contains(item))
				{
					return $"instruction {i} {val.opcode.Name} branches to a missing label";
				}
				if (!(val.operand is Label[] array))
				{
					continue;
				}
				Label[] array2 = array;
				foreach (Label item2 in array2)
				{
					if (!hashSet.Contains(item2))
					{
						return $"instruction {i} switch branches to a missing label";
					}
				}
			}
			return null;
		}

		private static bool NearAnyCall(List<int> callIdx, int i)
		{
			foreach (int item in callIdx)
			{
				if (Math.Abs(item - i) <= 8)
				{
					return true;
				}
			}
			return false;
		}

		private static int CountServerSyncCopies()
		{
			int num = 0;
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				try
				{
					if (assembly.GetType("ServerSync.ConfigSync", throwOnError: false) != null)
					{
						num++;
					}
				}
				catch
				{
				}
			}
			return Math.Max(1, num);
		}

		private static int GetEffectiveSendBufferCeilingBytes()
		{
			try
			{
				if (NetworkingRatesGroup.IsSendBufferRaiseApplied())
				{
					return Math.Max(524288, EffectiveConfig.SteamSendBufferBytes());
				}
			}
			catch
			{
			}
			return 524288;
		}

		private static int GetBudgetedGate()
		{
			int targetQueueSize = GetTargetQueueSize();
			int num = FiresGhettoNetworkMod.ConfigBulkTransferBudgetPercent?.Value ?? 40;
			int val = (int)((long)GetEffectiveSendBufferCeilingBytes() * (long)num / 100 / Math.Max(1, s_serverSyncCopyCount));
			return Math.Max(20000, Math.Min(targetQueueSize, val));
		}

		private static int GetTargetQueueSize()
		{
			return EffectiveConfig.QueueSizeBytes(20000);
		}
	}
	[HarmonyPatch]
	public static class ClientCleanupThrottle
	{
		private static readonly HashSet<ZNetView> _pendingDestroySet = new HashSet<ZNetView>();

		private static readonly Queue<ZNetView> _pendingLooseQueue = new Queue<ZNetView>();

		private static readonly Queue<ZNetView> _pendingSolidQueue = new Queue<ZNetView>();

		private static readonly List<ZDO> _zdosToUnregisterScratch = new List<ZDO>(256);

		private static readonly List<ZNetView> _destroyNowScratch = new List<ZNetView>(64);

		private static readonly HashSet<ZDO> _inAreaThisFrame = new HashSet<ZDO>();

		private static int _sessionMoversDestroyed;

		private static int _sessionStaticDeferred;

		private static float _nextTeardownLogTime;

		private const float TeardownLogIntervalSec = 5f;

		private const int MaxNamedMovers = 5;

		private static readonly Dictionary<string, int> _moverCounts = new Dictionary<string, int>();

		private static readonly List<KeyValuePair<string, int>> _moverRanking = new List<KeyValuePair<string, int>>();

		private const int MoverNone = 0;

		private const int MoverCharacter = 1;

		private const int MoverRigidbody = 2;

		[HarmonyPatch(typeof(ZNetScene), "RemoveObjects")]
		[HarmonyPrefix]
		public static bool RemoveObjects_ClientThrottle_Prefix(ZNetScene __instance, List<ZDO> currentNearObjects, List<ZDO> currentDistantObjects, bool __runOriginal)
		{
			if (!__runOriginal)
			{
				return false;
			}
			if (ValheimCommunityPatchCompat.SchedulesObjectRemoval)
			{
				return true;
			}
			if (ServerClientUtils.ZNetIsDedicated())
			{
				return true;
			}
			int num = FiresGhettoNetworkMod.ConfigClientMaxDestroysPerFrame?.Value ?? 0;
			if (num <= 0)
			{
				return true;
			}
			RebuildInAreaSet(currentNearObjects, currentDistantObjects);
			ScanInstances(__instance);
			DestroyUpToBudget(__instance, num);
			return false;
		}

		public static void ZNetScene_Shutdown_ClearPendingState()
		{
			_pendingDestroySet.Clear();
			VehicleOwnershipEdge.Clear();
			_pendingLooseQueue.Clear();
			_pendingSolidQueue.Clear();
			_zdosToUnregisterScratch.Clear();
			_destroyNowScratch.Clear();
			_inAreaThisFrame.Clear();
			_sessionMoversDestroyed = 0;
			_sessionStaticDeferred = 0;
		}

		private static void ReportTeardown(int moverCharacters, int moverBodies, int queuedLoose, int queuedSolid)
		{
			int num = moverCharacters + moverBodies;
			int num2 = queuedLoose + queuedSolid;
			if (num != 0 || num2 != 0)
			{
				_sessionMoversDestroyed += num;
				_sessionStaticDeferred += num2;
				if (num != 0 && !(Time.time < _nextTeardownLogTime))
				{
					_nextTeardownLogTime = Time.time + 5f;
					LoggerOptions.LogMessage($"[CleanupThrottle] teardown: {num} mover(s) destroyed IN-FRAME " + $"({moverCharacters} Character, {moverBodies} rigidbody: {NameMovers()}) — these would previously have been queued " + "and left simulating while their surroundings were destroyed. " + $"{num2} static deferred ({queuedSolid} solid last). " + $"Session totals: {_sessionMoversDestroyed} movers / {_sessionStaticDeferred} static.");
				}
			}
		}

		private static string NameMovers()
		{
			_moverCounts.Clear();
			for (int i = 0; i < _destroyNowScratch.Count; i++)
			{
				ZNetView val = _destroyNowScratch[i];
				if (!((Object)(object)val == (Object)null))
				{
					string key = PrefabName(val);
					_moverCounts.TryGetValue(key, out var value);
					_moverCounts[key] = value + 1;
				}
			}
			_moverRanking.Clear();
			_moverRanking.AddRange(_moverCounts);
			_moverRanking.Sort((KeyValuePair<string, int> a, KeyValuePair<string, int> b) => b.Value.CompareTo(a.Value));
			StringBuilder stringBuilder = new StringBuilder();
			for (int num = 0; num < _moverRanking.Count && num < 5; num++)
			{
				if (num > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(_moverRanking[num].Key);
				if (_moverRanking[num].Value > 1)
				{
					stringBuilder.Append(" x").Append(_moverRanking[num].Value);
				}
			}
			if (_moverRanking.Count > 5)
			{
				stringBuilder.Append(", +").Append(_moverRanking.Count - 5).Append(" more");
			}
			return stringBuilder.ToString();
		}

		private static string PrefabName(ZNetView view)
		{
			ZDO zDO = view.GetZDO();
			GameObject val = ((zDO != null && (Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(zDO.GetPrefab()) : null);
			if (!((Object)(object)val != (Object)null))
			{
				return ((Object)((Component)view).gameObject).name;
			}
			return ((Object)val).name;
		}

		private static void RebuildInAreaSet(List<ZDO> currentNearObjects, List<ZDO> currentDistantObjects)
		{
			_inAreaThisFrame.Clear();
			AddAllToInAreaSet(currentNearObjects);
			AddAllToInAreaSet(currentDistantObjects);
		}

		private static void AddAllToInAreaSet(List<ZDO> zdos)
		{
			if (zdos == null)
			{
				return;
			}
			for (int i = 0; i < zdos.Count; i++)
			{
				ZDO val = zdos[i];
				if (val != null)
				{
					_inAreaThisFrame.Add(val);
				}
			}
		}

		private static int ClassifyMover(ZNetView view)
		{
			if ((Object)(object)((Component)view).GetComponent<Character>() != (Object)null)
			{
				return 1;
			}
			Rigidbody component = ((Component)view).GetComponent<Rigidbody>();
			if (!((Object)(object)component != (Object)null) || component.isKinematic)
			{
				return 0;
			}
			return 2;
		}

		private static void ScanInstances(ZNetScene scene)
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Invalid comparison between Unknown and I4
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Invalid comparison between Unknown and I4
			_destroyNowScratch.Clear();
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			foreach (KeyValuePair<ZDO, ZNetView> instance in scene.m_instances)
			{
				ZNetView value = instance.Value;
				if ((Object)(object)value == (Object)null)
				{
					continue;
				}
				ZDO zDO = value.GetZDO();
				if (zDO == null || _inAreaThisFrame.Contains(zDO) || !_pendingDestroySet.Add(value))
				{
					continue;
				}
				int num5 = ClassifyMover(value);
				if (num5 != 0)
				{
					_destroyNowScratch.Add(value);
					if (num5 == 1)
					{
						num++;
					}
					else
					{
						num2++;
					}
				}
				else if ((int)zDO.Type == 2 || (int)zDO.Type == 3)
				{
					_pendingSolidQueue.Enqueue(value);
					num4++;
				}
				else
				{
					_pendingLooseQueue.Enqueue(value);
					num3++;
				}
			}
			ReportTeardown(num, num2, num3, num4);
			_zdosToUnregisterScratch.Clear();
			for (int i = 0; i < _destroyNowScratch.Count; i++)
			{
				ZNetView val = _destroyNowScratch[i];
				_pendingDestroySet.Remove(val);
				DestroyInstance(val);
			}
			_destroyNowScratch.Clear();
			UnregisterDestroyedFromScene(scene);
		}

		private static void DestroyUpToBudget(ZNetScene scene, int budget)
		{
			_zdosToUnregisterScratch.Clear();
			int num = 0;
			ZNetView view;
			while (num < budget && TryDequeueNextPending(out view))
			{
				if (!((Object)(object)view == (Object)null))
				{
					ZDO zDO = view.GetZDO();
					if (zDO != null && !_inAreaThisFrame.Contains(zDO))
					{
						DestroyInstance(view);
						num++;
					}
				}
			}
			UnregisterDestroyedFromScene(scene);
		}

		private static bool TryDequeueNextPending(out ZNetView view)
		{
			if (_pendingLooseQueue.Count > 0)
			{
				view = _pendingLooseQueue.Dequeue();
				_pendingDestroySet.Remove(view);
				return true;
			}
			if (_pendingSolidQueue.Count > 0)
			{
				view = _pendingSolidQueue.Dequeue();
				_pendingDestroySet.Remove(view);
				return true;
			}
			view = null;
			return false;
		}

		private static void DestroyInstance(ZNetView view)
		{
			if ((Object)(object)view == (Object)null)
			{
				return;
			}
			ZDO zDO = view.GetZDO();
			if (zDO != null)
			{
				view.ResetZDO();
				_zdosToUnregisterScratch.Add(zDO);
				if (!zDO.Persistent && zDO.IsOwner())
				{
					ZDOMan.instance.DestroyZDO(zDO);
				}
				Object.Destroy((Object)(object)((Component)view).gameObject);
			}
		}

		private static void UnregisterDestroyedFromScene(ZNetScene scene)
		{
			for (int i = 0; i < _zdosToUnregisterScratch.Count; i++)
			{
				scene.m_instances.Remove(_zdosToUnregisterScratch[i]);
			}
			_zdosToUnregisterScratch.Clear();
		}
	}
	public static class SolidSurface
	{
		public const string DefaultLayerCsv = "Default,static_solid,Default_small,piece,terrain,vehicle";

		private static int _cachedMask;

		private static string _cachedLayerCsv;

		public static int Mask()
		{
			string text = FiresGhettoNetworkMod.ConfigDediFellOutRescueLayers?.Value ?? "Default,static_solid,Default_small,piece,terrain,vehicle";
			if (_cachedLayerCsv == text && _cachedMask != 0)
			{
				return _cachedMask;
			}
			int num = ParseLayerMask(text);
			if (num == 0)
			{
				LoggerOptions.LogWarning("[SolidSurface] '" + text + "' matched no layers in this build; falling back to terrain only.");
				num = LayerMask.GetMask(new string[1] { "terrain" });
			}
			_cachedMask = num;
			_cachedLayerCsv = text;
			return num;
		}

		private static int ParseLayerMask(string layerCsv)
		{
			List<string> list = new List<string>();
			string[] array = layerCsv.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i]?.Trim();
				if (!string.IsNullOrEmpty(text))
				{
					list.Add(text);
				}
			}
			if (list.Count <= 0)
			{
				return 0;
			}
			return LayerMask.GetMask(list.ToArray());
		}
	}
	[HarmonyPatch]
	public static class GroundSnapPatches
	{
		private const float ProbeStartHeightAboveObject = 2f;

		private const float ProbeMaxDistance = 8f;

		[HarmonyPatch(typeof(Character), "UnderWorldCheck")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> Character_UnderWorldCheck_UseSupportProbe(IEnumerable<CodeInstruction> instructions)
		{
			return RedirectGroundHeightToSupportProbe(instructions, "Character.UnderWorldCheck");
		}

		[HarmonyPatch(typeof(TombStone), "PositionCheck")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> TombStone_PositionCheck_UseSupportProbe(IEnumerable<CodeInstruction> instructions)
		{
			return RedirectGroundHeightToSupportProbe(instructions, "TombStone.PositionCheck");
		}

		[HarmonyPatch(typeof(ItemDrop), "TerrainCheck")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> ItemDrop_TerrainCheck_UseSupportProbe(IEnumerable<CodeInstruction> instructions)
		{
			return RedirectGroundHeightToSupportProbe(instructions, "ItemDrop.TerrainCheck");
		}

		[HarmonyPatch(typeof(Floating), "TerrainCheck")]
		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> Floating_TerrainCheck_UseSupportProbe(IEnumerable<CodeInstruction> instructions)
		{
			return RedirectGroundHeightToSupportProbe(instructions, "Floating.TerrainCheck");
		}

		private static IEnumerable<CodeInstruction> RedirectGroundHeightToSupportProbe(IEnumerable<CodeInstruction> instructions, string patchedMethod)
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			MethodInfo methodInfo = AccessTools.Method(typeof(GroundSnapPatches), "SupportOrGroundHeight", (Type[])null, (Type[])null);
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				if (!(list[i].opcode != OpCodes.Call) || !(list[i].opcode != OpCodes.Callvirt))
				{
					MethodInfo methodInfo2 = list[i].operand as MethodInfo;
					if (!(methodInfo2 == null) && !(methodInfo2.Name != "GetGroundHeight") && methodInfo2.GetParameters().Length == 1)
					{
						list[i] = new CodeInstruction(OpCodes.Call, (object)methodInfo);
						num++;
					}
				}
			}
			if (num == 0)
			{
				LoggerOptions.LogWarning("[GroundSnap] " + patchedMethod + " has no GetGroundHeight call to redirect; vanilla behaviour stands there.");
			}
			return list;
		}

		public static float SupportOrGroundHeight(ZoneSystem zoneSystem, Vector3 point)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: 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_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			float groundHeight = zoneSystem.GetGroundHeight(point);
			ConfigEntry<bool> configFixGroundSnapThroughFloors = FiresGhettoNetworkMod.ConfigFixGroundSnapThroughFloors;
			if (configFixGroundSnapThroughFloors != null && !configFixGroundSnapThroughFloors.Value)
			{
				return groundHeight;
			}
			if (point.y >= groundHeight)
			{
				return groundHeight;
			}
			if (ServerClientUtils.ZNetIsDedicated() && VoxelSurfaceProbe.TryGetSupport(point, out var surfaceY))
			{
				return surfaceY;
			}
			RaycastHit val = default(RaycastHit);
			if (Physics.Raycast(point + Vector3.up * 2f, Vector3.down, ref val, 8f, SolidSurface.Mask(), (QueryTriggerInteraction)1))
			{
				return ((RaycastHit)(ref val)).point.y;
			}
			return groundHeight;
		}
	}
	[HarmonyPatch]
	public static class HelmOwnership
	{
		private struct HandOff
		{
			public long Helmsman;

			public double At;
		}

		private struct EmptyShip
		{
			public Vector3 Position;

			public double EmptySince;

			public double AskedAt;
		}

		public static ConfigEntry<bool> ConfigEnabled;

		internal static bool ServerOwnsShips;

		private const double PassOnSteeringSeconds = 5.0;

		private const int PruneAboveEntries = 32;

		private const string ShipHandBackRpc = "FGN_ShipHandBack";

		private const double ShipPassSeconds = 2.0;

		private const double EmptySecondsBeforeHandBack = 5.0;

		private const double HandBackRetrySeconds = 10.0;

		private const float OccupiedRadiusMeters = 12f;

		private const float AtRestMeters = 1f;

		private const float AtRestSpeed = 0.5f;

		private const int SectorsEachSide = 1;

		private static readonly Dictionary<ZDOID, HandOff> s_handedOff = new Dictionary<ZDOID, HandOff>();

		private static readonly List<ZDOID> s_expired = new List<ZDOID>();

		private static readonly Dictionary<ZDOID, EmptyShip> s_emptyShips = new Dictionary<ZDOID, EmptyShip>();

		private static readonly HashSet<ZDOID> s_seenThisPass = new HashSet<ZDOID>();

		private static readonly HashSet<int> s_shipPrefabs = new HashSet<int>();

		private static bool s_shipPrefabsScanned;

		private static double s_nextShipPass;

		private static readonly FieldRef<ShipControlls, ZNetView> s_controlsView = AccessTools.FieldRefAccess<ShipControlls, ZNetView>("m_nview");

		private static readonly FieldRef<Ship, float> s_lastWaterImpactTime = AccessTools.FieldRefAccess<Ship, float>("m_lastWaterImpactTime");

		private static readonly Dictionary<int, bool> s_wasOwner = new Dictionary<int, bool>();

		internal static int HandoffSlamsHeld;

		internal static void HoldSlamOnTakeover(Ship ship, ZNetView view)
		{
			ConfigEntry<bool> configEnabled = ConfigEnabled;
			if (configEnabled != null && configEnabled.Value && !((Object)(object)view == (Object)null) && view.IsValid())
			{
				bool flag = view.IsOwner();
				int instanceID = ((Object)ship).GetInstanceID();
				if (flag && s_wasOwner.TryGetValue(instanceID, out var value) && !value)
				{
					s_lastWaterImpactTime.Invoke(ship) = Time.time;
					HandoffSlamsHeld++;
					LoggerOptions.LogInfo("[HelmOwnership] took over '" + ((Object)ship).name + "': its water-impact slam waits vanilla's 2 s while the hull settles into this peer's sea.");
				}
				s_wasOwner[instanceID] = flag;
			}
		}

		public static void InitConfig(ConfigFile config)
		{
			ConfigEnabled = config.Bind<bool>("11 - Ship Fixes", "Give The Ship To Its Helmsman", true, Cfg.Adv("Hands the ship to whoever takes the helm so steering never lags."));
		}

		internal static void OnShutdown()
		{
			s_handedOff.Clear();
			s_emptyShips.Clear();
			s_shipPrefabs.Clear();
			s_shipPrefabsScanned = false;
		}

		[HarmonyPatch(typeof(ShipControlls), "RPC_RequestControl")]
		[HarmonyPostfix]
		private static void OnRequestControl(ShipControlls __instance, long sender, long playerID)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			if (!Active() || sender == 0L || sender == ZDOMan.GetSessionID())
			{
				return;
			}
			ZNetView val = s_controlsView.Invoke(__instance);
			if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner())
			{
				return;
			}
			ZDO zDO = val.GetZDO();
			if (zDO.GetLong(ZDOVars.s_user, 0L) == playerID && __instance.m_ship.IsPlayerInBoat(playerID))
			{
				ZDOMan.instance.ForceSendZDO(zDO.m_uid);
				zDO.SetOwner(sender);
				if (s_handedOff.Count > 32)
				{
					PruneExpired();
				}
				s_handedOff[zDO.m_uid] = new HandOff
				{
					Helmsman = sender,
					At = Time.realtimeSinceStartupAsDouble
				};
				s_emptyShips.Remove(zDO.m_uid);
				Player player = Player.GetPlayer(playerID);
				LoggerOptions.LogMessage("[Helm] Handed the " + PrefabName(zDO) + " to " + (((Object)(object)player != (Object)null) ? player.GetPlayerName() : sender.ToString()) + ", who took its helm.");
			}
		}

		[HarmonyPatch(typeof(Ship), "RPC_Forward")]
		[HarmonyPrefix]
		private static bool OnForward(ZNetView ___m_nview, long sender)
		{
			return !PassOnSteering(___m_nview, sender, "Forward");
		}

		[HarmonyPatch(typeof(Ship), "RPC_Backward")]
		[HarmonyPrefix]
		private static bool OnBackward(ZNetView ___m_nview, long sender)
		{
			return !PassOnSteering(___m_nview, sender, "Backward");
		}

		[HarmonyPatch(typeof(Ship), "RPC_Stop")]
		[HarmonyPrefix]
		private static bool OnStop(ZNetView ___m_nview, long sender)
		{
			return !PassOnSteering(___m_nview, sender, "Stop");
		}

		[HarmonyPatch(typeof(Ship), "RPC_Rudder")]
		[HarmonyPrefix]
		private static bool OnRudder(ZNetView ___m_nview, long sender, float value)
		{
			return !PassOnSteering(___m_nview, sender, "Rudder", value);
		}

		internal static void RegisterPeerRpcs(ZNet znet, ZNetPeer peer)
		{
			if (!znet.IsServer())
			{
				peer.m_rpc.Register<ZDOID>("FGN_ShipHandBack", (Action<ZRpc, ZDOID>)OnShipHandBackRequested);
			}
		}

		internal static void ReturnEmptyShipsToServer(ZDOMan zdoMan)
		{
			//IL_00a5: 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_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			if (!Active() || !ServerOwnsShips || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsDedicated())
			{
				return;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			if (realtimeSinceStartupAsDouble < s_nextShipPass)
			{
				return;
			}
			s_nextShipPass = realtimeSinceStartupAsDouble + 2.0;
			if (!s_shipPrefabsScanned)
			{
				ScanShipPrefabs();
			}
			if (s_shipPrefabs.Count == 0)
			{
				return;
			}
			long sessionID = ZDOMan.GetSessionID();
			List<ZNetPeer> peers = ZNet.instance.GetPeers();
			s_seenThisPass.Clear();
			foreach (ZNetPeer item in peers)
			{
				if (item == null || !item.IsReady())
				{
					continue;
				}
				Vector2s zone = ZoneSystem.GetZone(item.GetRefPos());
				for (int i = -1; i <= 1; i++)
				{
					for (int j = -1; j <= 1; j++)
					{
						List<ZDO> list = zdoMan.m_objectsBySector[ZoneSystem.SectorToIndex(zone.x + j, zone.y + i).Sector];
						if (list == null)
						{
							continue;
						}
						for (int k = 0; k < list.Count; k++)
						{
							ZDO val = list[k];
							if (s_shipPrefabs.Contains(val.GetPrefab()) && s_seenThisPass.Add(val.m_uid))
							{
								ConsiderHandBack(val, sessionID, peers, realtimeSinceStartupAsDouble);
							}
						}
					}
				}
			}
			if (s_emptyShips.Count > 0)
			{
				ForgetShipsNotSeen();
			}
		}

		private static void ConsiderHandBack(ZDO zdo, long serverUid, List<ZNetPeer> peers, double now)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			long owner = zdo.GetOwner();
			ZNetPeer val = ((owner != 0L && owner != serverUid) ? ZNet.instance.GetPeer(owner) : null);
			Vector3 position = zdo.GetPosition();
			if (val == null || !val.IsReady() || !FgnVersionGate.IsMatched(val) || zdo.GetLong(ZDOVars.s_user, 0L) != 0L || AnyPlayerNear(peers, position))
			{
				s_emptyShips.Remove(zdo.m_uid);
				return;
			}
			if (s_emptyShips.TryGetValue(zdo.m_uid, out var value))
			{
				Vector3 val2 = value.Position - position;
				if (!(((Vector3)(ref val2)).sqrMagnitude > 1f))
				{
					if (!(now - value.EmptySince < 5.0) && !(now - value.AskedAt < 10.0))
					{
						value.AskedAt = now;
						s_emptyShips[zdo.m_uid] = value;
						val.m_rpc.Invoke("FGN_ShipHandBack", new object[1] { zdo.m_uid });
					}
					return;
				}
			}
			s_emptyShips[zdo.m_uid] = new EmptyShip
			{
				Position = position,
				EmptySince = now
			};
		}

		private static void OnShipHandBackRequested(ZRpc rpc, ZDOID id)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			ZDOMan instance = ZDOMan.instance;
			ZDO val = ((instance != null) ? instance.GetZDO(id) : null);
			ZNetView val2 = ((val != null && (Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(val) : null);
			ZNet instance2 = ZNet.instance;
			ZNetPeer val3 = ((instance2 != null) ? instance2.GetServerPeer() : null);
			if ((Object)(object)val2 == (Object)null || !val2.IsValid() || !val2.IsOwner() || val3 == null)
			{
				return;
			}
			Ship component = ((Component)val2).GetComponent<Ship>();
			if ((Object)(object)component == (Object)null || component.HasPlayerOnboard() || val.GetLong(ZD