Decompiled source of FiresGhettoNetworking v1.3.7

VAGhettoNetworking.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FiresCore.Logging;
using FiresGhettoNetworkMod.AutoTune;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("VAGhettoNetworking")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("VAGhettoNetworking")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("f234bd11-429c-45e2-b280-1fb9121d050d")]
[assembly: AssemblyFileVersion("1.3.7.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.7.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 FiresCore.Logging
{
	public sealed class FiresLog
	{
		private readonly string _prefix;

		private readonly Func<bool> _verboseEnabled;

		public bool VerboseEnabled => ReadVerboseSafely();

		public FiresLog(string modTag, Func<bool> verboseEnabled)
		{
			_prefix = "[" + modTag + "]";
			_verboseEnabled = verboseEnabled ?? ((Func<bool>)(() => false));
		}

		public void Info(string message)
		{
			Debug.Log((object)(_prefix + " " + message));
		}

		public void Verbose(string message)
		{
			if (ReadVerboseSafely())
			{
				Debug.Log((object)(_prefix + " " + message));
			}
		}

		public void Warning(string message)
		{
			Debug.LogWarning((object)(_prefix + " " + message));
		}

		public void Error(string message)
		{
			Debug.LogError((object)(_prefix + " " + message));
		}

		private bool ReadVerboseSafely()
		{
			try
			{
				return _verboseEnabled();
			}
			catch
			{
				return false;
			}
		}
	}
}
namespace FiresGhettoNetworkMod
{
	[BepInPlugin("com.Fire.FiresGhettoNetworkMod", "FiresGhettoNetworkMod", "1.3.7")]
	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.3.7";

		public static ConfigEntry<LogLevel> ConfigLogLevel;

		public static ConfigEntry<bool> ConfigEnableCompression;

		public static ConfigEntry<UpdateRateOptions> ConfigUpdateRate;

		public static ConfigEntry<SendRateMinOptions> ConfigSendRateMin;

		public static ConfigEntry<SendRateMaxOptions> ConfigSendRateMax;

		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> ConfigEnableFallThroughGuard;

		public static ConfigEntry<bool> ConfigEnableFallThroughDiagnostics;

		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<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> ConfigEnableRpcRouter;

		public static ConfigEntry<bool> ConfigEnableRpcAoI;

		public static ConfigEntry<float> ConfigRpcAoIRadius;

		public static ConfigEntry<bool> ConfigEnableZDODelta;

		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;

		private static bool _dummyRpcRegistered;

		internal static Harmony Harmony { get; private set; }

		public static FiresGhettoNetworkMod Instance { get; private set; }

		private void Awake()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			Instance = this;
			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);
			ServerClientUtils.Detect(((BaseUnityPlugin)this).Logger);
			bool isDedicatedServerDetected = ServerClientUtils.IsDedicatedServerDetected;
			if (isDedicatedServerDetected)
			{
				LoggerOptions.LogMessage("FiresGhettoNetworkMod v1.3.7 — Running on DEDICATED SERVER, enabling server-side features.");
			}
			else
			{
				LoggerOptions.LogMessage("FiresGhettoNetworkMod v1.3.7 — Running on CLIENT or SINGLE-PLAYER/LISTEN SERVER, only client-safe features will be applied.");
			}
			SafeInvokeInit("FiresGhettoNetworkMod.CompressionGroup", "InitConfig", new object[1] { ((BaseUnityPlugin)this).Config });
			SafeInvokeInit("FiresGhettoNetworkMod.NetworkingRatesGroup", "Init", new object[1] { ((BaseUnityPlugin)this).Config });
			SafeInvokeInit("FiresGhettoNetworkMod.DedicatedServerGroup", "Init", new object[1] { ((BaseUnityPlugin)this).Config });
			Harmony.PatchAll(typeof(CompressionGroup));
			Harmony.PatchAll(typeof(NetworkingRatesGroup));
			Harmony.PatchAll(typeof(DedicatedServerGroup));
			Harmony.PatchAll(typeof(SendZDOsHeartbeatDiagnostic));
			Harmony.PatchAll(typeof(FallThroughGuard));
			if (ConfigEnableFallThroughDiagnostics.Value)
			{
				Harmony.PatchAll(typeof(FallThroughProbe));
				Harmony.PatchAll(typeof(PieceTypeAudit));
			}
			Harmony.PatchAll(typeof(ServerDisconnectDiagnostics));
			Harmony.PatchAll(typeof(BulkTransferGatePatches));
			Harmony.PatchAll(typeof(BigZdoDiagnostic));
			Harmony.PatchAll(typeof(DisarmOverloadTest));
			Harmony.PatchAll(typeof(CompressionRoundTripTest));
			WackyDatabaseCompatibilityPatch.Init(Harmony);
			Harmony.PatchAll(typeof(PlayerPositionSyncPatches));
			Harmony.PatchAll(typeof(WearNTearClientSupportPatches));
			Harmony.PatchAll(typeof(ClientCleanupThrottle));
			Harmony.PatchAll(typeof(AutoTuneProbeHooks));
			Harmony.PatchAll(typeof(ZoneLoadPatches));
			ServerAutoTune.InitServerSide();
			if (isDedicatedServerDetected && ConfigEnableServerAuthority.Value)
			{
				if (ConfigEnableShipFixes.Value)
				{
					Harmony.PatchAll(typeof(ShipFixesGroup));
				}
				Harmony.PatchAll(typeof(ZDOMemoryManager));
				Harmony.PatchAll(typeof(ServerAuthorityPatches));
				Harmony.PatchAll(typeof(ServerStabilityPatches));
				Harmony.PatchAll(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.");
					}
					Harmony.PatchAll(typeof(ServerOwnershipPatchesV3));
					LoggerOptions.LogMessage("Server ZDO ownership (V3 SELECTIVE) ENABLED — Character/Ship only; drops/voxel/interactables/carts stay peer-owned.");
				}
				else if (ConfigEnableServerOwnership.Value)
				{
					Harmony.PatchAll(typeof(ServerOwnershipPatches));
					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.");
				}
				else
				{
					LoggerOptions.LogInfo("Server ZDO ownership transfer disabled (both V2 and V3 flags = false). Vanilla peer ownership in effect.");
				}
				Harmony.PatchAll(typeof(ZDOThrottlingPatches));
				Harmony.PatchAll(typeof(AILODPatches));
				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(TerrainComp), "Update");
				}
				if (ConfigEnableRpcRouter.Value)
				{
					Harmony.PatchAll(typeof(RpcRouterPatches));
					DamageTextHandler.Register();
					HealthChangedHandler.Register();
					WNTHealthChangedHandler.Register();
					SetTargetHandler.Register();
					AddNoiseHandler.Register();
					TriggerAnimationHandler.Register();
					TriggerOnDeathHandler.Register();
					TalkerSayHandler.Register();
					SpawnedZoneHandler.Register();
					VAGhettoLoadSummary.EmitRpcRouter(9, ConfigRpcAoIRadius?.Value ?? 256f, ConfigEnableRpcAoI?.Value ?? false);
					if (VAGhettoLoadSummary.VerboseEnabled)
					{
						LoggerOptions.LogInfo("RPC Router enabled — DamageText, HealthChanged, WNTHealthChanged, SetTarget, AddNoise, TriggerAnimation, TriggerOnDeath, TalkerSay, SpawnedZone handlers registered.");
					}
				}
				if (ConfigEnableZDODelta.Value)
				{
					Harmony.PatchAll(typeof(ZDODeltaPatches));
					LoggerOptions.LogInfo("ZDO delta compression enabled.");
				}
				if (ConfigEnableWNTServerOptimization.Value)
				{
					Harmony.PatchAll(typeof(WearNTearServerPatches));
					LoggerOptions.LogInfo("WearNTear server optimization enabled.");
				}
				VAGhettoLoadSummary.EmitServerAuthority(ConfigEnableServerOwnershipSelective.Value ? "V3 selective" : (ConfigEnableServerOwnership.Value ? "V2 broad" : "vanilla peer"), ConfigEnableZDOThrottling?.Value ?? false, ConfigEnableAILOD?.Value ?? false, ConfigEnableWNTServerOptimization?.Value ?? false);
				if (VAGhettoLoadSummary.VerboseEnabled)
				{
					LoggerOptions.LogInfo("All server-side features and authority patches enabled.");
				}
			}
			else if (!isDedicatedServerDetected)
			{
				LoggerOptions.LogInfo("Server-side features skipped — not running on a dedicated server.");
			}
			else
			{
				LoggerOptions.LogInfo("Server-side features disabled via ConfigEnableServerAuthority = false.");
			}
			((MonoBehaviour)this).StartCoroutine(RegisterDummyRpcWhenReady());
			((MonoBehaviour)this).StartCoroutine(EmitCompactBannerWhenZNetReady());
		}

		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();
			try
			{
				VAGhettoBanner.Print();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("FiresGhettoNetworkMod v1.3.7 loaded. (banner failed: " + ex.Message + ")"));
			}
		}

		private void OnApplicationQuit()
		{
		}

		private void TryPatchAll(Type type)
		{
			if (type == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Tried to patch a null type!");
			}
			else
			{
				Harmony.PatchAll(type);
			}
		}

		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 SafeInvokeInit(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 void BindConfigs()
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Expected O, but got Unknown
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Expected O, but got Unknown
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Expected O, but got Unknown
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Expected O, but got Unknown
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			//IL_024d: Expected O, but got Unknown
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0284: Expected O, but got Unknown
			//IL_02d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02de: Expected O, but got Unknown
			//IL_0391: Unknown result type (might be due to invalid IL or missing references)
			//IL_039b: Expected O, but got Unknown
			//IL_03ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d4: Expected O, but got Unknown
			//IL_03f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0403: Expected O, but got Unknown
			//IL_0456: Unknown result type (might be due to invalid IL or missing references)
			//IL_0460: Expected O, but got Unknown
			//IL_0481: Unknown result type (might be due to invalid IL or missing references)
			//IL_048b: Expected O, but got Unknown
			//IL_04b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_04bf: Expected O, but got Unknown
			//IL_04e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ea: Expected O, but got Unknown
			//IL_050b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0515: Expected O, but got Unknown
			//IL_0536: Unknown result type (might be due to invalid IL or missing references)
			//IL_0540: Expected O, but got Unknown
			//IL_0567: Unknown result type (might be due to invalid IL or missing references)
			//IL_0571: Expected O, but got Unknown
			//IL_05c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ce: Expected O, but got Unknown
			//IL_0601: Unknown result type (might be due to invalid IL or missing references)
			//IL_060b: Expected O, but got Unknown
			//IL_0634: Unknown result type (might be due to invalid IL or missing references)
			//IL_063e: Expected O, but got Unknown
			//IL_0691: Unknown result type (might be due to invalid IL or missing references)
			//IL_069b: Expected O, but got Unknown
			//IL_06ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_06f8: Expected O, but got Unknown
			//IL_072b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0735: Expected O, but got Unknown
			//IL_0768: Unknown result type (might be due to invalid IL or missing references)
			//IL_0772: Expected O, but got Unknown
			//IL_07bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_07c6: Expected O, but got Unknown
			//IL_0815: Unknown result type (might be due to invalid IL or missing references)
			//IL_081f: Expected O, but got Unknown
			//IL_0840: Unknown result type (might be due to invalid IL or missing references)
			//IL_084a: Expected O, but got Unknown
			//IL_086b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0875: Expected O, but got Unknown
			ConfigLogLevel = ((BaseUnityPlugin)this).Config.Bind<LogLevel>("01 - General", "Log Level", LogLevel.Message, "Controls verbosity in BepInEx log.");
			ConfigZoneLoadBatchSize = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Zone Load Batch Size", 1, new ConfigDescription("How aggressively the client consumes incoming zone-stream backlog per frame.\n1 = vanilla (one CreateObjects pass per frame, capped by Valheim).\n2 = double the per-frame cap (faster zone load, bigger frame hitches).\n4 = quadruple (zone-cross stutter masking on capable machines).\nAuto-Tune may override this on the client based on measured frame time.\nCLIENT-ONLY — no effect on server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>()));
			PlayerPositionSyncPatches.Init(((BaseUnityPlugin)this).Config);
			ConfigEnableCompression = ((BaseUnityPlugin)this).Config.Bind<bool>("04 - Networking", "Enable Compression", true, "Enable ZSTD network compression (highly recommended).");
			ConfigUpdateRate = ((BaseUnityPlugin)this).Config.Bind<UpdateRateOptions>("04 - Networking", "ZDO Send Rate", UpdateRateOptions._100, "How often the server SENDS ZDO updates to clients. This is a NETWORK send-cadence\nsetting ONLY — it does NOT change the world/game tick, day length, smelter/cook timers,\ncooldowns, or any simulation speed. Higher = other players/creatures look smoother to\nyou, at the cost of more bandwidth.\n100% (20 sends/sec) matches vanilla and is the safe default. 150% (30 sends/sec) can look\nsmoother on high-pop servers with bandwidth headroom; lower it if bandwidth is tight.\nSERVER-ONLY.");
			ConfigSendRateMin = ((BaseUnityPlugin)this).Config.Bind<SendRateMinOptions>("05 - Networking - Steamworks", "Send Rate Min", SendRateMinOptions._256KB, "Minimum send rate Steam will attempt.");
			ConfigSendRateMax = ((BaseUnityPlugin)this).Config.Bind<SendRateMaxOptions>("05 - Networking - Steamworks", "Send Rate Max", SendRateMaxOptions._512KB, "Maximum send rate Steam will attempt.");
			AutoTuneConfig.Init(((BaseUnityPlugin)this).Config);
			ConfigQueueSize = ((BaseUnityPlugin)this).Config.Bind<QueueSizeOptions>("04 - Networking", "Queue Size", QueueSizeOptions._32KB, "Send queue size. Higher helps high-player servers.");
			ConfigForceCrossplay = ((BaseUnityPlugin)this).Config.Bind<ForceCrossplayOptions>("09 - Dedicated Server", "Force Crossplay", ForceCrossplayOptions.steamworks, "Requires restart.\nsteamworks = Force crossplay DISABLED (Steam friends only)\nplayfab = Force crossplay ENABLED (PlayFab matchmaking)\nvanilla = Respect command-line -crossplay flag (default Valheim behavior)");
			ConfigPlayerLimit = ((BaseUnityPlugin)this).Config.Bind<int>("09 - Dedicated Server", "Player Limit", 10, new ConfigDescription("Max players on dedicated server. Requires restart.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 999), Array.Empty<object>()));
			ConfigAdvertisedPlayerLimit = ((BaseUnityPlugin)this).Config.Bind<int>("09 - Dedicated Server", "Advertised Player Limit", 0, new ConfigDescription("Max players advertised to matchmaking (Steam server browser → BattleMetrics, PlayFab session/Party). Independent of the actual in-game limit set by 'Player Limit' — useful when an operator wants their server listed as '/500' for marketing while running a real 30-slot cap. 0 = mirror 'Player Limit' (advertised matches reality). Requires restart.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 9999), Array.Empty<object>()));
			ConfigZoneLoadBatchSize = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Zone Load Batch Size", 1, new ConfigDescription("How aggressively the client consumes incoming zone-stream backlog per frame.\n1 = vanilla (one CreateObjects pass per frame, capped by Valheim).\n2 = double the per-frame cap (faster zone load, bigger frame hitches).\n4 = quadruple (zone-cross stutter masking on capable machines).\nAuto-Tune may override this on the client based on measured frame time.\nCLIENT-ONLY — no effect on server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>()));
			ConfigZPackageReceiveBufferSize = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "ZPackage Receive Buffer Bytes", 262144, new ConfigDescription("Steam recv-buffer size for inbound network packages. Bigger = fewer dropped packets\nif the client briefly stalls (GC pause, disk hitch), but uses more RAM.\nAuto-Tune may override this on the client based on measured tier.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(65536, 4194304), Array.Empty<object>()));
			ConfigEnableTimeSliceInstantiation = ((BaseUnityPlugin)this).Config.Bind<bool>("02 - Client Performance", "Enable Time-Slice Instantiation", true, "Replace vanilla's fixed 10/100 per-frame ZDO instantiation cap with a per-frame ms\nbudget that drains incoming objects across multiple ticks. Eliminates the big spike\nwhen crossing into a heavy zone. Disable to fall back to the cap-bump transpiler\n(set 'Zone Load Batch Size' to control its multiplier).\nCLIENT-ONLY — no effect on dedicated server.");
			ConfigInstantiationBudgetMs = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Instantiation Budget Ms", 3, new ConfigDescription("Per-frame millisecond budget for ZDO → GameObject instantiation when time-slicing\nis enabled. Lower = smoother frame times during zone load (longer ramp-in); higher\n= zone loads finish faster (bigger spikes). 3 ms keeps 60-fps clients comfortable.\nAuto-Tune overrides this with tier-specific values when active.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16), Array.Empty<object>()));
			ConfigMaxInstancesPerFrame = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Max Instances Per Frame", 100, new ConfigDescription("Hard ceiling on instantiations per frame regardless of how much budget remains.\nBelt-and-suspenders against a runaway pending list.\nAuto-Tune overrides this with tier-specific values when active.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 500), Array.Empty<object>()));
			ConfigSafetyFallbackEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("02 - Client Performance", "Safety Fallback Enabled", true, "When the pending instantiation list grows past 'Safety Fallback Threshold' items\n(teleport into megabase, freshly-loaded zones), widen the per-frame budget so we\ndon't bleed across many seconds. Capped at 16 ms so we never burn an entire frame.");
			ConfigSafetyFallbackThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Safety Fallback Threshold", 5000, new ConfigDescription("Pending-ZDO count at which the safety fallback widens the instantiation budget.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 50000), Array.Empty<object>()));
			ConfigEnableRpcRouter = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable RPC Router", true, "Server-side RPC filtering — drops unnecessary DamageText/HealthChanged RPCs before routing\nand prevents SetTarget exploits (targeting players/tamed creatures).\nSaves bandwidth and hardens server security.\nSERVER-ONLY — no effect on client.");
			ConfigEnableShipFixes = ((BaseUnityPlugin)this).Config.Bind<bool>("11 - Ship Fixes", "Enable Universal Ship Fixes", true, "Apply permanent autopilot + jitter fixes to ALL ships.");
			ConfigEnableServerSideShipSimulation = ((BaseUnityPlugin)this).Config.Bind<bool>("11 - Ship Fixes", "Server-Side Ship Simulation", false, "Server authoritatively simulates ship physics.\nDisabled by default — enable manually if you want the server to drive ship physics.");
			ConfigEnableRpcAoI = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable RPC Area-of-Interest", true, "When enabled, broadcast RPCs targeting a specific ZDO are only forwarded to peers\nwithin the configured radius of that ZDO. Massively reduces bandwidth on busy servers.\nRPCs without a target ZDO (global RPCs) are always broadcast to all peers.\nRequires Enable RPC Router = true. SERVER-ONLY.");
			ConfigRpcAoIRadius = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "RPC AoI Radius", 256f, new ConfigDescription("Distance (meters) from the target ZDO within which peers will receive the RPC.\nVanilla active area is ~7 zones × 64m = ~448m. Default 256m covers nearby players.\nSet higher if players report missing interactions at distance.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(64f, 1024f), Array.Empty<object>()));
			ConfigClientMaxDestroysPerFrame = ((BaseUnityPlugin)this).Config.Bind<int>("02 - Client Performance", "Max Destroys Per Frame", 200, new ConfigDescription("Maximum ZNetScene instances the client destroys per CreateDestroyObjects pass.\nVanilla destroys every out-of-area instance in a single frame, producing a\nmulti-second hitch when leaving a heavily-loaded zone (e.g. ~150k instances\non a megabase). The throttle defers the surplus to subsequent frames so the\ndeparture cost spreads out smoothly.\n0 = no throttle (vanilla single-frame destruction).\n200 = ~12s to clear a 150k-instance backlog at 60 fps, with each frame's\ndestroy cost roughly equal to instantiating 200 objects.\nCLIENT-ONLY — no effect on dedicated server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5000), Array.Empty<object>()));
			ConfigDediFellOutRescueLayers = ((BaseUnityPlugin)this).Config.Bind<string>("10 - Server Authority", "Dedi Fell-Out Rescue Layers", "Default,static_solid,Default_small,piece,terrain,vehicle", new ConfigDescription("Comma-separated Unity layer names the dedi raycasts against when rescuing a mob\nthat fell below the kill plane (y < -5000). Default mirrors vanilla BaseAI's\nsolid-ray mask so any vanilla collider catches the mob. If your modlist ships\ncustom terrain on a non-vanilla layer (RPGmaker overlay, etc.) and you see mobs\npermanently parked instead of recovering, add that layer name here. The cache\nrefreshes when this value changes; no restart needed.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigShowAILODInServerStatus = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "Show AILOD in ServerStatus", true, "When ON, appends the AILOD throttle's per-window stats (mobs examined,\nnear/mid/far decision counts, nearest-peer distance range) to the\nconsolidated [ServerStatus] line. Useful for tuning the near/far gates.\nTurn OFF once tuning is validated and you want a quieter log.\nHas no effect when Enable AI LOD Throttling is OFF — there's nothing to report.");
			ConfigDiagnosticIntervalSec = ((BaseUnityPlugin)this).Config.Bind<float>("01 - General", "Diagnostic Rollup Interval (sec)", 60f, new ConfigDescription("How often the consolidated [ServerStatus] line is logged on a dedicated server.\nSummarises MonoUpdaters tick rate, AI list sizes, BaseAI/MonsterAI tick counts,\nZNetScene instantiation, our CreateDestroyObjects / IsActiveAreaLoaded gate\nbehavior, and ServerOwnership transfer/release churn in a single line.\nLower = more visibility, more log spam. Higher = quieter. Change takes effect on the next window.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 3600f), Array.Empty<object>()));
			ConfigEnableBulkTransferBoost = ((BaseUnityPlugin)this).Config.Bind<bool>("04 - Networking", "Enable Bulk Transfer Queue Boost", true, new ConfigDescription("When ON, reflectively patches the 20 KB GetSendQueueSize gate inside every loaded ServerSync.ConfigSync (AzuEPI, Marketplace, EpicLoot, Wizardry, EW Data, etc.) AND ServerCharacters.Shared copy so their fragment loops keep up with our raised ZDOMan cap.\nReplaces the old BulkTransferGuard which suppressed ZDOMan.SendZDOs during bulk bursts and incidentally starved player position ZDOs (the source of 'players flying / teleporting / hits from across the map' complaints).\nDisable as a kill switch if you suspect the scan is causing freezes or false-positive patching.\nBoth sides — applies on client and dedicated server.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigBulkTransferBudgetPercent = ((BaseUnityPlugin)this).Config.Bind<int>("04 - Networking", "Bulk Transfer Budget Percent", 40, new ConfigDescription("Caps how much of the Steam per-connection send buffer the raised ServerSync gates may collectively claim, so many ServerSync mods (Azu/EpicLoot/Marketplace/EW/etc.) can't stack their raised gates and overflow the buffer (the cause of the heavy-area peer disconnects). The per-mod gate is computed as min(Queue Size, (buffer * this% / ServerSync-mod-count)), floored at the vanilla 20 KB so it never throttles tighter than stock. The buffer is 512 KB by default, or the larger value set when FiresSteamworksPatcher is installed. 40% leaves headroom for ZDO + RPC traffic. Lower if you still see heavy-area disconnects; raise if config syncs feel slow on join. Only matters when Bulk Transfer Queue Boost is ON.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 80), Array.Empty<object>()));
			ConfigEnableServerAuthority = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Server-Side Simulation", false, new ConfigDescription("Makes the server fully authoritative over zones, ZDO ownership, monster AI, events, etc. (does NOT override your existing ship fixes).\n\nDisabled by default — enable manually on your DEDICATED SERVER if desired.\n\nWARNING: THIS IS A SERVER-ONLY FEATURE!\nEnabling this on a CLIENT will cause INFINITE LOADING SCREEN.\nThe mod automatically disables it on clients regardless of this setting.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigEnableServerOwnership = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Server ZDO Ownership Transfer (EXPERIMENTAL)", false, new ConfigDescription("EXPERIMENTAL — defaults OFF. Direct port of the original Serverside Simulations mod's\nZDOMan.ReleaseNearbyZDOS prefix. When enabled, the server takes ownership of EVERY\npersistent ZDO in any peer's active area (mobs, ships, terrain, structures, items, doors,\nthe whole world). Peers no longer own anything.\n\nWHEN TO ENABLE: you've validated the rest of your modlist plays nicely with broad\nserver ownership and you want maximum server-side simulation (offloads client CPU,\nflips the bandwidth pattern so server send dominates).\n\nWHEN TO LEAVE OFF: you're running a heavy modpack, you've seen mob freezes or\ninteraction issues after a previous attempt, or you don't know yet. Leaving this off\npreserves vanilla peer ownership — every other server-authority feature (zones,\nspawning, raids, throttling) still works without it.\n\nREQUIRES: ConfigEnableServerAuthority = true AND running on a dedicated server.\nTOGGLE IS INDEPENDENT — flip this without touching ConfigEnableServerAuthority.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigEnableServerOwnershipSelective = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Server ZDO Ownership Transfer — Selective (EXPERIMENTAL)", false, new ConfigDescription("EXPERIMENTAL — defaults OFF. Selective variant of the broad ownership transfer above.\nOnly Character (non-Player) and Ship prefabs are claimed by the server. Drops,\ncontainers, doors, signs, workstations, pickables, beds, traders, wards, voxel\nterrain, built structures, and carts all stay under vanilla peer ownership.\n\nRATIONALE: broad SSS-style ownership (the toggle above) exposes interaction-RPC\nrace conditions — `removedrops` failing for mob-dropped items, voxel mining/flattening\nbreaking intermittently under load, carts shaking/sinking when parked. Selective scope\navoids all three by keeping interactables on the vanilla peer-owned path.\n\nMUTUALLY EXCLUSIVE with the broad toggle above. If both are true, this selective\nvariant takes precedence (the safer choice — drops/voxel/etc. stay working).\n\nREQUIRES: ConfigEnableServerAuthority = true AND running on a dedicated server.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigExtendedZoneRadius = ((BaseUnityPlugin)this).Config.Bind<int>("10 - Server Authority", "Extended Zone Radius", 1, new ConfigDescription("Additional zone layers the server pre-loads around players for smoother zone transitions.\n0 = vanilla (no extra pre-load)\n1 = +1 layer (recommended, ~7x7 zones total)\n2 = +2 layers (~9x9 zones)\n3 = +3 layers (~11x11 zones)\n\nHigher values reduce stutter when crossing zone borders but increase server CPU/RAM usage.\nSERVER-ONLY — clients ignore this setting.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3), Array.Empty<object>()));
			ConfigEnablePredictiveZoneStreaming = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable Predictive Zone Streaming", true, "Bias each peer's active-area center forward along their velocity vector so the\nserver starts loading zones BEFORE the peer crosses the boundary. Composes with\n'Extended Zone Radius' — the symmetric ring still expands, the center just slides\nforward. By the time the peer arrives, the predicted zones are already loaded.\nSERVER-ONLY — clients ignore this setting.");
			ConfigPredictionLookaheadSec = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "Prediction Lookahead Sec", 3f, new ConfigDescription("How many seconds ahead the server projects each peer's position when deciding\nwhich zones to pre-load. 3 s gives one zone of headroom at running speed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>()));
			ConfigPredictionMinVelocity = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "Prediction Min Velocity", 2f, new ConfigDescription("Minimum smoothed speed (m/s) below which prediction is suppressed and the peer's\nreal position is used. Stops idle / slow-walking peers from triggering pre-load.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 20f), Array.Empty<object>()));
			ConfigPredictionMaxLookaheadZones = ((BaseUnityPlugin)this).Config.Bind<int>("10 - Server Authority", "Prediction Max Lookahead Zones", 9, new ConfigDescription("Hard cap on prediction distance, expressed in zones (64 m each). Stops a\nteleporting / glitching peer from subscribing to zones across the world.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 25), Array.Empty<object>()));
			ConfigEnableZDOThrottling = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable ZDO Throttling", true, "Reduce update frequency for distant ZDOs (creatures/structures far away) to save bandwidth.\nSERVER-ONLY — no effect on client.");
			ConfigZDOThrottleDistance = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "ZDO Throttle Distance", 500f, new ConfigDescription("Distance (meters) beyond which ZDOs are throttled (lower update rate).\n0 = disable throttling.\nRecommended: 400-600m.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f), Array.Empty<object>()));
			ConfigEnableAILOD = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Enable AI LOD Throttling", true, "Reduce FixedUpdate frequency for distant AI (saves server CPU).\nNearby AI stays full speed for smooth combat.\nSERVER-ONLY — no effect on client.");
			ConfigAILODNearDistance = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "AI LOD Near Distance", 100f, new ConfigDescription("Full-speed AI within this range (meters).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(50f, 200f), Array.Empty<object>()));
			ConfigAILODFarDistance = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "AI LOD Far Distance", 300f, new ConfigDescription("Beyond this distance, AI is throttled (meters).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(200f, 600f), Array.Empty<object>()));
			ConfigAILODThrottleFactor = ((BaseUnityPlugin)this).Config.Bind<float>("10 - Server Authority", "AI LOD Throttle Factor", 0.5f, new ConfigDescription("Update multiplier for throttled AI (0.5 = half speed, 0.25 = quarter). Lower = more savings.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 0.75f), Array.Empty<object>()));
			ConfigEnableAdaptiveThrottling = ((BaseUnityPlugin)this).Config.Bind<bool>("10 - Server Authority", "Adaptive Throttling", true, "Only engage FGN's send-side optimizations (distant-ZDO throttling, player\nboost, AI LOD) when a peer's send queue is actually backing up. On a server\nwith bandwidth to spare, FGN leaves vanilla update order untouched — leaner\nand lower-latency. Turn OFF to force the optimizations on at all times.\nSERVER-ONLY.");
			ConfigSendCongestionThresholdPct = ((BaseUnityPlugin)this).Config.Bind<int>("10 - Server Authority", "Congestion Threshold", 50, new ConfigDescription("How full a peer's send queue must get (percent of cap) before Adaptive\nThrottling engages the optimizations. Lower = engages sooner.\nSERVER-ONLY.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 100), Array.Empty<object>()));
			ConfigEnableSendHeartbeatLog = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Log Send Queue Heartbeat", false, "Diagnostic: log each peer's send-queue health every 10s on a dedicated\nserver. Useful when investigating lag, but writes ~1 line per player per\n10s to the log. Leave OFF for normal play. SERVER-ONLY.");
			ZDOMemoryManager.ConfigMaxZDOs = ((BaseUnityPlugin)this).Config.Bind<int>("12 - Advanced", "Max Active ZDOs", 500000, new ConfigDescription("If the number of active ZDOs exceeds this value, the mod will force cleanup of orphan non-persistent ZDOs and run garbage collection.\nSet to 0 to disable. Useful on very long-running servers with high entity counts.\nDefault: 500000 (vanilla rarely goes above ~200k).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000000), Array.Empty<object>()));
			ConfigEnableBootPatchVerification = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Boot Patch Verification", false, new ConfigDescription("OFF by default. When ON, logs every Harmony patch attached to the AI tick,\ninstantiation, and zone-gate methods this mod cares about — useful when\ntroubleshooting mod-conflict scenarios (another mod's transpiler stomping our\nprefix, etc.) or when bringing up a new feature.\n\nThe runtime [ServerStatus] rollup is the ongoing health indicator; this toggle\nis only useful when you suspect Harmony itself didn't attach something.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigEnableLargeZdoDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Large ZDO Diagnostics", false, new ConfigDescription("OFF by default. When ON, adds enriched [BigZdoDiag] log lines next to vanilla's\nexisting 'Writing a lot of data; X items, is not optimal' warning so you can see\nwhich ZDO is bloating its extra-data buckets (uid, prefab name, world position,\nowner peer id, bucket type and count).\n\nNOTE: the underlying warning is emitted by vanilla Valheim, not by this mod.\nThis toggle only controls whether we ATTACH context to it. Turn this on when you\nsee the vanilla warning and want to track down the offending entity; leave it off\notherwise so the log stays quiet.", (AcceptableValueBase)null, Array.Empty<object>()));
			ConfigEnableZDODelta = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable ZDO Delta Compression", true, "On re-syncs, only send ZDO fields that changed since last send to each peer.\nInitial sync always sends full ZDO state. Re-syncs only send the diff.\nSignificant bandwidth reduction for high-field ZDOs (creatures, players) where\nonly 1-2 fields change per tick (e.g. health, position).\nSERVER-ONLY — no effect on client.");
			ConfigEnableWNTServerOptimization = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable WearNTear Server Optimization", true, "Skips structural support recalculation for building pieces that are at full health,\nnot wet, and not in the Ashlands. Support cannot change for intact static pieces,\nso this is a safe CPU saving on servers with large player bases.\nAlso short-circuits damaged-but-invulnerable pieces (Infinity Hammer, admin-flagged)\nsince their support state can't change either.\nSERVER-ONLY — no effect on client.");
			ConfigEnableInvulnerableSupportSkip = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Invulnerable Support Skip", true, "CLIENT-side counterpart to the WearNTear server optimization. Short-circuits the\nexpensive WearNTear.UpdateSupport call (Physics.OverlapBoxNonAlloc per piece) for\npieces whose damage modifiers are all Immune/Ignore — e.g. Infinity Hammer pieces.\nPins m_support at the material's max value so neighbouring mortal pieces still\nsee full support when querying. Massive steady-state CPU saving in megabases\ndominated by invulnerable pieces.");
			ConfigEnableInstanceOrphanPrune = ((BaseUnityPlugin)this).Config.Bind<bool>("12 - Advanced", "Enable Instance Orphan Prune", true, "SERVER-ONLY defensive cleanup. Before vanilla ZNetScene.RemoveObjects walks\nm_instances on the dedicated server, scan for entries whose ZNetView is\nUnity-destroyed OR whose view.GetZDO() returns null, and remove just the dict\nentry (the GameObject is left alone). These are exactly the entries that would\nNRE vanilla RemoveObjects, so we're only purging things vanilla can't handle.\nTriggered by mods that block WearNTear.RPC_Remove on the server while ZDOMan\nstill reaps the ZDO via a separate path — e.g. TargetPortalProtection's\nPlayer.m_localPlayer-based permission check on a headless dedi when ZDO\nownership has been moved to the server (FGN's Server-Side Simulation).\n\nEvery prune is logged at warning level (rate-limited to once per 5s) and the\nper-window count appears in the [ServerStatus] CDO segment. If you see this\nfiring repeatedly on a healthy server, another mod is mismanaging ZNetScene\nstate and should be investigated. Disable as a kill switch if it ever causes\ntrouble (you'd then see the original NRE caught by the existing fallback).");
			ConfigEnableFallThroughGuard = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "Enable Fall-Through Guard", true, "Stops dropped items and tombstones from sinking through floors, decks, and other\nstructures right after they appear. On a busy server an item can spawn a frame\nbefore the floor under it finishes loading, so gravity pulls it through before the\ncollider exists. With this on, an item that spawns with nothing beneath it is held\nin place until its support loads in (or a few seconds pass), then drops normally —\nso it lands on the floor instead of vanishing under the world. Runs on whichever\nside owns the item (the player's client, or the server under Server-Side Simulation).");
			ConfigEnableFallThroughDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "Enable Fall-Through Diagnostics", false, "Verbose, opt-in diagnostics for investigating items and tombstones sinking through\nstructures. Off by default. When on, two probes run: a per-spawn probe that raycasts\nunder each dropped item/tombstone and logs the ones at risk (and any that actually\nfall), and a one-shot startup audit that names build pieces left non-Solid (the load\norder that lets an item spawn before its support). The per-spawn probe adds real cost\nand log volume on a busy server, so leave this OFF for normal play and the live read —\nturn it on only to investigate a fall-through report. The fix itself is the separate\nEnable Fall-Through Guard toggle, which stays on independently of this.");
			ConfigEntryBase[] array = (ConfigEntryBase[])(object)new ConfigEntryBase[47]
			{
				(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)ZDOMemoryManager.ConfigMaxZDOs,
				(ConfigEntryBase)ConfigEnableZDODelta,
				(ConfigEntryBase)ConfigShowAILODInServerStatus,
				(ConfigEntryBase)ConfigEnableBootPatchVerification,
				(ConfigEntryBase)ConfigEnableLargeZdoDiagnostics,
				(ConfigEntryBase)ConfigEnableWNTServerOptimization,
				(ConfigEntryBase)ConfigZoneLoadBatchSize,
				(ConfigEntryBase)ConfigZPackageReceiveBufferSize,
				(ConfigEntryBase)ConfigEnableTimeSliceInstantiation,
				(ConfigEntryBase)ConfigInstantiationBudgetMs,
				(ConfigEntryBase)ConfigMaxInstancesPerFrame,
				(ConfigEntryBase)ConfigSafetyFallbackEnabled,
				(ConfigEntryBase)ConfigSafetyFallbackThreshold,
				(ConfigEntryBase)ConfigEnablePredictiveZoneStreaming,
				(ConfigEntryBase)ConfigPredictionLookaheadSec,
				(ConfigEntryBase)ConfigPredictionMinVelocity,
				(ConfigEntryBase)ConfigPredictionMaxLookaheadZones,
				(ConfigEntryBase)ConfigEnableInvulnerableSupportSkip,
				(ConfigEntryBase)ConfigEnableInstanceOrphanPrune,
				(ConfigEntryBase)ConfigEnableFallThroughGuard,
				(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;
			}
		}

		private void Start()
		{
			((MonoBehaviour)this).StartCoroutine(RegisterDummyRpcWhenReady());
		}

		private IEnumerator RegisterDummyRpcWhenReady()
		{
			while (ZRoutedRpc.instance == null)
			{
				yield return null;
			}
			if (_dummyRpcRegistered)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Dummy ForceUpdateZDO RPC already registered — skipping.");
				yield break;
			}
			ZRoutedRpc.instance.Register("ForceUpdateZDO", (Action<long>)delegate
			{
			});
			_dummyRpcRegistered = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Dummy ForceUpdateZDO RPC registered.");
		}
	}
	public enum LogLevel
	{
		[Description("Errors/Warnings only")]
		Warning,
		[Description("Errors/Warnings/Messages [default]")]
		Message,
		[Description("Everything including Info")]
		Info
	}
	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 SendRateMinOptions
	{
		[Description("1024 KB/s | 8 Mbit/s")]
		_1024KB,
		[Description("768 KB/s | 6 Mbit/s")]
		_768KB,
		[Description("512 KB/s | 4 Mbit/s")]
		_512KB,
		[Description("256 KB/s | 2 Mbit/s [default]")]
		_256KB,
		[Description("150 KB/s | 1.2 Mbit/s [vanilla]")]
		_150KB
	}
	public enum SendRateMaxOptions
	{
		[Description("1024 KB/s | 8 Mbit/s")]
		_1024KB,
		[Description("768 KB/s | 6 Mbit/s")]
		_768KB,
		[Description("512 KB/s | 4 Mbit/s [default]")]
		_512KB,
		[Description("256 KB/s | 2 Mbit/s")]
		_256KB,
		[Description("150 KB/s | 1.2 Mbit/s [vanilla]")]
		_150KB
	}
	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
	}
	[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)
		{
			//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 value = FiresGhettoNetworkMod.ConfigAILODNearDistance.Value;
			float value2 = FiresGhettoNetworkMod.ConfigAILODFarDistance.Value;
			if (num <= value)
			{
				ServerStatusDiagnostics.s_ailod_decidedNear++;
				return true;
			}
			if (num > value2)
			{
				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 / FiresGhettoNetworkMod.ConfigAILODThrottleFactor.Value;
			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), "SaveAsync")]
		[HarmonyPrefix]
		public static void ZDOMan_SaveAsync_Prefix()
		{
			s_bulkSaveInProgress = true;
		}

		[HarmonyPatch(typeof(ZDOMan), "SaveAsync")]
		[HarmonyFinalizer]
		public static void ZDOMan_SaveAsync_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);
			}
		}

		[HarmonyPatch(typeof(ZDO), "Serialize")]
		[HarmonyPrefix]
		public static void ZDO_Serialize_Prefix(ZDO __instance)
		{
			if (__instance != null && LargeZdoDiagnosticIsEnabled())
			{
				TryReportOversizedBuckets(__instance, "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_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: 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_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			return new BucketSnapshot
			{
				Floats = ZDOExtraData.GetSaveFloats(uid).Count,
				Vector3s = ZDOExtraData.GetSaveVec3s(uid).Count,
				Quaternions = ZDOExtraData.GetSaveQuaternions(uid).Count,
				Ints = ZDOExtraData.GetSaveInts(uid).Count,
				Longs = ZDOExtraData.GetSaveLongs(uid).Count,
				Strings = ZDOExtraData.GetSaveStrings(uid).Count,
				ByteArrays = ZDOExtraData.GetSaveByteArrays(uid).Count
			};
		}

		private static BucketSnapshot SnapshotLiveBuckets(ZDOID uid)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: 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_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			return new BucketSnapshot
			{
				Floats = ZDOExtraData.GetFloats(uid).Count,
				Vector3s = ZDOExtraData.GetVec3s(uid).Count,
				Quaternions = ZDOExtraData.GetQuaternions(uid).Count,
				Ints = ZDOExtraData.GetInts(uid).Count,
				Longs = ZDOExtraData.GetLongs(uid).Count,
				Strings = ZDOExtraData.GetStrings(uid).Count,
				ByteArrays = ZDOExtraData.GetByteArrays(uid).Count
			};
		}

		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;

		[HarmonyPatch(typeof(ZNet), "Start")]
		[HarmonyPostfix]
		private 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_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Expected O, but got Unknown
			int targetQueueSize = GetTargetQueueSize();
			s_serverSyncCopyCount = CountServerSyncCopies();
			s_budgetedGate = GetBudgetedGate();
			LoggerOptions.LogMessage($"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.LogMessage("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;
			Target[] targets = Targets;
			for (int i = 0; i < targets.Length; i++)
			{
				Target target = targets[i];
				bool flag = s_budgetedGate > target.VanillaConstant;
				int num3 = 0;
				int num4 = 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;
					}
					num3++;
					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))
							{
								try
								{
									s_currentVanillaConstant = target.VanillaConstant;
									s_lastTimeoutSitesDisarmed = 0;
									harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(BulkTransferGatePatches), "RaiseGateAndDisarmTimeout", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
									num4++;
									LoggerOptions.LogMessage("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 += num3;
				num2 += num4;
				LoggerOptions.LogMessage($"Bulk-transfer scan for {target.TypeName}: {num3} copies found, {num4} 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).");
			ApplyJotunnTimeout();
			LoggerOptions.LogMessage((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;
					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 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)
		{
			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 ((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;
					}
					if (flag && flag2)
					{
						return true;
					}
				}
			}
			return false;
		}

		public static IEnumerable<CodeInstruction> RaiseGateAndDisarmTimeout(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Expected O, but got Unknown
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: Expected O, but got Unknown
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Expected O, but got Unknown
			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] = new CodeInstruction(OpCodes.Ldc_I4, (object)num2);
				}
				else if (list[j].opcode == OpCodes.Ldc_R4 && list[j].operand is float num5 && num5 == 30f)
				{
					list[j] = new CodeInstruction(OpCodes.Ldsfld, (object)TimeoutField);
					num3++;
				}
				else if (list[j].opcode == OpCodes.Ldc_R8 && list[j].operand is double num6 && num6 == 30.0)
				{
					list[j] = new CodeInstruction(OpCodes.Ldc_R8, (object)86400.0);
					num3++;
				}
			}
			s_lastTimeoutSitesDisarmed = num3;
			return list;
		}

		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.QueueSize() switch
			{
				QueueSizeOptions._80KB => 81920, 
				QueueSizeOptions._64KB => 65536, 
				QueueSizeOptions._48KB => 49152, 
				QueueSizeOptions._32KB => 32768, 
				_ => 20000, 
			};
		}
	}
	[HarmonyPatch]
	public static class ClientCleanupThrottle
	{
		private static readonly HashSet<ZNetView> _pendingDestroySet = new HashSet<ZNetView>();

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

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

		[HarmonyPatch(typeof(ZNetScene), "RemoveObjects")]
		[HarmonyPrefix]
		public static bool RemoveObjects_ClientThrottle_Prefix(ZNetScene __instance, List<ZDO> currentNearObjects, List<ZDO> currentDistantObjects)
		{
			if (IsDedicatedServer())
			{
				return true;
			}
			int num = FiresGhettoNetworkMod.ConfigClientMaxDestroysPerFrame?.Value ?? 0;
			if (num <= 0)
			{
				return true;
			}
			byte b = ComputeCurrentFrameEarmark();
			MarkZdosAsStillInArea(currentNearObjects, b);
			MarkZdosAsStillInArea(currentDistantObjects, b);
			EnqueueOutOfAreaInstancesForDeferredDestroy(__instance, b);
			DestroyUpToBudget(__instance, b, num);
			return false;
		}

		private static bool IsDedicatedServer()
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return ZNet.instance.IsDedicated();
			}
			return false;
		}

		private static byte ComputeCurrentFrameEarmark()
		{
			return (byte)(Time.frameCount & 0xFF);
		}

		private static void MarkZdosAsStillInArea(List<ZDO> zdos, byte mark)
		{
			if (zdos == null)
			{
				return;
			}
			for (int i = 0; i < zdos.Count; i++)
			{
				ZDO val = zdos[i];
				if (val != null)
				{
					val.TempRemoveEarmark = mark;
				}
			}
		}

		private static void EnqueueOutOfAreaInstancesForDeferredDestroy(ZNetScene scene, byte frameEarmark)
		{
			foreach (KeyValuePair<ZDO, ZNetView> instance in scene.m_instances)
			{
				ZNetView value = instance.Value;
				if (!((Object)(object)value == (Object)null))
				{
					ZDO zDO = value.GetZDO();
					if (zDO != null && !IsStillInActiveArea(zDO, frameEarmark) && _pendingDestroySet.Add(value))
					{
						_pendingDestroyQueue.Enqueue(value);
					}
				}
			}
		}

		private static void DestroyUpToBudget(ZNetScene scene, byte frameEarmark, int budget)
		{
			_zdosToUnregisterScratch.Clear();
			int num = 0;
			while (_pendingDestroyQueue.Count > 0 && num < budget)
			{
				ZNetView val = _pendingDestroyQueue.Dequeue();
				_pendingDestroySet.Remove(val);
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				ZDO zDO = val.GetZDO();
				if (zDO != null && !IsStillInActiveArea(zDO, frameEarmark))
				{
					val.ResetZDO();
					_zdosToUnregisterScratch.Add(zDO);
					if (!zDO.Persistent && zDO.IsOwner())
					{
						ZDOMan.instance.DestroyZDO(zDO);
					}
					Object.Destroy((Object)(object)((Component)val).gameObject);
					num++;
				}
			}
			UnregisterDestroyedFromScene(scene);
		}

		private static bool IsStillInActiveArea(ZDO zdo, byte frameEarmark)
		{
			return zdo.TempRemoveEarmark == frameEarmark;
		}

		private static void UnregisterDestroyedFromScene(ZNetScene scene)
		{
			for (int i = 0; i < _zdosToUnregisterScratch.Count; i++)
			{
				scene.m_instances.Remove(_zdosToUnregisterScratch[i]);
			}
			_zdosToUnregisterScratch.Clear();
		}
	}
	[HarmonyPatch]
	public static class CompressionGroup
	{
		internal static class CompressionStatus
		{
			public class SocketStatus
			{
				public int version;

				public bool compressionEnabled;

				public bool sendingCompressed;

				public bool receivingCompressed;
			}

			private const int COMPRESSION_VERSION = 8;

			public static readonly SocketStatus ourStatus = new SocketStatus
			{
				version = 8,
				compressionEnabled = false
			};

			private static readonly Dictionary<ISocket, SocketStatus> peerStatus = new Dictionary<ISocket, SocketStatus>();

			public static void AddPeer(ISocket socket)
			{
				if (socket != null)
				{
					if (peerStatus.ContainsKey(socket))
					{
						peerStatus.Remove(socket);
					}
					peerStatus[socket] = new SocketStatus();
					LoggerOptions.LogMessage("Compression: New peer connected " + socket.GetEndPointString());
				}
			}

			public static void RemovePeer(ISocket socket)
			{
				peerStatus.Remove(socket);
			}

			public static SocketStatus GetStatus(ISocket socket)
			{
				if (!peerStatus.TryGetValue(socket, out var value))
				{
					return null;
				}
				return value;
			}

			public static bool IsCompatible(ISocket socket)
			{
				SocketStatus status = GetStatus(socket);
				if (status != null)
				{
					return status.version == ourStatus.version;
				}
				return false;
			}

			public static bool GetSendCompressionStarted(ISocket socket)
			{
				return GetStatus(socket)?.sendingCompressed ?? false;
			}

			public static bool GetReceiveCompressionStarted(ISocket socket)
			{
				return GetStatus(socket)?.receivingCompressed ?? false;
			}

			public static void SetSendCompressionStarted(ISocket socket, bool started)
			{
				GetStatus(socket).sendingCompressed = started;
			}

			public static void SetReceiveCompressionStarted(ISocket socket, bool started)
			{
				GetStatus(socket).receivingCompressed = started;
			}
		}

		public static ConfigEntry<bool> ConfigCompressionEnabled;

		private static readonly byte[] CompressionMagic = new byte[4] { 70, 71, 68, 49 };

		private const string RPC_COMPRESSION_VERSION = "FiresGhetto.CompressionVersion";

		private const string RPC_COMPRESSION_ENABLED = "FiresGhetto.CompressionEnabled";

		private const string RPC_COMPRESSION_STARTED = "FiresGhetto.CompressedStarted";

		public static void InitConfig(ConfigFile config)
		{
			ConfigCompressionEnabled = FiresGhettoNetworkMod.ConfigEnableCompression;
			ConfigCompressionEnabled.SettingChanged += delegate
			{
				SetCompressionEnabledFromConfig();
			};
			CompressionStatus.ourStatus.compressionEnabled = ConfigCompressionEnabled?.Value ?? false;
		}

		private static void SetCompressionEnabledFromConfig()
		{
			bool value = ConfigCompressionEnabled.Value;
			CompressionStatus.ourStatus.compressionEnabled = value;
			LoggerOptions.LogMessage("Network compression: " + (value ? "Enabled" : "Disabled"));
			SendCompressionEnabledStatusToAll();
		}

		[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
		[HarmonyPostfix]
		private static void OnNewConnection(ZNetPeer peer)
		{
			CompressionStatus.AddPeer(peer.m_socket);
			RegisterRPCs(peer);
			SendCompressionVersion(peer);
		}

		[HarmonyPatch(typeof(ZNet), "Disconnect")]
		[HarmonyPostfix]
		private static void OnDisconnect(ZNetPeer peer)
		{
			CompressionStatus.RemovePeer(peer.m_socket);
		}

		private static void RegisterRPCs(ZNetPeer peer)
		{
			peer.m_rpc.Register<int>("FiresGhetto.CompressionVersion", (Action<ZRpc, int>)RPC_CompressionVersion);
			peer.m_rpc.Register<bool>("FiresGhetto.CompressionEnabled", (Action<ZRpc, bool>)RPC_CompressionEnabled);
			peer.m_rpc.Register<bool>("FiresGhetto.CompressedStarted", (Action<ZRpc, bool>)RPC_CompressionStarted);
		}

		private static void SendCompressionVersion(ZNetPeer peer)
		{
			peer.m_rpc.Invoke("FiresGhetto.CompressionVersion", new object[1] { CompressionStatus.ourStatus.version });
		}

		private static void RPC_CompressionVersion(ZRpc rpc, int version)
		{
			ZNetPeer val = FindPeerByRpc(rpc);
			if (val != null)
			{
				CompressionStatus.SocketStatus status = CompressionStatus.GetStatus(val.m_socket);
				if (status != null)
				{
					status.version = version;
				}
				if (version == CompressionStatus.ourStatus.version)
				{
					LoggerOptions.LogMessage("Compression compatible with " + GetPeerName(val));
				}
				else
				{
					LoggerOptions.LogWarning($"Compression version mismatch with {GetPeerName(val)} (them: {version}, us: {CompressionStatus.ourStatus.version})");
				}
				if (CompressionStatus.IsCompatible(val.m_socket))
				{
					SendCompressionEnabledStatus(val);
				}
			}
		}

		private static void SendCompressionEnabledStatusToAll()
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (CompressionStatus.IsCompatible(peer.m_socket))
				{
					SendCompressionEnabledStatus(peer);
				}
			}
		}

		private static void SendCompressionEnabledStatus(ZNetPeer peer)
		{
			peer.m_rpc.Invoke("FiresGhetto.CompressionEnabled", new object[1] { CompressionStatus.ourStatus.compressionEnabled });
			bool started = CompressionStatus.ourStatus.compressionEnabled && (CompressionStatus.GetStatus(peer.m_socket)?.compressionEnabled ?? false);
			SendCompressionStarted(peer, started);
		}

		private static void RPC_CompressionEnabled(ZRpc rpc, bool enabled)
		{
			ZNetPeer val = FindPeerByRpc(rpc);
			if (val != null)
			{
				CompressionStatus.SocketStatus status = CompressionStatus.GetStatus(val.m_socket);
				if (status != null)
				{
					status.compressionEnabled = enabled;
				}
				bool started = CompressionStatus.ourStatus.compressionEnabled && enabled;
				SendCompressionStarted(val, started);
			}
		}

		private static void SendCompressionStarted(ZNetPeer peer, bool started)
		{
			CompressionStatus.SocketStatus status = CompressionStatus.GetStatus(peer.m_socket);
			if (status != null && status.sendingCompressed != started)
			{
				peer.m_rpc.Invoke("FiresGhetto.CompressedStarted", new object[1] { started });
				Flush(peer);
				status.sendingCompressed = started;
				LoggerOptions.LogMessage("Compression " + (started ? "started" : "stopped") + " with " + GetPeerName(peer));
			}
		}

		private static void Flush(ZNetPeer peer)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			OnlineBackendType onlineBackend = ZNet.m_onlineBackend;
			if ((int)onlineBackend != 0)
			{
				_ = 1;
			}
			else
			{
				peer.m_socket.Flush();
			}
		}

		private static void RPC_CompressionStarted(ZRpc rpc, bool started)
		{
			ZNetPeer val = FindPeerByRpc(rpc);
			if (val != null)
			{
				CompressionStatus.SocketStatus status = CompressionStatus.GetStatus(val.m_socket);
				if (status != null)
				{
					status.receivingCompressed = started;
				}
				LoggerOptions.LogMessage("Receiving " + (started ? "compressed" : "uncompressed") + " data from " + GetPeerName(val));
			}
		}

		internal static byte[] Compress(byte[] data)
		{
			if (data == null || data.Length == 0)
			{
				return data;
			}
			if (HasCompressionHeader(data))
			{
				return data;
			}
			return AddCompressionHeaderIfUseful(data, Deflate(data));
		}

		internal static byte[] Decompress(byte[] data)
		{
			if (!HasCompressionHeader(data))
			{
				return data;
			}
			return Inflate(StripCompressionHeader(data));
		}

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

		private static byte[] Inflate(byte[] data)
		{
			using MemoryStream stream = new MemoryStream(data);
			using DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress);
			using MemoryStream memoryStream = new MemoryStream();
			deflateStream.CopyTo(memoryStream);
			return memoryStream.ToArray();
		}

		private static bool HasCompressionHeader(byte[] data)
		{
			if (data == null || data.Length < CompressionMagic.Length)
			{
				return false;
			}
			for (int i = 0; i < CompressionMagic.Length; i++)
			{
				if (data[i] != CompressionMagic[i])
				{
					return false;
				}
			}
			return true;
		}

		private static byte[] AddCompressionHeaderIfUseful(byte[] original, byte[] compressed)
		{
			if (compressed == null || original == null)
			{
				return original;
			}
			if (compressed.Length + CompressionMagic.Length >= original.Length)
			{
				return original;
			}
			byte[] array = new byte[compressed.Length + CompressionMagic.Length];
			Buffer.BlockCopy(CompressionMagic, 0, array, 0, CompressionMagic.Length);
			Buffer.BlockCopy(compressed, 0, array, CompressionMagic.Length, compressed.Length);
			return array;
		}

		private static byte[] StripCompressionHeader(byte[] data)
		{
			byte[] array = new byte[data.Length - CompressionMagic.Length];
			Buffer.BlockCopy(data, CompressionMagic.Length, array, 0, array.Length);
			return array;
		}

		[HarmonyPatch(typeof(ZSteamSocket), "SendQueuedPackages")]
		[HarmonyPrefix]
		private static bool Steam_SendCompressed(ref Queue<byte[]> ___m_sendQueue, ZSteamSocket __instance)
		{
			if (!CompressionStatus.GetSendCompressionStarted((ISocket)(object)__instance))
			{
				return true;
			}
			___m_sendQueue = new Queue<byte[]>(___m_sendQueue.Select((byte[] p) => Compress(p)));
			return true;
		}

		[HarmonyPatch(typeof(ZSteamSocket), "Recv")]
		[HarmonyPostfix]
		private static void Steam_RecvCompressed(ref ZPackage __result, ZSteamSocket __instance)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			if (__result == null)
			{
				return;
			}
			byte[] array = __result.GetArray();
			if (!HasCompressionHeader(array))
			{
				return;
			}
			try
			{
				__result = new ZPackage(Decompress(array));
			}
			catch
			{
				LoggerOptions.LogWarning("Compression: framed packet failed to decompress — passing through unchanged.");
			}
		}

		private static ZNetPeer FindPeerByRpc(ZRpc rpc)
		{
			try
			{
				if (rpc == null || ZRoutedRpc.instance == null)
				{
					return null;
				}
				return ((List<ZNetPeer>)AccessTools.Field(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance))?.FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_rpc == rpc));
			}
			catch
			{
				return null;
			}
		}

		private static string GetPeerName(ZNetPeer peer)
		{
			if (peer == null)
			{
				return "unknown";
			}
			try
			{
				if (peer.m_socket != null)
				{
					return peer.m_socket.GetEndPointString();
				}
			}
			catch
			{
			}
			return peer.m_uid.ToString();
		}
	}
	[HarmonyPatch]
	public static class CompressionRoundTripTest
	{
		private const string RpcStart = "FGN_CompTestStart";

		private const string RpcData = "FGN_CompTestData";

		private const string RpcStatus = "FGN_CompTestStatus";

		private const int DefaultCount = 20;

		private const int DefaultSizeKB = 32;

		private const int MaxCount = 100;

		private const int MaxSizeKB = 128;

		private static bool s_commandRegistered;

		private static long s_nonceSeq;

		private static long s_expectNonce;

		private static int s_expectCount;

		private static int s_received;

		private static int s_corrupt;

		private static int s_outOfOrder;

		private static int s_lastSeq;

		[HarmonyPatch(typeof(ZNet), "Start")]
		[HarmonyPostfix]
		public static void OnZNetStart()
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Expected O, but got Unknown
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			if (ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.Register<int, int, long>("FGN_CompTestStart", (Action<long, int, int, long>)RPC_Start);
				ZRoutedRpc.instance.Register<ZPackage>("FGN_CompTestData", (Action<long, ZPackage>)RPC_Data);
				ZRoutedRpc.instance.Register<string>("FGN_CompTestStatus", (Action<long, string>)RPC_Status);
				if (!s_commandRegistered)
				{
					s_commandRegistered = true;
					new ConsoleCommand("fgn_comptest", "[count] [sizeKB] — FGN diagnostic: round-trip-test Deflate compression. Does a local Compress/Decompress check, then has the server burst N compressible packets (default 20 x 32KB) at you and verifies every one decompressed intact.", new ConsoleEvent(OnCommand), false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
				}
			}
		}

		private static void OnCommand(ConsoleEventArgs args)
		{
			int result = 20;
			int result2 = 32;
			if (args.Length >= 2)
			{
				int.TryParse(args[1], out result);
			}
			if (args.Length >= 3)
			{
				int.TryParse(args[2], out result2);
			}
			result = Mathf.Clamp(result, 1, 100);
			result2 = Mathf.Clamp(result2, 1, 128);
			if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null)
			{
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString("FGN: not connected.");
				}
				return;
			}
			byte[] array = MakePayload(result2 * 1024, 0);
			byte[] array2 = CompressionGroup.Compress(array);
			bool flag = array2.Length < array.Length;
			byte[] array3 = CompressionGroup.Decompress(array2);
			bool flag2 = array3 != null && array3.Length == array.Length && Checksum(array3) == Checksum(array);
			Terminal context2 = args.Context;
			if (context2 != null)
			{
				context2.AddString(string.Format("FGN comptest local {0}KB: {1} ", result2, flag2 ? "OK" : "MISMATCH") + string.Format("(compressed {0}->{1} bytes, magic={2}).", array.Length, array2.Length, flag ? "yes" : "no/uncompressible"));
			}
			s_expectNonce = ++s_nonceSeq;
			s_expectCount = result;
			s_received = 0;
			s_corrupt = 0;
			s_outOfOrder = 0;
			s_lastSeq = -1;
			ZRoutedRpc.instance.InvokeRoutedRPC("FGN_CompTestStart", new object[3] { result, result2, s_expectNonce });
			float num = ReportDelay(result, result2);
			Terminal context3 = args.Context;
			if (context3 != null)
			{
				context3.AddString($"FGN comptest wire: requested {result} x {result2}KB compressed packets from the server. Result in ~{num:F0}s (if you DISCONNECT mid-test, that's the bug).");
			}
			if ((Object)(object)FiresGhettoNetworkMod.Instance != (Object)null)
			{
				((MonoBehaviour)FiresGhettoNetworkMod.Instance).StartCoroutine(ReportAfter(num, args.Context));
			}
		}

		private static float ReportDelay(int count, int sizeKB)
		{
			return Mathf.Clamp(5f + (float)(count * sizeKB) / 200f, 5f, 60f);
		}

		private static IEnumerator ReportAfter(float seconds, Terminal ctx)
		{
			yield return (object)new WaitForSeconds(seconds);
			bool flag = s_received == s_expectCount && s_corrupt == 0 && s_outOfOrder == 0;
			string arg = (flag ? "Every compressed packet round-tripped intact — the fix holds." : ((s_received < s_expectCount) ? "Missing packets = compressed data read as raw (mis-parse) — the start-boundary corruption." : "Corruption = the compression round-trip is broken."));
			string text = string.Format("[CompTest] {0} — received {1}/{2}, ", flag ? "PASS" : "FAIL", s_received, s_expectCount) + $"corrupt={s_corrupt}, out-of-order={s_outOfOrder}. {arg}";
			if (ctx != null)
			{
				ctx.AddString(text);
			}
			LoggerOptions.LogMessage(text);
		}

		private static void RPC_Start(long sender, int count, int sizeKB, long nonce)
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			if (!SenderIsAdmin(sender))
			{
				Status(sender, "FGN comptest denied — admin only.");
				return;
			}
			count = Mathf.Clamp(count, 1, 100);
			sizeKB = Mathf.Clamp(sizeKB, 1, 128);
			bool flag = IsCompressionActive(sender);
			Status(sender, $"FGN comptest: server bursting {count} x {sizeKB}KB to you (compression to you: " + (flag ? "ACTIVE" : "INACTIVE — packets go raw, so a PASS proves nothing; enable compression both sides") + ").");
			if ((Object)(object)FiresGhettoNetworkMod.Instance != (Object)null)
			{
				((MonoBehaviour)FiresGhettoNetworkMod.Instance).StartCoroutine(SendBurst(sender, count, sizeKB, nonce));
			}
		}

		private static bool IsCompressionActive(long uid)
		{
			try
			{
				ZNetPeer peer = ZNet.instance.GetPeer(uid);
				return peer?.m_socket != null && CompressionGroup.CompressionStatus.GetSendCompressionStarted(peer.m_socket);
			}
			catch
			{
				return false;
			}
		}

		private static IEnumerator SendBurst(long target, int count, int sizeKB, long nonce)
		{
			int size = sizeKB * 1024;
			for (int seq = 0; seq < count; seq++)
			{
				ZPackage val = new ZPackage();
				val.Write(nonce);
				val.Write(seq);
				byte[] array = MakePayload(size, seq);
				val.Write(array);
				val.Write(Checksum(array));
				ZRoutedRpc.instance.InvokeRoutedRPC(target, "FGN_CompTestData", new object[1] { val });
				if ((seq & 0xF) == 15)
				{
					yield return null;
				}
			}
		}

		private static void RPC_Data(long sender, ZPackage pkg)
		{
			try
			{
				if (pkg.ReadLong() == s_expectNonce)
				{
					int num = pkg.ReadInt();
					byte[] array = pkg.ReadByteArray();
					int num2 = pkg.ReadInt();
					if (Checksum(array) != num2 || !PayloadMatches(array, num))
					{
						s_corrupt++;
					}
					if (num != s_lastSeq + 1)
					{
						s_outOfOrder++;
					}
					s_lastSeq = num;
					s_received++;
				}
			}
			catch
			{
				s_corrupt++;
			}
		}

		private static void RPC_Status(long sender, string msg)
		{
			if ((Object)(object)Console.instance != (Object)null)
			{
				((Terminal)Console.instance).AddString(msg);
			}
			else
			{
				LoggerOptions.LogMessage(msg);
			}
		}

		private static void Status(long target, string msg)
		{
			try
			{
				if (ZRoutedRpc.instance != null)
				{
					ZRoutedRpc.instance.InvokeRoutedRPC(target, "FGN_CompTestStatus", new object[1] { msg });
				}
			}
			catch
			{
			}
		}

		private static bool SenderIsAdmin(long sender)
		{
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			if (peer == null)
			{
				return true;
			}
			ZRpc rpc = peer.m_rpc;
			object obj;
			if (rpc == null)
			{
				obj = null;
			}
			else
			{
				ISocket socket = rpc.GetSocket();
				obj = ((socket != null) ? socket.GetHostName() : null);
			}
			string text = (string)obj;
			if (!string.IsNullOrEmpty(text))
			{
				return ZNet.instance.IsAdmin(text);
			}
			return false;
		}

		private static byte[] MakePayload(int size, int seq)
		{
			byte[] array = new byte[size];
			for (int i = 0; i < size; i++)
			{
				array[i] = (byte)((i + seq) & 0xFF);
			}
			return array;
		}

		private static bool PayloadMatches(byte[] p, int seq)
		{
			if (p == null || p.Length == 0)
			{
				return false;
			}
			int num = p.Length;
			int[] array = new int[5]
			{
				0,
				num / 3,
				num / 2,
				2 * num / 3,
				num - 1
			};
			foreach (int num2 in array)
			{
				if (p[num2] != (byte)((num2 + seq) & 0xFF))
				{
					return false;
				}
			}
			return true;
		}

		private static int Checksum(byte[] data)
		{
			uint num = 2166136261u;
			for (int i = 0; i < data.Length; i++)
			{
				num ^= data[i];
				num *= 16777619;
			}
			return (int)num;
		}
	}
	[HarmonyPatch]
	public static class DedicatedServerGroup
	{
		private static bool isDedicatedDetected;

		public static void Init(ConfigFile config)
		{
			LoggerOptions.LogInfo("Dedicated server features initialized.");
			isDedicatedDetected = ServerClientUtils.IsDedicatedServerDetected;
			if (isDedicatedDetected)
			{
				LoggerOptions.LogInfo("Running as dedicated server (ServerClientUtils).");
			}
			else
			{
				LoggerOptions.LogInfo("Running as client/listen-server (ServerClientUtils).");
			}
		}

		[HarmonyPatch(typeof(FejdStartup), "ParseServerArguments")]
		[HarmonyPostfix]
		private static void ApplyForceCrossplay()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if (isDedicatedDetected)
			{
				switch (FiresGhettoNetworkMod.ConfigForceCrossplay.Value)
				{
				case ForceCrossplayOptions.playfab:
					ZNet.m_onlineBackend = (OnlineBackendType)1;
					LoggerOptions.LogInfo("Forcing crossplay ENABLED (PlayFab backend).");
					break;
				case ForceCrossplayOptions.steamworks:
					ZNet.m_onlineBackend = (OnlineBackendType)0;
					LoggerOptions.LogInfo("Forcing crossplay DISABLED (Steamworks backend).");
					break;
				default:
					LoggerOptions.LogInfo("Crossplay mode: vanilla (respecting command line).");
					break;
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> OverridePlayerLimit(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Invalid comparison between Unknown and I4
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Expected O, but got Unknown
			if (!isDedicatedDetected)
			{
				return instructions;
			}
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			bool flag = false;
			for (int i = 0; i < list.Count; i++)
			{
				if (!(list[i].opcode == OpCodes.Call) || !(list[i].operand is MethodInfo { Name: "GetNrOfPlayers" }))
				{
					continue;
				}
				for (int j = i + 1; j < list.Count; j++)
				{
					if (list[j].opcode == OpCodes.Ldc_I4_S || list[j].opcode == OpCodes.Ldc_I4 || list[j].opcode == OpCodes.Ldc_I4_0 || list[j].opcode == OpCodes.Ldc_I4_1 || list[j].opcode == OpCodes.Ldc_I4_2 || list[j].opcode == OpCodes.Ldc_I4_3 || list[j].opcode == OpCodes.Ldc_I4_4 || list[j].opcode == OpCodes.Ldc_I4_5 || list[j].opcode == OpCodes.Ldc_I4_6 || list[j].opcode == OpCodes.Ldc_I4_7 || list[j].opcode == OpCodes.Ldc_I4_8)
					{
						int num = FiresGhettoNetworkMod.ConfigPlayerLimit.Value;
						if ((int)ZNet.m_onlineBackend == 1)
						{
							num++;
							LoggerOptions.LogInfo("Applied +1 player limit for PlayFab backend.");
						}
						L