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 _verboseEnabled; public bool VerboseEnabled => ReadVerboseSafely(); public FiresLog(string modTag, Func verboseEnabled) { _prefix = "[" + modTag + "]"; _verboseEnabled = verboseEnabled ?? ((Func)(() => 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(true).Length != 0; bool flag = ((Component)item).GetComponentsInChildren(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 ConfigLogLevel; public static ConfigEntry ConfigEnableCompression; public static ConfigEntry ConfigUpdateRate; public static ConfigEntry ConfigSendRateMin; public static ConfigEntry ConfigSendRateMax; public static ConfigEntry ConfigQueueSize; public static ConfigEntry ConfigForceCrossplay; public static ConfigEntry ConfigPlayerLimit; public static ConfigEntry ConfigAdvertisedPlayerLimit; public static ConfigEntry ConfigEnableShipFixes; public static ConfigEntry ConfigEnableServerSideShipSimulation; public static ConfigEntry ConfigExtendedZoneRadius; public static ConfigEntry ConfigEnableZDOThrottling; public static ConfigEntry ConfigZDOThrottleDistance; public static ConfigEntry ConfigEnableAILOD; public static ConfigEntry ConfigAILODNearDistance; public static ConfigEntry ConfigAILODFarDistance; public static ConfigEntry ConfigAILODThrottleFactor; public static ConfigEntry ConfigEnableAdaptiveThrottling; public static ConfigEntry ConfigSendCongestionThresholdPct; public static ConfigEntry ConfigEnableSendHeartbeatLog; public static ConfigEntry ConfigEnableFallThroughGuard; public static ConfigEntry ConfigEnableFallThroughDiagnostics; public static ConfigEntry ConfigZoneLoadBatchSize; public static ConfigEntry ConfigZPackageReceiveBufferSize; public static ConfigEntry ConfigEnableTimeSliceInstantiation; public static ConfigEntry ConfigInstantiationBudgetMs; public static ConfigEntry ConfigMaxInstancesPerFrame; public static ConfigEntry ConfigSafetyFallbackEnabled; public static ConfigEntry ConfigSafetyFallbackThreshold; public static ConfigEntry ConfigEnablePredictiveZoneStreaming; public static ConfigEntry ConfigPredictionLookaheadSec; public static ConfigEntry ConfigPredictionMinVelocity; public static ConfigEntry ConfigPredictionMaxLookaheadZones; public static ConfigEntry ConfigEnableInvulnerableSupportSkip; public static ConfigEntry ConfigEnableInstanceOrphanPrune; public static ConfigEntry ConfigEnableRpcRouter; public static ConfigEntry ConfigEnableRpcAoI; public static ConfigEntry ConfigRpcAoIRadius; public static ConfigEntry ConfigEnableZDODelta; public static ConfigEntry ConfigEnableWNTServerOptimization; public static ConfigEntry ConfigEnableServerAuthority; public static ConfigEntry ConfigEnableServerOwnership; public static ConfigEntry ConfigEnableServerOwnershipSelective; public static ConfigEntry ConfigShowAILODInServerStatus; public static ConfigEntry ConfigEnableBootPatchVerification; public static ConfigEntry ConfigEnableLargeZdoDiagnostics; public static ConfigEntry ConfigClientMaxDestroysPerFrame; public static ConfigEntry ConfigDediFellOutRescueLayers; public static ConfigEntry ConfigDiagnosticIntervalSec; public static ConfigEntry ConfigEnableBulkTransferBoost; public static ConfigEntry 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 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("01 - General", "Log Level", LogLevel.Message, "Controls verbosity in BepInEx log."); ConfigZoneLoadBatchSize = ((BaseUnityPlugin)this).Config.Bind("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(1, 8), Array.Empty())); PlayerPositionSyncPatches.Init(((BaseUnityPlugin)this).Config); ConfigEnableCompression = ((BaseUnityPlugin)this).Config.Bind("04 - Networking", "Enable Compression", true, "Enable ZSTD network compression (highly recommended)."); ConfigUpdateRate = ((BaseUnityPlugin)this).Config.Bind("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("05 - Networking - Steamworks", "Send Rate Min", SendRateMinOptions._256KB, "Minimum send rate Steam will attempt."); ConfigSendRateMax = ((BaseUnityPlugin)this).Config.Bind("05 - Networking - Steamworks", "Send Rate Max", SendRateMaxOptions._512KB, "Maximum send rate Steam will attempt."); AutoTuneConfig.Init(((BaseUnityPlugin)this).Config); ConfigQueueSize = ((BaseUnityPlugin)this).Config.Bind("04 - Networking", "Queue Size", QueueSizeOptions._32KB, "Send queue size. Higher helps high-player servers."); ConfigForceCrossplay = ((BaseUnityPlugin)this).Config.Bind("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("09 - Dedicated Server", "Player Limit", 10, new ConfigDescription("Max players on dedicated server. Requires restart.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 999), Array.Empty())); ConfigAdvertisedPlayerLimit = ((BaseUnityPlugin)this).Config.Bind("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(0, 9999), Array.Empty())); ConfigZoneLoadBatchSize = ((BaseUnityPlugin)this).Config.Bind("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(1, 8), Array.Empty())); ConfigZPackageReceiveBufferSize = ((BaseUnityPlugin)this).Config.Bind("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(65536, 4194304), Array.Empty())); ConfigEnableTimeSliceInstantiation = ((BaseUnityPlugin)this).Config.Bind("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("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(1, 16), Array.Empty())); ConfigMaxInstancesPerFrame = ((BaseUnityPlugin)this).Config.Bind("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(10, 500), Array.Empty())); ConfigSafetyFallbackEnabled = ((BaseUnityPlugin)this).Config.Bind("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("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(100, 50000), Array.Empty())); ConfigEnableRpcRouter = ((BaseUnityPlugin)this).Config.Bind("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("11 - Ship Fixes", "Enable Universal Ship Fixes", true, "Apply permanent autopilot + jitter fixes to ALL ships."); ConfigEnableServerSideShipSimulation = ((BaseUnityPlugin)this).Config.Bind("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("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("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(64f, 1024f), Array.Empty())); ConfigClientMaxDestroysPerFrame = ((BaseUnityPlugin)this).Config.Bind("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(0, 5000), Array.Empty())); ConfigDediFellOutRescueLayers = ((BaseUnityPlugin)this).Config.Bind("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())); ConfigShowAILODInServerStatus = ((BaseUnityPlugin)this).Config.Bind("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("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(10f, 3600f), Array.Empty())); ConfigEnableBulkTransferBoost = ((BaseUnityPlugin)this).Config.Bind("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())); ConfigBulkTransferBudgetPercent = ((BaseUnityPlugin)this).Config.Bind("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(10, 80), Array.Empty())); ConfigEnableServerAuthority = ((BaseUnityPlugin)this).Config.Bind("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())); ConfigEnableServerOwnership = ((BaseUnityPlugin)this).Config.Bind("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())); ConfigEnableServerOwnershipSelective = ((BaseUnityPlugin)this).Config.Bind("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())); ConfigExtendedZoneRadius = ((BaseUnityPlugin)this).Config.Bind("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(0, 3), Array.Empty())); ConfigEnablePredictiveZoneStreaming = ((BaseUnityPlugin)this).Config.Bind("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("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(0.5f, 10f), Array.Empty())); ConfigPredictionMinVelocity = ((BaseUnityPlugin)this).Config.Bind("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(0.5f, 20f), Array.Empty())); ConfigPredictionMaxLookaheadZones = ((BaseUnityPlugin)this).Config.Bind("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(1, 25), Array.Empty())); ConfigEnableZDOThrottling = ((BaseUnityPlugin)this).Config.Bind("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("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(0f, 1000f), Array.Empty())); ConfigEnableAILOD = ((BaseUnityPlugin)this).Config.Bind("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("10 - Server Authority", "AI LOD Near Distance", 100f, new ConfigDescription("Full-speed AI within this range (meters).", (AcceptableValueBase)(object)new AcceptableValueRange(50f, 200f), Array.Empty())); ConfigAILODFarDistance = ((BaseUnityPlugin)this).Config.Bind("10 - Server Authority", "AI LOD Far Distance", 300f, new ConfigDescription("Beyond this distance, AI is throttled (meters).", (AcceptableValueBase)(object)new AcceptableValueRange(200f, 600f), Array.Empty())); ConfigAILODThrottleFactor = ((BaseUnityPlugin)this).Config.Bind("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(0.25f, 0.75f), Array.Empty())); ConfigEnableAdaptiveThrottling = ((BaseUnityPlugin)this).Config.Bind("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("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(10, 100), Array.Empty())); ConfigEnableSendHeartbeatLog = ((BaseUnityPlugin)this).Config.Bind("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("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(0, 1000000), Array.Empty())); ConfigEnableBootPatchVerification = ((BaseUnityPlugin)this).Config.Bind("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())); ConfigEnableLargeZdoDiagnostics = ((BaseUnityPlugin)this).Config.Bind("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())); ConfigEnableZDODelta = ((BaseUnityPlugin)this).Config.Bind("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("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("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("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("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("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)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 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 ""; } } [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 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 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 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 RaiseGateAndDisarmTimeout(IEnumerable 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 list = new List(instructions); List list2 = new List(); 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 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 _pendingDestroySet = new HashSet(); private static readonly Queue _pendingDestroyQueue = new Queue(); private static readonly List _zdosToUnregisterScratch = new List(256); [HarmonyPatch(typeof(ZNetScene), "RemoveObjects")] [HarmonyPrefix] public static bool RemoveObjects_ClientThrottle_Prefix(ZNetScene __instance, List currentNearObjects, List 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 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 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 peerStatus = new Dictionary(); 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 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("FiresGhetto.CompressionVersion", (Action)RPC_CompressionVersion); peer.m_rpc.Register("FiresGhetto.CompressionEnabled", (Action)RPC_CompressionEnabled); peer.m_rpc.Register("FiresGhetto.CompressedStarted", (Action)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 ___m_sendQueue, ZSteamSocket __instance) { if (!CompressionStatus.GetSendCompressionStarted((ISocket)(object)__instance)) { return true; } ___m_sendQueue = new Queue(___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)AccessTools.Field(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance))?.FirstOrDefault((Func)((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("FGN_CompTestStart", (Action)RPC_Start); ZRoutedRpc.instance.Register("FGN_CompTestData", (Action)RPC_Data); ZRoutedRpc.instance.Register("FGN_CompTestStatus", (Action)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 OverridePlayerLimit(IEnumerable 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 list = new List(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."); } LoggerOptions.LogInfo($"Overriding player limit constant → {num}"); list[j] = new CodeInstruction(OpCodes.Ldc_I4, (object)num); flag = true; break; } } if (flag) { break; } } if (!flag) { LoggerOptions.LogWarning("Player limit constant not found in ZNet.RPC_PeerInfo. Patch skipped – possible game update or conflicting mod."); } return list; } private static int ResolveAdvertisedLimit(bool addPlayFabHostSlot) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 int num = FiresGhettoNetworkMod.ConfigAdvertisedPlayerLimit?.Value ?? 0; int num2 = ((num > 0) ? num : FiresGhettoNetworkMod.ConfigPlayerLimit.Value); if (addPlayFabHostSlot && (int)ZNet.m_onlineBackend == 1) { num2++; } return num2; } private static bool IsIntConstantLoad(CodeInstruction ins) { OpCode opcode = ins.opcode; if (!(opcode == OpCodes.Ldc_I4) && !(opcode == OpCodes.Ldc_I4_S) && !(opcode == OpCodes.Ldc_I4_0) && !(opcode == OpCodes.Ldc_I4_1) && !(opcode == OpCodes.Ldc_I4_2) && !(opcode == OpCodes.Ldc_I4_3) && !(opcode == OpCodes.Ldc_I4_4) && !(opcode == OpCodes.Ldc_I4_5) && !(opcode == OpCodes.Ldc_I4_6) && !(opcode == OpCodes.Ldc_I4_7)) { return opcode == OpCodes.Ldc_I4_8; } return true; } [HarmonyPatch(typeof(SteamMatchmaking), "CreateLobby")] [HarmonyPrefix] private static void OverrideSteamLobbyMaxMembers(ELobbyType eLobbyType, ref int cMaxMembers) { if (isDedicatedDetected) { int num = ResolveAdvertisedLimit(addPlayFabHostSlot: false); if (num > 0 && cMaxMembers != num) { LoggerOptions.LogInfo($"Overriding SteamMatchmaking.CreateLobby cMaxMembers: {cMaxMembers} → {num}"); cMaxMembers = num; } } } [HarmonyPatch(typeof(SteamGameServer), "SetMaxPlayerCount")] [HarmonyPrefix] private static void OverrideSteamGameServerMaxPlayers(ref int cPlayersMax) { if (isDedicatedDetected) { int num = ResolveAdvertisedLimit(addPlayFabHostSlot: false); if (num > 0 && cPlayersMax != num) { LoggerOptions.LogInfo($"Overriding SteamGameServer.SetMaxPlayerCount cPlayersMax: {cPlayersMax} → {num}"); cPlayersMax = num; } } } private static List RewriteConstBeforeFieldStore(IEnumerable instructions, string fieldName, string methodLabel, bool addPlayFabHostSlot, bool silentIfNotFound = false) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown List list = (instructions as List) ?? new List(instructions); bool flag = false; for (int i = 0; i < list.Count; i++) { CodeInstruction val = list[i]; if (!(val.opcode != OpCodes.Stfld) && val.operand is FieldInfo fieldInfo && !(fieldInfo.Name != fieldName) && i != 0 && IsIntConstantLoad(list[i - 1])) { int num = ResolveAdvertisedLimit(addPlayFabHostSlot); LoggerOptions.LogInfo($"Overriding {methodLabel} {fieldName} → {num}"); list[i - 1] = new CodeInstruction(OpCodes.Ldc_I4, (object)num); flag = true; break; } } if (!flag && !silentIfNotFound) { LoggerOptions.LogWarning(fieldName + " constant not found in " + methodLabel + ". Patch skipped."); } return list; } private static List RewriteConstBeforePropertySet(List list, string propertyName, string methodLabel, bool addPlayFabHostSlot) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown string text = "set_" + propertyName; bool flag = false; for (int i = 0; i < list.Count; i++) { CodeInstruction val = list[i]; if ((!(val.opcode != OpCodes.Callvirt) || !(val.opcode != OpCodes.Call)) && val.operand is MethodInfo methodInfo && !(methodInfo.Name != text) && i != 0 && IsIntConstantLoad(list[i - 1])) { int num = ResolveAdvertisedLimit(addPlayFabHostSlot); LoggerOptions.LogInfo($"Overriding {methodLabel} {propertyName} → {num}"); list[i - 1] = new CodeInstruction(OpCodes.Ldc_I4, (object)num); flag = true; break; } } if (!flag) { LoggerOptions.LogWarning(propertyName + " constant not found in " + methodLabel + ". Patch skipped."); } return list; } } [HarmonyPatch] public static class DisarmOverloadTest { private const int ForcedQueueBytes = 50000; private const int DefaultSeconds = 60; private const int MaxSeconds = 300; private const float ArmedTimeoutSeconds = 30f; private const string RpcStart = "FGN_OverloadStart"; private const string RpcResult = "FGN_OverloadResult"; private static bool s_active; private static long s_forceCount; private static bool s_commandRegistered; [HarmonyPatch(typeof(ZSteamSocket), "GetSendQueueSize")] [HarmonyPostfix] public static void ForceQueueHigh(ref int __result) { if (s_active && __result < 50000) { __result = 50000; s_forceCount++; } } [HarmonyPatch(typeof(ZNet), "Start")] [HarmonyPostfix] public static void OnZNetStart() { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register("FGN_OverloadStart", (Action)RPC_Start); ZRoutedRpc.instance.Register("FGN_OverloadResult", (Action)RPC_Result); if (!s_commandRegistered) { s_commandRegistered = true; new ConsoleCommand("fgn_overload", "[seconds] [arm] — FGN diagnostic (admin): force the send queue above the ServerSync 20 KB gate for N seconds to test the 30 s self-disconnect disarm. Add 'arm' to momentarily restore the real 30 s timeout as a control — a client reconnecting then SHOULD drop at 30 s, proving the test path is live.", new ConsoleEvent(OnCommand), false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } } private static void OnCommand(ConsoleEventArgs args) { int result = 60; if (args.Length >= 2) { int.TryParse(args[1], out result); } result = Mathf.Clamp(result, 5, 300); int num = ((args.Length >= 3 && args[2] != null && args[2].ToLower().StartsWith("arm")) ? 1 : 0); if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { Terminal context = args.Context; if (context != null) { context.AddString("FGN: not connected to a server."); } return; } ZRoutedRpc.instance.InvokeRoutedRPC("FGN_OverloadStart", new object[2] { result, num }); Terminal context2 = args.Context; if (context2 != null) { context2.AddString(string.Format("FGN: overload test ({0}s{1}) requested. ", result, (num == 1) ? ", ARMED control" : "") + "Reconnect a client now; watch here and the server console for [OverloadTest]."); } } private static void RPC_Start(long sender, int seconds, int arm) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } if (!SenderIsAdmin(sender)) { Reply(sender, "FGN overload test denied — admin only."); return; } if (s_active) { Reply(sender, "FGN overload test already running."); return; } seconds = Mathf.Clamp(seconds, 5, 300); if ((Object)(object)FiresGhettoNetworkMod.Instance != (Object)null) { ((MonoBehaviour)FiresGhettoNetworkMod.Instance).StartCoroutine(Run(seconds, sender, arm == 1)); } } 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 IEnumerator Run(int secs, long requester, bool arm) { float savedTimeout = BulkTransferGatePatches.DisconnectTimeoutSeconds; BulkTransferGatePatches.DisconnectTimeoutSeconds = (arm ? 30f : savedTimeout); BulkTransferGatePatches.ApplyJotunnTimeout(); int startPeers = PeerCount(); s_forceCount = 0L; s_active = true; string text = (arm ? ("[OverloadTest] OVERLOAD ON (ARMED CONTROL) — real 30s timeout restored, queue forced " + $">= {50000} B for {secs}s. Reconnect a client now: it SHOULD drop at ~30s, " + $"which proves the test path is live. Peers at start: {startPeers}.") : ($"[OverloadTest] OVERLOAD ON — forcing every send queue >= {50000} B (gate 20000) " + $"for {secs}s. Reconnect a client now; with the disarm it stays. Peers at start: {startPeers}.")); LoggerOptions.LogMessage(text); Reply(requester, text); float elapsed = 0f; bool dropSeen = false; int minPeers = startPeers; while (elapsed < (float)secs) { yield return (object)new WaitForSeconds(5f); elapsed += 5f; int num = PeerCount(); if (num < minPeers) { minPeers = num; } if (num < startPeers && !dropSeen) { dropSeen = true; string text2 = $"[OverloadTest] Peer count dropped at ~{elapsed:F0}s ({startPeers} -> {num})."; LoggerOptions.LogWarning(text2); Reply(requester, text2); } } s_active = false; BulkTransferGatePatches.DisconnectTimeoutSeconds = savedTimeout; BulkTransferGatePatches.ApplyJotunnTimeout(); int num2 = PeerCount(); string text3 = $"forced GetSendQueueSize high {s_forceCount} time(s)"; string text4 = ((!arm) ? ((!dropSeen) ? ($"[OverloadTest] PASS — no peer dropped through {secs}s of forced saturation " + $"(peers {startPeers}->{num2}, {text3}). The 30s self-disconnect is disarmed. " + $"To prove the test bites, run 'fgn_overload {secs} arm' and reconnect — that should drop at ~30s.") : ($"[OverloadTest] FAIL — a peer dropped during a DISARMED run (start={startPeers} min={minPeers} " + $"end={num2}, {text3}). Check the scan log for 'disarmed at 0 site(s)' on ServerCharacters.Shared.")) : (dropSeen ? ("[OverloadTest] CONTROL PASS — the armed run dropped a peer at ~30s, so the test path is " + $"LIVE ({text3}). Now run 'fgn_overload {secs}' (no arm) and reconnect: it should NOT drop.") : ("[OverloadTest] CONTROL INCONCLUSIVE — armed run dropped nobody (" + text3 + "). " + ((s_forceCount == 0L) ? "Force count is ZERO — GetSendQueueSize was never forced; the peer socket type may differ from ZSteamSocket." : "The queue WAS forced, so reconnect a client DURING the window and retry — the connect-time sync is what runs waitForQueue.")))); if (arm ? dropSeen : (!dropSeen)) { LoggerOptions.LogMessage(text4); } else { LoggerOptions.LogWarning(text4); } Reply(requester, text4); } private static void RPC_Result(long sender, string msg) { if ((Object)(object)Console.instance != (Object)null) { ((Terminal)Console.instance).AddString(msg); } else { LoggerOptions.LogMessage(msg); } } private static void Reply(long target, string msg) { try { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(target, "FGN_OverloadResult", new object[1] { msg }); } } catch { } } private static int PeerCount() { try { return ((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetPeers().Count : 0; } catch { return 0; } } } public static class LoggerOptions { private static ManualLogSource logger; public static void Init(ManualLogSource source) { logger = source; } public static void LogError(object data) { logger.LogError(data); } public static void LogWarning(object data) { logger.LogWarning(data); } public static void LogMessage(object data) { if (FiresGhettoNetworkMod.ConfigLogLevel != null && FiresGhettoNetworkMod.ConfigLogLevel.Value >= LogLevel.Message) { logger.LogMessage(data); } } public static void LogInfo(object data) { if (FiresGhettoNetworkMod.ConfigLogLevel != null && FiresGhettoNetworkMod.ConfigLogLevel.Value >= LogLevel.Info) { logger.LogInfo(data); } } } [HarmonyPatch] public static class MonsterAIPatches { private const float SpawnZoneHalfExtentMeters = 32f; private const float SpawnZoneExtraExtentForEventDetection = 32f; private const float EventDiagnosticIntervalSec = 30f; private static readonly List _playersInZoneScratch = new List(); private static readonly Dictionary _eventDiagnosticLastLogTime = new Dictionary(); private static readonly FieldInfo _f_spawnsystem_heightmap = AccessTools.Field(typeof(SpawnSystem), "m_heightmap"); private static readonly FieldInfo _f_randomEvent = AccessTools.Field(typeof(RandEventSystem), "m_randomEvent"); private static readonly FieldInfo _f_forcedEvent = AccessTools.Field(typeof(RandEventSystem), "m_forcedEvent"); private static readonly FieldInfo _f_activeEvent = AccessTools.Field(typeof(RandEventSystem), "m_activeEvent"); private static readonly MethodInfo _m_setActiveEvent = AccessTools.Method(typeof(RandEventSystem), "SetActiveEvent", new Type[2] { typeof(RandomEvent), typeof(bool) }, (Type[])null); private static readonly MethodInfo _m_isAnyPlayerIn = AccessTools.Method(typeof(RandEventSystem), "IsAnyPlayerInEventArea", new Type[1] { typeof(RandomEvent) }, (Type[])null); private const int MaxAwakeLogsPerSession = 10; private static readonly HashSet _seenAwakeNames = new HashSet(); private static int _awakeLogsRemaining = 10; private static bool IsDedicatedServer() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsDedicated(); } return false; } [HarmonyPatch(typeof(BaseAI), "UpdateAI")] [HarmonyPrefix] public static bool BaseAI_UpdateAI_Prefix(BaseAI __instance) { ServerStatusDiagnostics.s_uai_examined++; if ((Object)(object)__instance.m_nview == (Object)null) { ServerStatusDiagnostics.s_uai_bail_nviewNull++; return false; } if (!__instance.m_nview.IsValid()) { ServerStatusDiagnostics.s_uai_bail_nviewInvalid++; return false; } if (__instance.m_nview.GetZDO() == null) { ServerStatusDiagnostics.s_uai_bail_zdoNull++; return false; } if ((Object)(object)__instance.m_character == (Object)null) { ServerStatusDiagnostics.s_uai_bail_charNull++; return false; } if (__instance.m_nview.IsOwner()) { ServerStatusDiagnostics.s_uai_passThrough_isOwner++; } else { ServerStatusDiagnostics.s_uai_passThrough_notOwner++; } return true; } [HarmonyPatch(typeof(MonoUpdaters), "FixedUpdate")] [HarmonyPostfix] public static void MonoUpdaters_FixedUpdate_RollupDriver_Postfix() { ServerStatusDiagnostics.TryEmit(); } [HarmonyPatch(typeof(MonsterAI), "UpdateAI")] [HarmonyPrefix] public static void MonsterAI_UpdateAI_Counter_Prefix() { if (IsDedicatedServer()) { ServerStatusDiagnostics.s_mai_examined++; } } [HarmonyPatch(typeof(BaseAI), "Awake")] [HarmonyPostfix] public static void BaseAI_Awake_Diagnostic_Postfix(BaseAI __instance) { if (IsDedicatedServer() && _awakeLogsRemaining > 0) { string text = (((Object)(object)((Component)__instance).gameObject != (Object)null) ? ((Object)((Component)__instance).gameObject).name : ""); if (_seenAwakeNames.Add(text)) { _awakeLogsRemaining--; LoggerOptions.LogMessage("[BaseAI.Awake] First-time mob '" + text + "' awoke server-side. " + $"Post-Awake BaseAI.Instances.Count={BaseAI.Instances.Count}, " + $"BaseAI.BaseAIInstances.Count={BaseAI.BaseAIInstances.Count}. " + $"(Remaining log budget: {_awakeLogsRemaining}.)"); } } } [HarmonyPatch(typeof(ZNetScene), "CreateObject")] [HarmonyPostfix] public static void ZNetScene_CreateObject_Diagnostic_Postfix(GameObject __result) { if (IsDedicatedServer()) { ServerStatusDiagnostics.s_co_calls++; if ((Object)(object)__result == (Object)null) { ServerStatusDiagnostics.s_co_nullReturns++; } } } [HarmonyPatch(typeof(SpawnSystem), "UpdateSpawning")] [HarmonyPrefix] private static bool UpdateSpawning_Prefix(SpawnSystem __instance, ZNetView ___m_nview, List ___m_spawnLists) { if (!IsDedicatedServer()) { return true; } if ((Object)(object)___m_nview == (Object)null || !___m_nview.IsValid() || !___m_nview.IsOwner()) { return false; } RandomEvent activeEvt; RandomEvent runningEvt; bool flag = ShouldLogEventDiagnosticForSpawnSystem(__instance, out activeEvt, out runningEvt); CollectPlayersInsideExpandedSpawnZone(__instance); if (_playersInZoneScratch.Count == 0) { if (flag) { LogEventDiagnosticSkippedNoPlayers(__instance, activeEvt, runningEvt); } return false; } PrimeVanillaTempNearPlayersFromScratch(); if (!TryEnsureSpawnSystemHeightmap(__instance)) { return false; } DateTime time = ZNet.instance.GetTime(); foreach (SpawnSystemList ___m_spawnList in ___m_spawnLists) { if (___m_spawnList?.m_spawners != null) { __instance.UpdateSpawnList(___m_spawnList.m_spawners, time, false); } } RunEventSpawners(__instance, time, flag, activeEvt, runningEvt); return false; } private static bool ShouldLogEventDiagnosticForSpawnSystem(SpawnSystem ss, out RandomEvent activeEvt, out RandomEvent runningEvt) { activeEvt = (RandomEvent)(((Object)(object)RandEventSystem.instance != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null); runningEvt = (RandomEvent)(((Object)(object)RandEventSystem.instance != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null); if (activeEvt == null && runningEvt == null) { return false; } int instanceID = ((Object)ss).GetInstanceID(); float time = Time.time; if (_eventDiagnosticLastLogTime.TryGetValue(instanceID, out var value) && time - value < 30f) { return false; } _eventDiagnosticLastLogTime[instanceID] = time; return true; } private static void CollectPlayersInsideExpandedSpawnZone(SpawnSystem ss) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) _playersInZoneScratch.Clear(); foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)(object)allPlayer != (Object)null && IsPointInsideSpawnZone(ss, ((Component)allPlayer).transform.position, 32f)) { _playersInZoneScratch.Add(allPlayer); } } } private static bool IsPointInsideSpawnZone(SpawnSystem ss, Vector3 point, float extraExtent) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) float num = 32f + extraExtent; Vector3 position = ((Component)ss).transform.position; if (point.x >= position.x - num && point.x <= position.x + num && point.z >= position.z - num) { return point.z <= position.z + num; } return false; } private static void PrimeVanillaTempNearPlayersFromScratch() { SpawnSystem.m_tempNearPlayers.Clear(); SpawnSystem.m_tempNearPlayers.AddRange(_playersInZoneScratch); } private static bool TryEnsureSpawnSystemHeightmap(SpawnSystem ss) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ss == (Object)null || _f_spawnsystem_heightmap == null) { return false; } object? value = _f_spawnsystem_heightmap.GetValue(ss); Heightmap val = (Heightmap)((value is Heightmap) ? value : null); if ((Object)(object)val != (Object)null) { return true; } val = Heightmap.FindHeightmap(((Component)ss).transform.position); if ((Object)(object)val == (Object)null) { return false; } _f_spawnsystem_heightmap.SetValue(ss, val); return true; } private static void RunEventSpawners(SpawnSystem ss, DateTime time, bool shouldLog, RandomEvent activeEvt, RandomEvent runningEvt) { if (!((Object)(object)RandEventSystem.instance == (Object)null)) { List currentSpawners = RandEventSystem.instance.GetCurrentSpawners(); if (shouldLog) { LogEventDiagnosticRanEventPath(ss, activeEvt, runningEvt, currentSpawners); } if (currentSpawners != null) { ss.UpdateSpawnList(currentSpawners, time, true); } } } private static void LogEventDiagnosticSkippedNoPlayers(SpawnSystem ss, RandomEvent activeEvt, RandomEvent runningEvt) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)ss).transform.position; LoggerOptions.LogInfo($"[EventDiag] SpawnSystem@({position.x:F0},{position.z:F0}) SKIPPED: no players in zone. " + "ActiveEvent='" + ((activeEvt != null) ? activeEvt.m_name : "null") + "' RunningEvent='" + ((runningEvt != null) ? runningEvt.m_name : "null") + "' " + $"TotalPlayers={Player.GetAllPlayers().Count} Peers={ZNet.instance.GetConnectedPeers().Count}"); } private static void LogEventDiagnosticRanEventPath(SpawnSystem ss, RandomEvent activeEvt, RandomEvent runningEvt, List currentSpawners) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)ss).transform.position; int num = currentSpawners?.Count ?? (-1); LoggerOptions.LogInfo($"[EventDiag] SpawnSystem@({position.x:F0},{position.z:F0}) RAN event path. " + "ActiveEvent='" + ((activeEvt != null) ? activeEvt.m_name : "null") + "' RunningEvent='" + ((runningEvt != null) ? runningEvt.m_name : "null") + "' " + $"GetCurrentSpawners.Count={num} Players={_playersInZoneScratch.Count}"); } [HarmonyPatch(typeof(RandEventSystem), "FixedUpdate")] [HarmonyPrefix] private static void RandEventSystem_FixedUpdate_Prefix(RandEventSystem __instance) { if (IsDedicatedServer() && !(_f_forcedEvent.GetValue(__instance) is RandomEvent)) { object? value = _f_randomEvent.GetValue(__instance); RandomEvent val = (RandomEvent)((value is RandomEvent) ? value : null); if (val != null && (bool)_m_isAnyPlayerIn.Invoke(__instance, new object[1] { val })) { _f_activeEvent.SetValue(__instance, val); } } } [HarmonyPatch(typeof(RandEventSystem), "SetActiveEvent")] [HarmonyPrefix] private static bool RandEventSystem_SetActiveEvent_Prefix(RandEventSystem __instance, RandomEvent ev) { if (!IsDedicatedServer()) { return true; } if (ev != null) { return true; } if (_f_forcedEvent.GetValue(__instance) is RandomEvent) { return true; } return !(_f_randomEvent.GetValue(__instance) is RandomEvent); } [HarmonyPatch(typeof(RandEventSystem), "FixedUpdate")] [HarmonyPostfix] private static void RandEventSystem_FixedUpdate_Postfix(RandEventSystem __instance) { if (IsDedicatedServer() && !(_f_forcedEvent.GetValue(__instance) is RandomEvent)) { object? value = _f_randomEvent.GetValue(__instance); RandomEvent val = (RandomEvent)((value is RandomEvent) ? value : null); if (val != null) { bool flag = (bool)_m_isAnyPlayerIn.Invoke(__instance, new object[1] { val }); _m_setActiveEvent.Invoke(__instance, new object[2] { flag ? val : null, false }); } else { _m_setActiveEvent.Invoke(__instance, new object[2] { null, false }); } } } } [HarmonyPatch] public static class NetworkingRatesGroup { private static bool _recvBufferProbed; private static bool _recvBufferSupported; private static bool _recvMaxMessageProbed; private static bool _recvMaxMessageSupported; private static bool _sendBufferProbed; private static bool _sendBufferSupported; public static void Init(ConfigFile config) { FiresGhettoNetworkMod.ConfigUpdateRate.SettingChanged += delegate { ApplyUpdateRate(); }; FiresGhettoNetworkMod.ConfigSendRateMin.SettingChanged += delegate { ApplySendRates(); }; FiresGhettoNetworkMod.ConfigSendRateMax.SettingChanged += delegate { ApplySendRates(); }; FiresGhettoNetworkMod.ConfigQueueSize.SettingChanged += delegate { LoggerOptions.LogInfo("Queue size changed - restart recommended."); }; ApplyUpdateRate(); ApplySendRates(); } private static void ApplyUpdateRate() { LoggerOptions.LogMessage($"Update rate set to {FiresGhettoNetworkMod.ConfigUpdateRate.Value}"); } public static void ApplySendRates() { if (!((Object)(object)ZNet.instance == (Object)null)) { int num = EffectiveConfig.SteamSendRateMin(); int num2 = EffectiveConfig.SteamSendRateMax(); SetSteamConfig("k_ESteamNetworkingConfig_SendRateMin", num); SetSteamConfig("k_ESteamNetworkingConfig_SendRateMax", num2); LoggerOptions.LogMessage($"Steam send rates applied: Min {num / 1024} KB/s, Max {num2 / 1024} KB/s"); } } public static void ApplySendBufferSize() { if ((Object)(object)ZNet.instance == (Object)null) { return; } if (!_sendBufferProbed) { _sendBufferProbed = true; _sendBufferSupported = HasSteamConfigMember("k_ESteamNetworkingConfig_SendBufferSize"); if (!_sendBufferSupported) { LoggerOptions.LogInfo("Steam ESteamNetworkingConfigValue does not expose SendBufferSize in this game build; AutoTune buffer scaling has no effect."); } } if (_sendBufferSupported) { int num = EffectiveConfig.SteamSendBufferBytes(); SetSteamConfig("k_ESteamNetworkingConfig_SendBufferSize", num); LoggerOptions.LogMessage($"Steam send buffer applied: {num / 1024} KB"); } } public static void ApplyRecvBufferSize() { if ((Object)(object)ZNet.instance == (Object)null) { return; } if (!_recvBufferProbed) { _recvBufferProbed = true; _recvBufferSupported = HasSteamConfigMember("k_ESteamNetworkingConfig_RecvBufferSize"); if (!_recvBufferSupported) { LoggerOptions.LogInfo("Steam ESteamNetworkingConfigValue does not expose RecvBufferSize in this game build; 'ZPackage Receive Buffer Bytes' setting has no effect (Valheim ships an older Steamworks SDK)."); } } if (_recvBufferSupported) { int num = EffectiveConfig.SteamRecvBufferBytes(); SetSteamConfig("k_ESteamNetworkingConfig_RecvBufferSize", num); LoggerOptions.LogMessage($"Steam recv buffer applied: {num / 1024} KB"); } } public static void ApplyRecvMaxMessageSize() { if ((Object)(object)ZNet.instance == (Object)null) { return; } if (!_recvMaxMessageProbed) { _recvMaxMessageProbed = true; _recvMaxMessageSupported = HasSteamConfigMember("k_ESteamNetworkingConfig_RecvMaxMessageSize"); if (!_recvMaxMessageSupported) { LoggerOptions.LogInfo("Steam ESteamNetworkingConfigValue does not expose RecvMaxMessageSize in this game build; per-message cap stays at Steam's 512 KB default (install FiresSteamworksPatcher on the server to enable)."); } } if (_recvMaxMessageSupported) { int num = EffectiveConfig.SteamRecvMaxMessageBytes(); SetSteamConfig("k_ESteamNetworkingConfig_RecvMaxMessageSize", num); LoggerOptions.LogMessage($"Steam recv-max-message applied: {num / 1024} KB"); } } public static bool IsSendBufferRaiseApplied() { return HasSteamConfigMember("k_ESteamNetworkingConfig_SendBufferSize"); } private static bool HasSteamConfigMember(string memberName) { try { Type type = AppDomain.CurrentDomain.GetAssemblies().SelectMany(delegate(Assembly a) { try { return a.GetTypes(); } catch { return Array.Empty(); } }).FirstOrDefault((Type t) => t.FullName == "Steamworks.ESteamNetworkingConfigValue"); if (type == null) { return false; } string[] names = Enum.GetNames(type); for (int num = 0; num < names.Length; num++) { if (names[num] == memberName) { return true; } } return false; } catch { return false; } } private static int GetSendRateValue(object option) { return option.ToString() switch { "_1024KB" => 1048576, "_768KB" => 786432, "_512KB" => 524288, "_256KB" => 262144, _ => 153600, }; } [HarmonyPatch(typeof(ZDOMan), "SendZDOToPeers2")] [HarmonyPrefix] private static void AdjustUpdateInterval(ref float dt) { switch (EffectiveConfig.UpdateRate()) { case UpdateRateOptions._150: dt *= 1.5f; break; case UpdateRateOptions._75: dt *= 0.75f; break; case UpdateRateOptions._50: dt *= 0.5f; break; case UpdateRateOptions._100: break; } } [HarmonyPatch(typeof(ZNet), "Start")] [HarmonyPostfix] private static void EnsureRatesOnStart() { ApplySendRates(); ApplySendBufferSize(); ApplyRecvBufferSize(); ApplyRecvMaxMessageSize(); } private static void SetSteamConfig(string enumMemberName, int value) { IntPtr intPtr = IntPtr.Zero; try { IEnumerable source = AppDomain.CurrentDomain.GetAssemblies().SelectMany(delegate(Assembly a) { try { return a.GetTypes(); } catch { return Array.Empty(); } }); Type type = source.FirstOrDefault((Type t) => t.FullName == "Steamworks.ESteamNetworkingConfigValue"); Type type2 = source.FirstOrDefault((Type t) => t.FullName == "Steamworks.ESteamNetworkingConfigScope"); Type type3 = source.FirstOrDefault((Type t) => t.FullName == "Steamworks.ESteamNetworkingConfigDataType"); if (type == null || type2 == null || type3 == null) { LoggerOptions.LogWarning("Steamworks.NET types not found - send rate config skipped."); return; } object obj = Enum.Parse(type, enumMemberName); object obj2 = Enum.Parse(type2, "k_ESteamNetworkingConfig_Global"); object obj3 = Enum.Parse(type3, "k_ESteamNetworkingConfig_Int32"); intPtr = Marshal.AllocHGlobal(4); Marshal.WriteInt32(intPtr, value); Type type4 = ((Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsDedicated()) ? source.FirstOrDefault((Type t) => t.FullName == "Steamworks.SteamGameServerNetworkingUtils") : source.FirstOrDefault((Type t) => t.FullName == "Steamworks.SteamNetworkingUtils")); if (type4 == null) { LoggerOptions.LogWarning("Steamworks utils type not found - send rate config skipped."); return; } MethodInfo method = type4.GetMethod("SetConfigValue", BindingFlags.Static | BindingFlags.Public); if (method == null) { LoggerOptions.LogWarning("SetConfigValue method not found - send rate config skipped."); return; } method.Invoke(null, new object[5] { obj, obj2, IntPtr.Zero, obj3, intPtr }); } catch (Exception ex) { LoggerOptions.LogWarning("Failed to set Steam config " + enumMemberName + ": " + ex.Message); } finally { if (intPtr != IntPtr.Zero) { Marshal.FreeHGlobal(intPtr); } } } [HarmonyPatch(typeof(ZSteamSocket), "RegisterGlobalCallbacks")] [HarmonyPostfix] private static void ApplySendRatesOnConnect() { ApplySendRates(); ApplySendBufferSize(); ApplyRecvBufferSize(); ApplyRecvMaxMessageSize(); } [HarmonyPatch(typeof(ZDOMan), "SendZDOs")] [HarmonyTranspiler] private static IEnumerable SendZDOs_QueueLimitTranspiler(IEnumerable instructions) { List list = new List(instructions); int num = 0; int configuredQueueLimit = GetConfiguredQueueLimit(); for (int i = 0; i < list.Count; i++) { if ((!(list[i].opcode == OpCodes.Ldc_I4) && !(list[i].opcode == OpCodes.Ldc_I4_S)) || !(list[i].operand is int num2) || num2 < 10240) { continue; } bool flag = false; for (int j = Math.Max(0, i - 15); j < Math.Min(list.Count, i + 15); j++) { if (list[j].opcode == OpCodes.Callvirt && list[j].operand is MethodInfo { Name: "GetSendQueueSize" }) { flag = true; break; } } if (flag) { LoggerOptions.LogInfo($"Overriding ZDOMan.SendZDOs queue limit #{num + 1}: original {num2} → {configuredQueueLimit} bytes"); list[i].opcode = OpCodes.Ldc_I4; list[i].operand = configuredQueueLimit; num++; } } if (num == 0) { LoggerOptions.LogWarning("No queue limit constants found in ZDOMan.SendZDOs — queue size config not applied (game update may have changed IL)."); } else { LoggerOptions.LogInfo($"Successfully patched {num} queue limit constant(s)."); } return list.AsEnumerable(); } private static int GetConfiguredQueueLimit() { return EffectiveConfig.QueueSize() switch { QueueSizeOptions._80KB => 81920, QueueSizeOptions._64KB => 65536, QueueSizeOptions._48KB => 49152, QueueSizeOptions._32KB => 32768, _ => 10240, }; } } public static class NetworkStats { private struct Snapshot { public int PeerCount; public int PlayerLimit; public long TotalQueueBytes; public int CapBytes; public int CongestedPeers; public int AvgPingMs; public int MaxPingMs; public float SendBytesPerSec; public float RecvBytesPerSec; public int ZdosSentSec; public int ZdosRecvSec; } private const float SnapshotTtlSeconds = 0.5f; private static Snapshot s_snapshot; private static float s_snapshotTime = -999f; private static FieldInfo s_steamConField; private static bool s_loggedPeerStatusSource; public static bool IsServerActive() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } public static int PeerCount() { return Current().PeerCount; } public static int PlayerLimit() { return Current().PlayerLimit; } public static long TotalSendQueueBytes() { return Current().TotalQueueBytes; } public static int SendQueueCapBytes() { return Current().CapBytes; } public static int CongestedPeerCount() { return Current().CongestedPeers; } public static int AveragePingMs() { return Current().AvgPingMs; } public static int MaxPingMs() { return Current().MaxPingMs; } public static float TotalSendBytesPerSec() { return Current().SendBytesPerSec; } public static float TotalRecvBytesPerSec() { return Current().RecvBytesPerSec; } public static int ZdosSentPerSec() { return Current().ZdosSentSec; } public static int ZdosRecvPerSec() { return Current().ZdosRecvSec; } private static Snapshot Current() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (s_snapshotTime > 0f && realtimeSinceStartup - s_snapshotTime < 0.5f) { return s_snapshot; } s_snapshot = Build(); s_snapshotTime = realtimeSinceStartup; return s_snapshot; } private static Snapshot Build() { Snapshot result = new Snapshot { PlayerLimit = 10 }; if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return result; } List peers; try { peers = ZNet.instance.GetPeers(); } catch { return result; } if (peers == null) { return result; } int capBytes = SendCongestion.EffectiveCapBytes(); float num = SendCongestion.CongestionThresholdBytes(); result.CapBytes = capBytes; long num2 = 0L; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; float num7 = 0f; float num8 = 0f; int num9 = 0; float num11 = default(float); float num12 = default(float); foreach (ZNetPeer item in peers) { if (item == null || item.m_socket == null) { continue; } num9++; ISocket socket = item.m_socket; int num10; try { num10 = socket.GetSendQueueSize(); } catch { num10 = -1; } if (num10 > 0) { num2 += num10; if ((float)num10 >= num) { num3++; } } try { int ping; float outBytesSec; float inBytesSec; bool flag = TryGameServerPeerStatus(socket, out ping, out outBytesSec, out inBytesSec); if (!flag) { socket.GetConnectionQuality(ref num11, ref num12, ref ping, ref outBytesSec, ref inBytesSec); } if (!s_loggedPeerStatusSource) { s_loggedPeerStatusSource = true; LogPeerStatusSource(socket, flag); } if (ping > 0) { num4 += ping; num5++; if (ping > num6) { num6 = ping; } } if (outBytesSec > 0f) { num7 += outBytesSec; } if (inBytesSec > 0f) { num8 += inBytesSec; } } catch { } } result.PeerCount = num9; if (num9 == 0) { s_loggedPeerStatusSource = false; } result.TotalQueueBytes = num2; result.CongestedPeers = num3; result.AvgPingMs = ((num5 > 0) ? Mathf.RoundToInt((float)num4 / (float)num5) : 0); result.MaxPingMs = num6; result.SendBytesPerSec = num7; result.RecvBytesPerSec = num8; if (ZDOMan.instance != null) { try { result.ZdosSentSec = ZDOMan.instance.GetSentZDOs(); } catch { } try { result.ZdosRecvSec = ZDOMan.instance.GetRecvZDOs(); } catch { } } return result; } private static bool TryGameServerPeerStatus(ISocket socket, out int ping, out float outBytesSec, out float inBytesSec) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Invalid comparison between Unknown and I4 //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) ping = 0; outBytesSec = 0f; inBytesSec = 0f; ZSteamSocket val = (ZSteamSocket)(object)((socket is ZSteamSocket) ? socket : null); if (val == null) { return false; } if (s_steamConField == null) { s_steamConField = AccessTools.Field(typeof(ZSteamSocket), "m_con"); } if (s_steamConField == null) { return false; } HSteamNetConnection val2 = (HSteamNetConnection)s_steamConField.GetValue(val); if (val2 == HSteamNetConnection.Invalid) { return false; } SteamNetConnectionRealTimeStatus_t val3 = default(SteamNetConnectionRealTimeStatus_t); SteamNetConnectionRealTimeLaneStatus_t val4 = default(SteamNetConnectionRealTimeLaneStatus_t); if ((int)SteamGameServerNetworkingSockets.GetConnectionRealTimeStatus(val2, ref val3, 0, ref val4) != 1) { return false; } ping = val3.m_nPing; outBytesSec = val3.m_flOutBytesPerSec; inBytesSec = val3.m_flInBytesPerSec; return true; } private static void LogPeerStatusSource(ISocket socket, bool viaGameServer) { string text = ((socket != null) ? ((object)socket).GetType().Name : "null"); LoggerOptions.LogMessage("[NetworkStats] per-peer ping/throughput source = " + (viaGameServer ? "game-server interface (dedicated path)" : "vanilla GetConnectionQuality") + " for " + text + ". Vanilla reads 0 on a dedicated server; the game-server path restores ping + Tx/Rx."); } } [HarmonyPatch] public static class PlayerPositionSyncPatches { private sealed class PlayerSyncData { public Vector3 networkPos; public Vector3 prevNetworkPos; public Quaternion networkRot; public Quaternion prevNetworkRot; public Vector3 velocity; public Vector3 smoothedVelocity; public float lastNetworkUpdateTime; public float networkUpdateInterval; public bool hasData; public Vector3 renderPos; public Quaternion renderRot; public float lastFrameTime; public Vector3 acceleration; } public static ConfigEntry ConfigEnablePlayerPositionBoost; public static ConfigEntry ConfigPlayerPositionUpdateMultiplier; public static ConfigEntry ConfigEnableClientInterpolation; public static ConfigEntry ConfigEnablePlayerPrediction; public static ConfigEntry ConfigSmoothingMinInterval; public static ConfigEntry ConfigSmoothingMaxInterval; private static readonly int PlayerPrefabHash = StringExtensionMethods.GetStableHashCode("Player"); private static readonly Dictionary _syncData = new Dictionary(); public static void Init(ConfigFile config) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown ConfigEnablePlayerPositionBoost = config.Bind("03 - Player Sync", "Enable High-Frequency Position Updates", true, "Boosts server send priority for player ZDOs so they sync before terrain/object ZDOs.\nReduces the floaty delayed movement you see on other players.\nSERVER-ONLY — no effect on client."); ConfigPlayerPositionUpdateMultiplier = config.Bind("03 - Player Sync", "Position Update Multiplier", 2.5f, new ConfigDescription("How aggressively player ZDOs are prioritized over other ZDOs (1.0 = vanilla, 2.5 = recommended).\nHigher values push player positions to the front of the send queue more strongly.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 5f), Array.Empty())); ConfigEnableClientInterpolation = config.Bind("03 - Player Sync", "Enable Client-Side Interpolation", false, "Smooths other players' movement on your client by interpolating between received network positions.\nEliminates the snapping/teleporting caused by discrete 50ms network updates.\nDisabled by default — the rest of the mod's networking improvements (higher update rate, server\nZDO priority boost, larger Steam buffers) usually deliver positions smoothly enough on their own,\nand interpolation adds a small render-lag that some players prefer to avoid. Turn ON if you still\nsee other players snap/teleport even on a healthy connection.\nCLIENT-ONLY — no server impact."); ConfigEnablePlayerPrediction = config.Bind("03 - Player Sync", "Enable Client-Side Prediction", false, "Extrapolates other players' positions forward between network updates using their last known velocity.\nCan help on high latency (>100ms) but may cause overshooting at low ping.\nDisabled by default — only enable if interpolation alone feels laggy.\nCLIENT-ONLY — no server impact."); ConfigSmoothingMinInterval = config.Bind("03 - Player Sync", "Smoothing Min Interval (s)", 0f, new ConfigDescription("When a remote player's packets arrive faster than this interval (seconds),\nsmoothing is disabled entirely and vanilla movement renders directly.\nDefault 0 = always smooth (recommended). The earlier default of 0.05\n(vanilla 20Hz) collapsed the smoothing-strength formula to ~0 under\nhealthy traffic, which made BOTH this slider and Smoothing Max feel\nlike no-ops and produced visible snaps every time inter-packet jitter\ncrossed the threshold. Raise above 0 only if you specifically want\nfast packets to bypass smoothing (LAN / very low-latency servers).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 0.2f), Array.Empty())); ConfigSmoothingMaxInterval = config.Bind("03 - Player Sync", "Smoothing Max Interval (s)", 0.2f, new ConfigDescription("When a remote player's packets arrive slower than this interval (seconds),\nsmoothing runs at full strength. Between Min and Max the strength fades\nin linearly so the handoff is invisible. Raise it for a more aggressive\nfade-in (less smoothing on borderline-laggy connections); lower it for a\nsnappier ramp to full smoothing.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 0.5f), Array.Empty())); } private static bool IsPlayerZDO(ZDO zdo) { if (zdo != null) { return zdo.m_prefab == PlayerPrefabHash; } return false; } [HarmonyPatch(typeof(ZDO), "Deserialize")] [HarmonyPostfix] public static void ZDO_Deserialize_Postfix(ZDO __instance) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) if (ConfigEnableClientInterpolation == null || ConfigEnablePlayerPrediction == null || (!ConfigEnableClientInterpolation.Value && !EffectiveConfig.EnablePlayerPrediction()) || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || __instance == null || !IsPlayerZDO(__instance)) { return; } long owner = __instance.GetOwner(); if (owner == ZNet.GetUID()) { return; } Vector3 position = __instance.GetPosition(); Quaternion rotation = __instance.GetRotation(); rotation = Quaternion.Normalize(rotation); float time = Time.time; if (!_syncData.TryGetValue(owner, out var value)) { _syncData[owner] = new PlayerSyncData { networkPos = position, prevNetworkPos = position, networkRot = rotation, prevNetworkRot = rotation, renderPos = position, renderRot = rotation, velocity = Vector3.zero, smoothedVelocity = Vector3.zero, acceleration = Vector3.zero, networkUpdateInterval = 0.05f, lastNetworkUpdateTime = time, lastFrameTime = time, hasData = true }; return; } float num = time - value.lastNetworkUpdateTime; if (position == value.networkPos) { value.velocity = Vector3.Lerp(value.velocity, Vector3.zero, 0.9f); value.acceleration = Vector3.Lerp(value.acceleration, Vector3.zero, 0.9f); if (((Vector3)(ref value.velocity)).magnitude < 0.05f) { value.velocity = Vector3.zero; value.acceleration = Vector3.zero; } } else if (num >= 0.005f && num <= 0.5f) { value.networkUpdateInterval = Mathf.Lerp(value.networkUpdateInterval, num, 0.15f); Vector3 val = (position - value.networkPos) / num; if (((Vector3)(ref val)).magnitude <= 30f) { Vector3 val2 = (val - value.velocity) / num; value.acceleration = Vector3.Lerp(value.acceleration, val2, 0.25f); Vector3 val3 = Vector3.Lerp(value.velocity, val, 0.5f); value.velocity = Vector3.Lerp(value.velocity, val3, 0.7f); value.smoothedVelocity = Vector3.Lerp(value.smoothedVelocity, value.velocity, 0.6f); } else { value.velocity = Vector3.zero; value.smoothedVelocity = Vector3.zero; value.acceleration = Vector3.zero; } } value.prevNetworkPos = value.networkPos; value.prevNetworkRot = value.networkRot; value.networkPos = position; value.networkRot = rotation; value.lastNetworkUpdateTime = time; value.hasData = true; } [HarmonyPatch(typeof(Player), "LateUpdate")] [HarmonyPostfix] public static void Player_LateUpdate_Postfix(Player __instance) { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_02f9: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_0307: Unknown result type (might be due to invalid IL or missing references) //IL_0314: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_0381: Unknown result type (might be due to invalid IL or missing references) //IL_0387: Unknown result type (might be due to invalid IL or missing references) //IL_038e: Unknown result type (might be due to invalid IL or missing references) //IL_039f: Unknown result type (might be due to invalid IL or missing references) //IL_03a4: Unknown result type (might be due to invalid IL or missing references) //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03b1: Unknown result type (might be due to invalid IL or missing references) //IL_034a: Unknown result type (might be due to invalid IL or missing references) //IL_0350: Unknown result type (might be due to invalid IL or missing references) //IL_035f: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Unknown result type (might be due to invalid IL or missing references) //IL_0370: Unknown result type (might be due to invalid IL or missing references) //IL_0375: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)__instance == (Object)(object)Player.m_localPlayer || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || ConfigEnableClientInterpolation == null || !ConfigEnableClientInterpolation.Value) { return; } ZNetView nview = ((Character)__instance).m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return; } ZDO zDO = nview.GetZDO(); if (zDO == null) { return; } long owner = zDO.GetOwner(); if (!_syncData.TryGetValue(owner, out var value) || !value.hasData) { return; } float networkUpdateInterval = value.networkUpdateInterval; float num = EffectiveConfig.SmoothingMinInterval(); float num2 = Mathf.Max(num + 0.001f, EffectiveConfig.SmoothingMaxInterval()); if (networkUpdateInterval < num) { return; } float num3 = Mathf.Clamp01((networkUpdateInterval - num) / (num2 - num)); bool num4 = ((Vector3)(ref value.smoothedVelocity)).magnitude < 0.03f; bool flag = Vector3.Distance(value.renderPos, value.networkPos) < 0.005f; bool flag2 = Quaternion.Angle(value.renderRot, value.networkRot) < 0.05f; if (num4 && flag && flag2) { return; } float time = Time.time; float num5 = time - value.lastFrameTime; value.lastFrameTime = time; float num6 = time - value.lastNetworkUpdateTime; if (num6 > 0.3f && ((Vector3)(ref value.smoothedVelocity)).magnitude > 0.05f) { float num7 = Mathf.Clamp01(num5 * 8f); value.velocity = Vector3.Lerp(value.velocity, Vector3.zero, num7); value.smoothedVelocity = Vector3.Lerp(value.smoothedVelocity, Vector3.zero, num7); value.acceleration = Vector3.Lerp(value.acceleration, Vector3.zero, num7); } float num8 = 0f; num8 = ((!EffectiveConfig.EnablePlayerPrediction() || !(((Vector3)(ref value.smoothedVelocity)).magnitude > 0.2f)) ? (value.networkUpdateInterval * 0.4f) : Mathf.Min(num6 * 0.8f, value.networkUpdateInterval * 1.2f)); Vector3 val2; if (((Vector3)(ref value.smoothedVelocity)).magnitude > 1f) { Vector3 val = value.smoothedVelocity + value.acceleration * num8 * 0.5f; val2 = value.networkPos + val * num8; } else { val2 = value.networkPos; } float num9 = Vector3.Distance(value.renderPos, val2); if (num9 > 5f) { value.renderPos = val2; value.renderRot = Quaternion.Normalize(value.networkRot); } else { float num10 = Mathf.Clamp01(((Vector3)(ref value.smoothedVelocity)).magnitude / 8f); float num11 = Mathf.Clamp01(num9 / 1.5f); float num12 = Mathf.Lerp(15f, 30f, Mathf.Max(num10, num11)); if (num9 < 0.2f) { num12 *= 0.6f; } float num13 = Mathf.Clamp01(num5 * num12); num13 = num13 * num13 * (3f - 2f * num13); value.renderPos = Vector3.Lerp(value.renderPos, val2, num13); float num14 = 25f; float num15 = Quaternion.Angle(value.renderRot, value.networkRot); if (num15 > 30f) { num14 = 40f; } else if (num15 < 5f) { num14 = 15f; } value.renderRot = Quaternion.Slerp(value.renderRot, value.networkRot, Mathf.Clamp01(num5 * num14)); value.renderRot = Quaternion.Normalize(value.renderRot); } ((Component)__instance).transform.position = Vector3.Lerp(value.networkPos, value.renderPos, num3); ((Component)__instance).transform.rotation = Quaternion.Slerp(Quaternion.Normalize(value.networkRot), value.renderRot, num3); } } public static class RoutedRpcManager { public static readonly Dictionary HashCodeToMethodNameCache = new Dictionary(); private static readonly Dictionary> _rpcMethodHandlers = new Dictionary>(); private static readonly RoutedRPCData _routedRpcData = new RoutedRPCData(); private static float _aoiRadiusOverride = -1f; public static void SetAoIRadiusOverride(float radius) { _aoiRadiusOverride = radius; } public static void AddHandler(string methodName, RpcMethodHandler handler) { int stableHashCode = StringExtensionMethods.GetStableHashCode(methodName); HashCodeToMethodNameCache[stableHashCode] = methodName; LoggerOptions.LogInfo($"[RpcRouter] Registering handler for {methodName} ({stableHashCode}): {handler.GetType().Name}"); if (!_rpcMethodHandlers.TryGetValue(stableHashCode, out var value)) { value = new List(); _rpcMethodHandlers[stableHashCode] = value; } value.Add(handler); } public static void ProcessRoutedRPC(ZRoutedRpc routedRpc, ZRpc rpc, ZPackage package) { _routedRpcData.Deserialize(package); long targetPeerID = _routedRpcData.m_targetPeerID; long id = routedRpc.m_id; if (targetPeerID == id) { routedRpc.HandleRoutedRPC(_routedRpcData); ClearTransientState(); return; } if (targetPeerID == 0L) { routedRpc.HandleRoutedRPC(_routedRpcData); } if (!ProcessHandlers(_routedRpcData)) { ClearTransientState(); return; } ForwardRpc(routedRpc, targetPeerID); ClearTransientState(); } private static void ForwardRpc(ZRoutedRpc routedRpc, long target) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) bool num = target == 0; bool flag = FiresGhettoNetworkMod.ConfigEnableRpcAoI != null && FiresGhettoNetworkMod.ConfigEnableRpcAoI.Value; if (!num || !flag) { routedRpc.RouteRPC(_routedRpcData); return; } float radius = ((_aoiRadiusOverride > 0f) ? _aoiRadiusOverride : FiresGhettoNetworkMod.ConfigRpcAoIRadius.Value); if (!((ZDOID)(ref _routedRpcData.m_targetZDO)).IsNone()) { RouteRPCWithAoIFromZDO(routedRpc, _routedRpcData, radius); } else if (SpawnedZonePositionHint.HasHint) { RouteRPCWithAoIFromPosition(routedRpc, _routedRpcData, SpawnedZonePositionHint.Position, radius); } else { routedRpc.RouteRPC(_routedRpcData); } } private static void ClearTransientState() { _aoiRadiusOverride = -1f; SpawnedZonePositionHint.Clear(); } private static void RouteRPCWithAoIFromZDO(ZRoutedRpc routedRpc, RoutedRPCData rpcData, float radius) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (!ZDOMan.instance.m_objectsByID.TryGetValue(rpcData.m_targetZDO, out var value)) { routedRpc.RouteRPC(rpcData); } else { RouteRPCWithAoIFromPosition(routedRpc, rpcData, value.m_position, radius); } } private static void RouteRPCWithAoIFromPosition(ZRoutedRpc routedRpc, RoutedRPCData rpcData, Vector3 worldPos, float radius) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) float num = radius * radius; ZPackage val = new ZPackage(); rpcData.Serialize(val); foreach (ZNetPeer peer in routedRpc.m_peers) { if (rpcData.m_senderPeerID != peer.m_uid && peer.IsReady()) { Vector3 refPos = peer.GetRefPos(); float num2 = refPos.x - worldPos.x; float num3 = refPos.z - worldPos.z; if (num2 * num2 + num3 * num3 <= num) { peer.m_rpc.Invoke("RoutedRPC", new object[1] { val }); } } } } public static bool ProcessHandlers(RoutedRPCData routedRpcData) { if (!_rpcMethodHandlers.TryGetValue(routedRpcData.m_methodHash, out var value)) { return true; } bool flag = true; foreach (RpcMethodHandler item in value) { flag &= item.Process(routedRpcData); } return flag; } public static string MethodHashToString(int methodHash) { if (HashCodeToMethodNameCache.TryGetValue(methodHash, out var value)) { return value; } return $"RPC_{methodHash}"; } public static void Reset() { _rpcMethodHandlers.Clear(); HashCodeToMethodNameCache.Clear(); _aoiRadiusOverride = -1f; } } public sealed class AddNoiseHandler : RpcMethodHandler { private static readonly AddNoiseHandler _instance = new AddNoiseHandler(); private AddNoiseHandler() { } public static void Register() { RoutedRpcManager.AddHandler("AddNoise", _instance); } public override bool Process(RoutedRPCData routedRpcData) { ZPackage parameters = routedRpcData.m_parameters; if (parameters == null || parameters.Size() < 4) { return true; } int pos = parameters.GetPos(); parameters.SetPos(0); float num = parameters.ReadSingle(); parameters.SetPos(pos); RoutedRpcManager.SetAoIRadiusOverride(num * 2f); return true; } } public sealed class DamageTextHandler : RpcMethodHandler { private static readonly DamageTextHandler _instance = new DamageTextHandler(); private DamageTextHandler() { } public static void Register() { RoutedRpcManager.AddHandler("DamageText", _instance); } public override bool Process(RoutedRPCData routedRpcData) { return false; } } public sealed class HealthChangedHandler : RpcMethodHandler { private static readonly HealthChangedHandler _instance = new HealthChangedHandler(); private HealthChangedHandler() { } public static void Register() { RoutedRpcManager.AddHandler("RPC_HealthChanged", _instance); } public override bool Process(RoutedRPCData routedRpcData) { return false; } } public sealed class SetTargetHandler : RpcMethodHandler { private static readonly SetTargetHandler _instance = new SetTargetHandler(); public static readonly int PlayerHashCode = StringExtensionMethods.GetStableHashCode("Player"); private SetTargetHandler() { } public static void Register() { RoutedRpcManager.AddHandler("RPC_SetTarget", _instance); } public override bool Process(RoutedRPCData routedRpcData) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) ZPackage parameters = routedRpcData.m_parameters; parameters.SetPos(0); ZDOID val = parameters.ReadZDOID(); parameters.SetPos(0); if (val == ZDOID.None) { return true; } if (!ZDOMan.instance.m_objectsByID.TryGetValue(val, out var value)) { return true; } if (Utils.DistanceXZ(Vector3.zero, value.m_position) > 50000f) { LoggerOptions.LogWarning($"[RpcRouter] SetTarget suspicious — target ZDO at extreme distance ({value.m_position})"); return true; } if (value.m_prefab == PlayerHashCode || value.GetBool(ZDOVars.s_tamed, false)) { parameters.Clear(); parameters.Write(ZDOID.None); parameters.m_writer.Flush(); parameters.m_stream.Flush(); parameters.SetPos(0); routedRpcData.m_senderPeerID = ZRoutedRpc.instance.m_id; LoggerOptions.LogInfo("[RpcRouter] SetTarget cleared — target was player or tamed creature"); } return true; } } public sealed class SpawnedZoneHandler : RpcMethodHandler { private static readonly SpawnedZoneHandler _instance = new SpawnedZoneHandler(); private SpawnedZoneHandler() { } public static void Register() { RoutedRpcManager.AddHandler("SpawnedZone", _instance); } public override bool Process(RoutedRPCData routedRpcData) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) ZPackage parameters = routedRpcData.m_parameters; if (parameters == null || parameters.Size() < 8) { return true; } int pos = parameters.GetPos(); parameters.SetPos(0); int num = parameters.ReadInt(); int num2 = parameters.ReadInt(); parameters.SetPos(pos); SpawnedZonePositionHint.Position = ZoneSystem.GetZonePos(new Vector2i(num, num2)); SpawnedZonePositionHint.HasHint = true; RoutedRpcManager.SetAoIRadiusOverride(FiresGhettoNetworkMod.ConfigRpcAoIRadius.Value); return true; } } public static class SpawnedZonePositionHint { public static bool HasHint; public static Vector3 Position; public static void Clear() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) HasHint = false; Position = Vector3.zero; } } public sealed class TalkerSayHandler : RpcMethodHandler { private static readonly TalkerSayHandler _instance = new TalkerSayHandler(); private TalkerSayHandler() { } public static void Register() { RoutedRpcManager.AddHandler("Say", _instance); } public override bool Process(RoutedRPCData routedRpcData) { return true; } } public sealed class TriggerAnimationHandler : RpcMethodHandler { private static readonly TriggerAnimationHandler _instance = new TriggerAnimationHandler(); private TriggerAnimationHandler() { } public static void Register() { RoutedRpcManager.AddHandler("TriggerAnimation", _instance); } public override bool Process(RoutedRPCData routedRpcData) { return true; } } public sealed class TriggerOnDeathHandler : RpcMethodHandler { private static readonly TriggerOnDeathHandler _instance = new TriggerOnDeathHandler(); private TriggerOnDeathHandler() { } public static void Register() { RoutedRpcManager.AddHandler("OnDeath", _instance); } public override bool Process(RoutedRPCData routedRpcData) { return true; } } public sealed class WNTHealthChangedHandler : RpcMethodHandler { private static readonly WNTHealthChangedHandler _instance = new WNTHealthChangedHandler(); private WNTHealthChangedHandler() { } public static void Register() { RoutedRpcManager.AddHandler("RPC_WNTHealthChanged", _instance); } public override bool Process(RoutedRPCData routedRpcData) { return false; } } public abstract class RpcMethodHandler { public abstract bool Process(RoutedRPCData routedRpcData); } [HarmonyPatch] public static class RpcRouterPatches { [HarmonyPatch(typeof(ZRoutedRpc), "RPC_RoutedRPC")] [HarmonyPrefix] public static bool RPC_RoutedRPC_Prefix(ZRoutedRpc __instance, ZRpc rpc, ZPackage pkg) { if (!__instance.m_server) { return true; } RoutedRpcManager.ProcessRoutedRPC(__instance, rpc, pkg); return false; } } [HarmonyPatch] public static class ServerAuthorityPatches { private struct PeerMotionSample { public Vector3 Pos; public float Time; public Vector3 Velocity; } private const int DefaultActiveArea = 3; private const int DefaultDistantArea = 5; private const float ZoneSizeMeters = 64f; private const float KillPlaneY = -5000f; private const float RescueRaycastStartHeight = 6000f; private const float RescueRaycastMaxDistance = 12000f; private const float RescueGroundClearance = 1f; private const string DefaultRescueLayerMaskCsv = "Default,static_solid,Default_small,piece,terrain,vehicle"; private const float VelocityEmaWeight = 0.5f; private const float VelocitySampleMinDeltaSec = 0.05f; private const float VelocitySampleStaleSec = 5f; private const float VelocityRefreshMinMoveSqr = 0.01f; private const float DefaultPredictionMinSpeedMps = 2f; private const int DefaultPredictionMaxLookaheadZones = 9; private static readonly Dictionary _peerMotion = new Dictionary(); private static readonly List _cdoNearScratch = new List(8192); private static readonly List _cdoDistantScratch = new List(8192); private static readonly HashSet _cdoSeenSet = new HashSet(); private static readonly List _cdoNearFiltered = new List(8192); private static readonly List _cdoDistantFiltered = new List(8192); private static readonly List _orphanScratch = new List(64); private const float OrphanLogIntervalSec = 5f; private static float _orphanNextLogTime; private static int s_fellOutRescueMaskCached; private static string s_fellOutRescueMaskCachedSource; internal static Vector3 GetPredictedRefPos(ZNetPeer peer) { //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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) Vector3 refPos = peer.GetRefPos(); ConfigEntry configEnablePredictiveZoneStreaming = FiresGhettoNetworkMod.ConfigEnablePredictiveZoneStreaming; if (configEnablePredictiveZoneStreaming == null || !configEnablePredictiveZoneStreaming.Value) { return refPos; } float num = FiresGhettoNetworkMod.ConfigPredictionLookaheadSec?.Value ?? 0f; if (num <= 0f) { return refPos; } long uid = peer.m_uid; float time = Time.time; if (!_peerMotion.TryGetValue(uid, out var value)) { _peerMotion[uid] = new PeerMotionSample { Pos = refPos, Time = time, Velocity = Vector3.zero }; return refPos; } float num2 = time - value.Time; if (num2 > 5f) { _peerMotion[uid] = new PeerMotionSample { Pos = refPos, Time = time, Velocity = Vector3.zero }; return refPos; } Vector3 val = RefreshVelocityIfMovedEnough(uid, value, refPos, num2, time); float num3 = FiresGhettoNetworkMod.ConfigPredictionMinVelocity?.Value ?? 2f; if (((Vector3)(ref val)).sqrMagnitude < num3 * num3) { return refPos; } int maxZones = FiresGhettoNetworkMod.ConfigPredictionMaxLookaheadZones?.Value ?? 9; return refPos + ClampForwardOffsetToMaxZones(val * num, maxZones); } private static Vector3 RefreshVelocityIfMovedEnough(long uid, PeerMotionSample prev, Vector3 currentPos, float dt, float now) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) Vector3 val = currentPos - prev.Pos; if (((Vector3)(ref val)).sqrMagnitude <= 0.01f || dt <= 0.05f) { return prev.Velocity; } Vector3 val2 = val / dt; Vector3 val3 = Vector3.Lerp(prev.Velocity, val2, 0.5f); _peerMotion[uid] = new PeerMotionSample { Pos = currentPos, Time = now, Velocity = val3 }; return val3; } private static Vector3 ClampForwardOffsetToMaxZones(Vector3 offset, int maxZones) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) float num = (float)maxZones * 64f; if (!(((Vector3)(ref offset)).sqrMagnitude > num * num)) { return offset; } return ((Vector3)(ref offset)).normalized * num; } [HarmonyPatch(typeof(ZDOMan), "RemovePeer")] [HarmonyPostfix] public static void RemovePeer_ClearMotion_Postfix(ZNetPeer netPeer) { if (netPeer != null) { _peerMotion.Remove(netPeer.m_uid); } } [HarmonyPatch(typeof(ZoneSystem), "IsActiveAreaLoaded")] [HarmonyPriority(800)] [HarmonyPrefix] public static bool IsActiveAreaLoaded_TeleportFix(ref bool __result) { if ((Object)(object)Player.m_localPlayer == (Object)null || !((Character)Player.m_localPlayer).IsTeleporting()) { return true; } __result = true; return false; } [HarmonyPatch(typeof(ZNetScene), "CreateDestroyObjects")] [HarmonyPrefix] public static bool CreateDestroyObjects_Prefix(ZNetScene __instance) { if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsDedicated() || ZNet.instance.GetConnectedPeers().Count == 0) { ServerStatusDiagnostics.s_cdo_bail_noPeers++; return true; } try { int value = FiresGhettoNetworkMod.ConfigExtendedZoneRadius.Value; int activeArea = (ZoneSystem.instance?.m_activeArea ?? 3) + value; int distantArea = (ZoneSystem.instance?.m_activeDistantArea ?? 5) + value; CollectZdosFromAllPeerActiveAreas(activeArea, distantArea); FilterAndDedupeZdos(_cdoNearScratch, _cdoNearFiltered); FilterAndDedupeZdos(_cdoDistantScratch, _cdoDistantFiltered); RecordCreateDestroyObjectsDiagnostics(); __instance.CreateObjects(_cdoNearFiltered, _cdoDistantFiltered); try { __instance.RemoveObjects(_cdoNearFiltered, _cdoDistantFiltered); } catch (NullReferenceException) when (OrphanPruneEnabled()) { PruneOrphanInstances(__instance); __instance.RemoveObjects(_cdoNearFiltered, _cdoDistantFiltered); } return false; } catch (NullReferenceException ex2) { ServerStatusDiagnostics.s_cdo_bail_nre++; LoggerOptions.LogWarning("CreateDestroyObjects encountered NRE (likely mod conflict), falling back to vanilla: " + ex2.Message); return true; } } private static void CollectZdosFromAllPeerActiveAreas(int activeArea, int distantArea) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) _cdoNearScratch.Clear(); _cdoDistantScratch.Clear(); foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { if (connectedPeer.IsReady()) { Vector2i zone = ZoneSystem.GetZone(GetPredictedRefPos(connectedPeer)); ZDOMan.instance.FindSectorObjects(zone, activeArea, distantArea, _cdoNearScratch, _cdoDistantScratch); } } } private static void FilterAndDedupeZdos(List source, List dest) { dest.Clear(); _cdoSeenSet.Clear(); for (int i = 0; i < source.Count; i++) { ZDO val = source[i]; if (val != null && val.IsValid() && !((ZDOID)(ref val.m_uid)).IsNone() && _cdoSeenSet.Add(val)) { dest.Add(val); } } } private static void RecordCreateDestroyObjectsDiagnostics() { ServerStatusDiagnostics.s_cdo_passes++; ServerStatusDiagnostics.s_cdo_nearTotal += _cdoNearScratch.Count; ServerStatusDiagnostics.s_cdo_distantTotal += _cdoDistantScratch.Count; ServerStatusDiagnostics.s_cdo_distinctNearTotal += _cdoNearFiltered.Count; ServerStatusDiagnostics.s_cdo_distinctDistantTotal += _cdoDistantFiltered.Count; if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.IsActiveAreaLoaded()) { ServerStatusDiagnostics.s_cdo_areaReadyTrue++; } else { ServerStatusDiagnostics.s_cdo_areaReadyFalse++; } } private static bool OrphanPruneEnabled() { return FiresGhettoNetworkMod.ConfigEnableInstanceOrphanPrune?.Value ?? true; } private static void PruneOrphanInstances(ZNetScene scene) { if (!OrphanPruneEnabled() || (Object)(object)scene == (Object)null || scene.m_instances == null) { return; } _orphanScratch.Clear(); int num = 0; int num2 = 0; foreach (KeyValuePair instance in scene.m_instances) { ZNetView value = instance.Value; if ((Object)(object)value == (Object)null) { _orphanScratch.Add(instance.Key); num++; } else if (value.GetZDO() == null) { _orphanScratch.Add(instance.Key); num2++; } } if (_orphanScratch.Count != 0) { for (int i = 0; i < _orphanScratch.Count; i++) { scene.m_instances.Remove(_orphanScratch[i]); } ServerStatusDiagnostics.s_cdo_orphansPruned += _orphanScratch.Count; _orphanScratch.Clear(); float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup >= _orphanNextLogTime) { LoggerOptions.LogWarning($"[m_instances orphan prune] removed {num + num2} broken entry/ies " + $"(nullView={num}, nullZdo={num2}). If this fires repeatedly, " + "another mod is mismanaging ZNetScene state — most often a permission mod blocking WearNTear.RPC_Remove on the dedi while ZDOMan still reaps the ZDO."); _orphanNextLogTime = realtimeSinceStartup + 5f; } } } [HarmonyPatch(typeof(ZoneSystem), "IsActiveAreaLoaded")] [HarmonyPrefix] public static bool IsActiveAreaLoaded_Prefix(ref bool __result, ZoneSystem __instance) { if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsDedicated() || ZNet.instance.GetPeers().Count == 0) { return true; } if (__instance.m_zones == null) { return true; } int firstMissX; int firstMissY; int num = CountMissingZonesAcrossAllPeers(__instance, out firstMissX, out firstMissY); RecordIsActiveAreaLoadedDiagnostics(num, firstMissX, firstMissY); __result = num == 0; return false; } private static int CountMissingZonesAcrossAllPeers(ZoneSystem zs, out int firstMissX, out int firstMissY) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) int activeArea = zs.m_activeArea; Dictionary zones = zs.m_zones; int num = 0; firstMissX = 0; firstMissY = 0; bool flag = false; foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (!peer.IsReady()) { continue; } Vector2i zone = ZoneSystem.GetZone(GetPredictedRefPos(peer)); for (int i = zone.y - activeArea; i <= zone.y + activeArea; i++) { for (int j = zone.x - activeArea; j <= zone.x + activeArea; j++) { if (!zones.ContainsKey(new Vector2i(j, i))) { num++; if (!flag) { firstMissX = j; firstMissY = i; flag = true; } } } } } return num; } private static void RecordIsActiveAreaLoadedDiagnostics(int missing, int firstMissX, int firstMissY) { ServerStatusDiagnostics.s_iaal_calls++; if (missing == 0) { ServerStatusDiagnostics.s_iaal_resultTrue++; return; } ServerStatusDiagnostics.s_iaal_resultFalse++; if (missing < ServerStatusDiagnostics.s_iaal_minMissingZones) { ServerStatusDiagnostics.s_iaal_minMissingZones = missing; } if (missing > ServerStatusDiagnostics.s_iaal_maxMissingZones) { ServerStatusDiagnostics.s_iaal_maxMissingZones = missing; } ServerStatusDiagnostics.s_iaal_lastMissingZoneX = firstMissX; ServerStatusDiagnostics.s_iaal_lastMissingZoneY = firstMissY; } [HarmonyPatch(typeof(ZoneSystem), "Update")] [HarmonyPostfix] public static void ZoneSystem_Update_Postfix(ZoneSystem __instance) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsDedicated() || ZNet.instance.GetPeers().Count == 0) { return; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady()) { __instance.CreateLocalZones(GetPredictedRefPos(peer)); } } } [HarmonyPatch(typeof(ZNetScene), "OutsideActiveArea", new Type[] { typeof(Vector3) })] [HarmonyPrefix] public static bool OutsideActiveArea_Prefix(ref bool __result, Vector3 point) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsDedicated() || ZNet.instance.GetPeers().Count == 0) { return true; } int value = FiresGhettoNetworkMod.ConfigExtendedZoneRadius.Value; int activeArea = (ZoneSystem.instance?.m_activeArea ?? 3) + value; __result = !IsPointInsideAnyPeerActiveArea(point, activeArea); return false; } private static bool IsPointInsideAnyPeerActiveArea(Vector3 point, int activeArea) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady() && !ZNetScene.OutsideActiveArea(point, ZoneSystem.GetZone(GetPredictedRefPos(peer)), activeArea)) { return true; } } return false; } [HarmonyPatch(typeof(Tameable), "Awake")] [HarmonyPrefix] public static bool Tameable_Awake_Prefix() { return !IsDedicatedServer(); } [HarmonyPatch(typeof(Tameable), "Update")] [HarmonyPrefix] public static bool Tameable_Update_Prefix() { return !IsDedicatedServer(); } [HarmonyPatch(typeof(Tameable), "SetText")] [HarmonyPrefix] public static bool Tameable_SetText_Prefix() { return !IsDedicatedServer(); } [HarmonyPatch(typeof(AudioMan), "Update")] [HarmonyPrefix] public static bool AudioMan_Update_Prefix() { return !IsDedicatedServer(); } [HarmonyPatch(typeof(TerrainComp), "Awake")] [HarmonyPrefix] public static bool TerrainComp_Awake_Prefix() { return !IsDedicatedServer(); } [HarmonyPatch(typeof(TerrainComp), "Update")] [HarmonyPrefix] public static bool TerrainComp_Update_Prefix() { return !IsDedicatedServer(); } [HarmonyPatch(typeof(TerrainComp), "OnDestroy")] [HarmonyPrefix] public static bool TerrainComp_OnDestroy_Prefix() { return !IsDedicatedServer(); } [HarmonyPatch(typeof(ShieldDomeImageEffect), "Awake")] [HarmonyPrefix] public static bool ShieldDomeImageEffect_Awake_Prefix() { return !IsDedicatedServer(); } private static bool IsDedicatedServer() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsDedicated(); } return false; } private static int GetFellOutRescueMask() { string text = FiresGhettoNetworkMod.ConfigDediFellOutRescueLayers?.Value ?? "Default,static_solid,Default_small,piece,terrain,vehicle"; if (s_fellOutRescueMaskCachedSource == text) { return s_fellOutRescueMaskCached; } int num = ParseLayerMaskCsv(text); if (num == 0) { LoggerOptions.LogWarning("[FellOutRescue] Layer mask from '" + text + "' resolved to 0 — falling back to vanilla terrain only. Check layer names exist in the build."); num = LayerMask.GetMask(new string[1] { "terrain" }); } s_fellOutRescueMaskCached = num; s_fellOutRescueMaskCachedSource = text; return num; } private static int ParseLayerMaskCsv(string csv) { List list = new List(); string[] array = csv.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i]?.Trim(); if (!string.IsNullOrEmpty(text)) { list.Add(text); } } if (list.Count <= 0) { return 0; } return LayerMask.GetMask(list.ToArray()); } [HarmonyPatch(typeof(ZSyncTransform), "OwnerSync")] [HarmonyPrefix] public static bool ZSyncTransform_OwnerSync_DediFellOutFix_Prefix(ZSyncTransform __instance, Rigidbody ___m_body) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (!IsDedicatedServer()) { return true; } Vector3 position = ((Component)__instance).transform.position; if (position.y >= -5000f) { return true; } FreezeRigidbodyIfActive(___m_body); TryRescueOntoGroundCollider(__instance, ___m_body, position); return false; } private static void FreezeRigidbodyIfActive(Rigidbody rb) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)rb == (Object)null) && !rb.isKinematic) { rb.isKinematic = true; rb.linearVelocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } } private static void TryRescueOntoGroundCollider(ZSyncTransform sync, Rigidbody rb, Vector3 currentPos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) RaycastHit val = default(RaycastHit); if (Physics.Raycast(new Vector3(currentPos.x, 6000f, currentPos.z), Vector3.down, ref val, 12000f, GetFellOutRescueMask())) { Vector3 position = currentPos; position.y = ((RaycastHit)(ref val)).point.y + 1f; ((Component)sync).transform.position = position; if ((Object)(object)rb != (Object)null) { rb.isKinematic = false; Physics.SyncTransforms(); } } } } public static class ServerClientUtils { private static ManualLogSource _earlyLogger; public static bool IsDedicatedServerDetected { get; private set; } public static void Detect(ManualLogSource logger = null) { //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Invalid comparison between Unknown and I4 _earlyLogger = logger; IsDedicatedServerDetected = false; try { string path = Process.GetCurrentProcess().MainModule?.FileName ?? ""; string text = Path.GetFileNameWithoutExtension(path).ToLowerInvariant(); string text2 = Path.GetDirectoryName(path)?.ToLowerInvariant() ?? ""; Log("Executable name: " + text); Log("Executable directory: " + text2); if (text.Contains("server")) { IsDedicatedServerDetected = true; Log("Detected dedicated server via executable name containing 'server'."); return; } if (text2.Contains("server") || text2.Contains("dedicated")) { IsDedicatedServerDetected = true; Log("Detected dedicated server via directory path containing 'server' or 'dedicated'."); return; } Log($"Application.isBatchMode = {Application.isBatchMode}"); if (Application.isBatchMode) { IsDedicatedServerDetected = true; Log("Detected dedicated server via Application.isBatchMode."); return; } string[] commandLineArgs = Environment.GetCommandLineArgs(); foreach (string text3 in commandLineArgs) { string text4 = text3.ToLowerInvariant(); if (text4 == "-batchmode" || text4 == "-nographics" || text4.Contains("dedicated")) { IsDedicatedServerDetected = true; Log("Detected dedicated server via command line argument: " + text3); return; } } if ((int)SystemInfo.graphicsDeviceType == 4) { IsDedicatedServerDetected = true; Log("Detected dedicated server via null graphics device (headless mode)."); return; } Type type = AccessTools.TypeByName("ZNet") ?? Type.GetType("ZNet, Assembly-CSharp"); if (type == null) { Log("ZNet type not found; assuming client/listen-server."); return; } MethodInfo method = type.GetMethod("IsDedicated", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { object obj = method.Invoke(null, null); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { IsDedicatedServerDetected = true; Log("Detected dedicated server via ZNet.IsDedicated()."); return; } } MethodInfo method2 = type.GetMethod("IsServer", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method2 != null) { object obj2 = method2.Invoke(null, null); bool flag2 = default(bool); int num2; if (obj2 is bool) { flag2 = (bool)obj2; num2 = 1; } else { num2 = 0; } if (((uint)num2 & (flag2 ? 1u : 0u)) != 0) { IsDedicatedServerDetected = true; Log("Detected dedicated server via ZNet.IsServer()."); return; } } object obj3 = (type.GetField("m_instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))?.GetValue(null); if (obj3 != null) { PropertyInfo property = type.GetProperty("IsServer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { object value = property.GetValue(obj3); bool flag3 = default(bool); int num3; if (value is bool) { flag3 = (bool)value; num3 = 1; } else { num3 = 0; } if (((uint)num3 & (flag3 ? 1u : 0u)) != 0) { IsDedicatedServerDetected = true; Log("Detected dedicated server via ZNet.instance.IsServer."); return; } } MethodInfo method3 = type.GetMethod("IsDedicated", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method3 != null) { object obj4 = method3.Invoke(obj3, null); bool flag4 = default(bool); int num4; if (obj4 is bool) { flag4 = (bool)obj4; num4 = 1; } else { num4 = 0; } if (((uint)num4 & (flag4 ? 1u : 0u)) != 0) { IsDedicatedServerDetected = true; Log("Detected dedicated server via ZNet.instance.IsDedicated."); return; } } } Log("No dedicated-server indicator found; assuming client/listen-server."); } catch (Exception ex) { LogWarn("Server detection failed: " + ex.Message); IsDedicatedServerDetected = false; } } private static void Log(string message) { ManualLogSource earlyLogger = _earlyLogger; if (earlyLogger != null) { earlyLogger.LogInfo((object)message); } } private static void LogWarn(string message) { ManualLogSource earlyLogger = _earlyLogger; if (earlyLogger != null) { earlyLogger.LogWarning((object)message); } } public static bool IsRunningOnDedicatedServer() { if (IsDedicatedServerDetected) { return true; } if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsDedicated(); } return false; } } public static class SendCongestion { private static bool s_cached; private static float s_nextCheck; private const float RecheckSeconds = 1f; public static int EffectiveCapBytes() { return EffectiveConfig.QueueSize() switch { QueueSizeOptions._80KB => 81920, QueueSizeOptions._64KB => 65536, QueueSizeOptions._48KB => 49152, QueueSizeOptions._32KB => 32768, _ => 102400, }; } private static float ThresholdFraction() { return (float)Mathf.Clamp((FiresGhettoNetworkMod.ConfigSendCongestionThresholdPct != null) ? FiresGhettoNetworkMod.ConfigSendCongestionThresholdPct.Value : 50, 10, 100) / 100f; } public static float CongestionThresholdBytes() { return (float)EffectiveCapBytes() * ThresholdFraction(); } public static int GetQueueSize(ZDOPeer peer) { if (peer == null || peer.m_peer == null || peer.m_peer.m_socket == null) { return -1; } try { return peer.m_peer.m_socket.GetSendQueueSize(); } catch { return -1; } } public static bool IsPeerCongested(ZDOPeer peer) { int queueSize = GetQueueSize(peer); if (queueSize < 0) { return false; } return (float)queueSize >= (float)EffectiveCapBytes() * ThresholdFraction(); } public static bool AnyPeerCongested() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < s_nextCheck) { return s_cached; } s_nextCheck = realtimeSinceStartup + 1f; s_cached = false; if ((Object)(object)ZNet.instance == (Object)null) { return false; } float num = (float)EffectiveCapBytes() * ThresholdFraction(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.m_socket != null) { int sendQueueSize; try { sendQueueSize = peer.m_socket.GetSendQueueSize(); } catch { continue; } if ((float)sendQueueSize >= num) { s_cached = true; break; } } } return s_cached; } } [HarmonyPatch] public static class SendZDOsHeartbeatDiagnostic { private struct PeerStats { public int TicksAttempted; public int TicksBailedQueueFull; public float NextLogTime; public int LastQueueSize; } private static readonly Dictionary s_stats = new Dictionary(); private const int VanillaMinBudget = 2048; private static int EffectiveCapBytes() { return SendCongestion.EffectiveCapBytes(); } [HarmonyPatch(typeof(ZDOMan), "SendZDOs")] [HarmonyPrefix] [HarmonyPriority(0)] public static void SendZDOs_Heartbeat_Prefix(ZDOPeer peer, bool flush) { if (FiresGhettoNetworkMod.ConfigEnableSendHeartbeatLog == null || !FiresGhettoNetworkMod.ConfigEnableSendHeartbeatLog.Value || peer == null || peer.m_peer == null || peer.m_peer.m_socket == null || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsDedicated()) { return; } long uid = peer.m_peer.m_uid; int sendQueueSize; try { sendQueueSize = peer.m_peer.m_socket.GetSendQueueSize(); } catch { return; } int num = EffectiveCapBytes(); int num2 = num - sendQueueSize; bool num3 = !flush && sendQueueSize > num; bool flag = num2 < 2048; if (!s_stats.TryGetValue(uid, out var value)) { value = new PeerStats { NextLogTime = Time.realtimeSinceStartup + 10f }; } value.TicksAttempted++; if (num3 || flag) { value.TicksBailedQueueFull++; } value.LastQueueSize = sendQueueSize; s_stats[uid] = value; float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup < value.NextLogTime)) { string text = ""; try { text = peer.m_peer.m_socket.GetEndPointString(); } catch { } float num4 = ((value.TicksAttempted > 0) ? (100f * (float)value.TicksBailedQueueFull / (float)value.TicksAttempted) : 0f); LoggerOptions.LogMessage($"[SendZDOsHB] peer={text} uid={uid} queue={sendQueueSize}B cap={num}B budget={num2}B " + $"ticks(attempted={value.TicksAttempted},bailedQueueFull={value.TicksBailedQueueFull},bailPct={num4:F1}%) " + $"flush={flush}."); value.TicksAttempted = 0; value.TicksBailedQueueFull = 0; value.NextLogTime = realtimeSinceStartup + 10f; s_stats[uid] = value; } } [HarmonyPatch(typeof(ZDOMan), "RemovePeer")] [HarmonyPostfix] public static void RemovePeer_ClearStats_Postfix(ZNetPeer netPeer) { if (netPeer != null) { s_stats.Remove(netPeer.m_uid); } } } [HarmonyPatch] public static class ServerOwnershipPatches { private static bool _firstFireLogged; private static readonly HashSet _teleportWorldPrefabs = new HashSet(); private static bool _portalExclusionActive; [HarmonyPatch(typeof(ZNetScene), "Awake")] [HarmonyPostfix] [HarmonyPriority(0)] public static void ZNetScene_Awake_BuildPortalExclusionSet(ZNetScene __instance) { _teleportWorldPrefabs.Clear(); _portalExclusionActive = false; if ((Object)(object)__instance == (Object)null || __instance.m_prefabs == null || !IsTargetPortalProtectionLoaded()) { return; } foreach (GameObject prefab in __instance.m_prefabs) { if (!((Object)(object)prefab == (Object)null) && (Object)(object)prefab.GetComponent() != (Object)null) { _teleportWorldPrefabs.Add(StringExtensionMethods.GetStableHashCode(((Object)prefab).name)); } } if (_teleportWorldPrefabs.Count > 0) { _portalExclusionActive = true; LoggerOptions.LogMessage("[ServerOwnership] TargetPortalProtection detected. Excluding " + $"{_teleportWorldPrefabs.Count} TeleportWorld prefab(s) from V2 broad-ownership " + "claim so portal destroy flows route through a peer client where TPP's Player.m_localPlayer-based permission check actually works."); } } private static bool IsTargetPortalProtectionLoaded() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { if (assembly.GetType("TargetPortalProtection.TargetPortalProtection", throwOnError: false, ignoreCase: false) != null) { return true; } } catch { } } return false; } [HarmonyPatch(typeof(ZDOMan), "ReleaseNearbyZDOS")] [HarmonyPrefix] public static bool ReleaseNearbyZDOS_Prefix(ZDOMan __instance, Vector3 refPosition, long uid) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsDedicated()) { return true; } if ((Object)(object)ZoneSystem.instance == (Object)null) { return true; } ConfigEntry configEnableServerAuthority = FiresGhettoNetworkMod.ConfigEnableServerAuthority; if (configEnableServerAuthority == null || !configEnableServerAuthority.Value) { return true; } ConfigEntry configEnableServerOwnership = FiresGhettoNetworkMod.ConfigEnableServerOwnership; if (configEnableServerOwnership == null || !configEnableServerOwnership.Value) { return true; } ConfigEntry configEnableServerOwnershipSelective = FiresGhettoNetworkMod.ConfigEnableServerOwnershipSelective; if (configEnableServerOwnershipSelective != null && configEnableServerOwnershipSelective.Value) { return true; } long sessionID = ZDOMan.GetSessionID(); LogFirstFireOnce(sessionID); ServerStatusDiagnostics.s_so_passes++; Vector2i zone = ZoneSystem.GetZone(refPosition); __instance.m_tempNearObjects.Clear(); __instance.FindSectorObjects(zone, ZoneSystem.instance.m_activeArea, 0, __instance.m_tempNearObjects, (List)null); foreach (ZDO tempNearObject in __instance.m_tempNearObjects) { if (tempNearObject != null && tempNearObject.Persistent && (!_portalExclusionActive || !_teleportWorldPrefabs.Contains(tempNearObject.m_prefab))) { ServerStatusDiagnostics.s_so_zdosProcessed++; ApplySssOwnershipRule(__instance, tempNearObject, uid, sessionID); } } return false; } private static void ApplySssOwnershipRule(ZDOMan zdoMan, ZDO zdo, long callerUid, long serverUid) { //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_0007: 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) Vector2i sector = zdo.GetSector(); bool flag = SectorCoveredByAnyConnectedPeer(sector); long owner = zdo.GetOwner(); if (owner == callerUid || owner == serverUid) { if (!flag) { zdo.SetOwner(0L); ServerStatusDiagnostics.s_so_releases++; } } else if ((owner == 0L || !zdoMan.IsInPeerActiveArea(sector, owner)) && flag) { zdo.SetOwner(serverUid); ServerStatusDiagnostics.s_so_transfersToServer++; } } private static bool SectorCoveredByAnyConnectedPeer(Vector2i sector) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && ZNetScene.InActiveArea(sector, ZoneSystem.GetZone(peer.GetRefPos()))) { return true; } } return false; } private static void LogFirstFireOnce(long serverUid) { if (!_firstFireLogged) { _firstFireLogged = true; LoggerOptions.LogMessage("[ServerOwnership] Server-side ZDO ownership transfer engaged. " + $"serverUid={serverUid}, peers={ZNet.instance.GetPeers().Count}. " + "(First ZDOMan.ReleaseNearbyZDOS pass on this session; further passes aggregate into [ServerStatus].)"); } } } [HarmonyPatch] public static class ServerOwnershipPatchesV3 { private static readonly HashSet s_simulatedPrefabs = new HashSet(); private static bool s_built; private static readonly List s_tempNearObjects = new List(); private static bool s_firstFireLogged; private static int s_passCount; private static int s_zdosProcessed; private static int s_transfersToServer; private static int s_releases; private static int s_simulatedSeen; private static int s_nonSimulatedTransfersToPeer; private static float s_nextStatLogTime; [HarmonyPatch(typeof(ZNetScene), "Awake")] [HarmonyPostfix] [HarmonyPriority(0)] public static void ZNetScene_Awake_BuildSet(ZNetScene __instance) { s_simulatedPrefabs.Clear(); s_built = false; if ((Object)(object)__instance == (Object)null || __instance.m_prefabs == null) { return; } foreach (GameObject prefab in __instance.m_prefabs) { if (!((Object)(object)prefab == (Object)null) && !((Object)(object)prefab.GetComponent() != (Object)null) && ((Object)(object)prefab.GetComponent() != (Object)null || (Object)(object)prefab.GetComponent() != (Object)null)) { s_simulatedPrefabs.Add(StringExtensionMethods.GetStableHashCode(((Object)prefab).name)); } } s_built = true; LoggerOptions.LogMessage($"[ServerOwnership-V3] simulated-prefab set built ({s_simulatedPrefabs.Count} entries — Character/Ship only)."); } [HarmonyPatch(typeof(ZNetScene), "Shutdown")] [HarmonyPostfix] public static void ZNetScene_Shutdown_DropSet() { s_simulatedPrefabs.Clear(); s_built = false; } [HarmonyPatch(typeof(ZDOMan), "ReleaseNearbyZDOS")] [HarmonyPrefix] public static bool ReleaseNearbyZDOS_Prefix(ZDOMan __instance, Vector3 refPosition, long uid) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsDedicated()) { return true; } if (!s_built || s_simulatedPrefabs.Count == 0) { return true; } if ((Object)(object)ZoneSystem.instance == (Object)null) { return true; } ConfigEntry configEnableServerAuthority = FiresGhettoNetworkMod.ConfigEnableServerAuthority; if (configEnableServerAuthority == null || !configEnableServerAuthority.Value) { return true; } ConfigEntry configEnableServerOwnershipSelective = FiresGhettoNetworkMod.ConfigEnableServerOwnershipSelective; if (configEnableServerOwnershipSelective == null || !configEnableServerOwnershipSelective.Value) { return true; } long sessionID = ZDOMan.GetSessionID(); Vector2i zone = ZoneSystem.GetZone(refPosition); s_tempNearObjects.Clear(); __instance.FindSectorObjects(zone, ZoneSystem.instance.m_activeArea, 0, s_tempNearObjects, (List)null); int num = ZoneSystem.instance.m_activeArea - 1; s_passCount++; int num2 = 0; int num3 = 0; foreach (ZDO s_tempNearObject in s_tempNearObjects) { if (s_tempNearObject == null || !s_tempNearObject.Persistent) { continue; } num2++; Vector2i sector = s_tempNearObject.GetSector(); long owner = s_tempNearObject.GetOwner(); bool flag = s_simulatedPrefabs.Contains(s_tempNearObject.m_prefab); if (flag) { num3++; } if (owner == uid || owner == sessionID) { if (ZNetScene.InActiveArea(sector, zone, num)) { continue; } bool flag2 = false; foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && ZNetScene.InActiveArea(sector, ZoneSystem.GetZone(peer.GetRefPos()))) { flag2 = true; break; } } if (!flag2) { s_tempNearObject.SetOwner(0L); s_releases++; } continue; } bool flag3 = owner == 0L || !__instance.IsInPeerActiveArea(sector, owner); bool flag4 = false; foreach (ZNetPeer peer2 in ZNet.instance.GetPeers()) { if (peer2 != null && ZNetScene.InActiveArea(sector, ZoneSystem.GetZone(peer2.GetRefPos()))) { flag4 = true; break; } } if (flag3 && flag4) { if (flag) { s_tempNearObject.SetOwner(sessionID); s_transfersToServer++; } else if (uid != sessionID) { s_tempNearObject.SetOwner(uid); s_nonSimulatedTransfersToPeer++; } } } s_zdosProcessed += num2; s_simulatedSeen += num3; if (!s_firstFireLogged) { s_firstFireLogged = true; LoggerOptions.LogMessage("[ServerOwnership-V3] ReleaseNearbyZDOS_Prefix fired for the first time. " + $"serverUid={sessionID}, caller uid={uid}, refPos=({refPosition.x:F2}, {refPosition.y:F2}, {refPosition.z:F2}), " + $"connectedPeers={ZNet.instance.GetConnectedPeers().Count}, " + $"simulatedPrefabs={s_simulatedPrefabs.Count}. Patch is wired correctly."); } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup >= s_nextStatLogTime) { LoggerOptions.LogMessage($"[ServerOwnership-V3] Last 10s: {s_passCount} passes, {s_zdosProcessed} ZDOs processed " + $"({s_simulatedSeen} simulated-class), " + $"{s_transfersToServer} transfers-to-server, " + $"{s_nonSimulatedTransfersToPeer} transfers-to-peer (non-simulated), " + $"{s_releases} releases-to-unowned."); s_passCount = 0; s_zdosProcessed = 0; s_simulatedSeen = 0; s_transfersToServer = 0; s_nonSimulatedTransfersToPeer = 0; s_releases = 0; s_nextStatLogTime = realtimeSinceStartup + 10f; } return false; } } [HarmonyPatch] public static class FallThroughProbe { private static int s_groundMask; private static bool s_maskReady; private static int GroundMask() { if (!s_maskReady) { s_groundMask = LayerMask.GetMask(new string[6] { "Default", "static_solid", "Default_small", "piece", "terrain", "vehicle" }); s_maskReady = true; } return s_groundMask; } [HarmonyPatch(typeof(ItemDrop), "Awake")] [HarmonyPostfix] public static void ItemDrop_Awake_Probe(ItemDrop __instance) { Begin(((Object)(object)__instance != (Object)null) ? ((Component)__instance).gameObject : null, "ItemDrop"); } [HarmonyPatch(typeof(TombStone), "Awake")] [HarmonyPostfix] public static void TombStone_Awake_Probe(TombStone __instance) { Begin(((Object)(object)__instance != (Object)null) ? ((Component)__instance).gameObject : null, "TombStone"); } private static void Begin(GameObject go, string kind) { if (!((Object)(object)go == (Object)null) && !((Object)(object)FiresGhettoNetworkMod.Instance == (Object)null)) { ((MonoBehaviour)FiresGhettoNetworkMod.Instance).StartCoroutine(Track(go, kind)); } } private static IEnumerator Track(GameObject go, string kind) { yield return (object)new WaitForSeconds(0.25f); if ((Object)(object)go == (Object)null) { yield break; } ZNetView component = go.GetComponent(); Vector3 spawnPos = go.transform.position; if (((Vector3)(ref spawnPos)).sqrMagnitude < 1f) { yield break; } bool flag = (Object)(object)component != (Object)null && component.IsValid() && component.IsOwner(); RaycastHit val = default(RaycastHit); bool flag2 = Physics.Raycast(spawnPos + Vector3.up * 0.3f, Vector3.down, ref val, 8f, GroundMask(), (QueryTriggerInteraction)1); string beneath = (flag2 ? $"{((Object)((Component)((RaycastHit)(ref val)).collider).gameObject).name}@{((RaycastHit)(ref val)).distance:F2}m" : "NONE"); if (!flag2) { LoggerOptions.LogMessage($"[FallProbe] SPAWN {kind} pos=({spawnPos.x:F1},{spawnPos.y:F1},{spawnPos.z:F1}) " + $"owner={flag} beneath=NONE"); } yield return (object)new WaitForSeconds(3f); if (!((Object)(object)go == (Object)null)) { Vector3 position = go.transform.position; float num = spawnPos.y - position.y; if (num > 0.5f) { RaycastHit val2 = default(RaycastHit); string text = (Physics.Raycast(position + Vector3.up * 0.3f, Vector3.down, ref val2, 8f, GroundMask(), (QueryTriggerInteraction)1) ? $"{((Object)((Component)((RaycastHit)(ref val2)).collider).gameObject).name}@{((RaycastHit)(ref val2)).distance:F2}m" : "NONE"); LoggerOptions.LogWarning($"[FallProbe] FELL {kind} drop={num:F2}m spawn=({spawnPos.x:F1},{spawnPos.y:F1},{spawnPos.z:F1}) " + $"now=({position.x:F1},{position.y:F1},{position.z:F1}) spawnBeneath={beneath} nowBeneath={text}"); } } } } [HarmonyPatch] public static class FallThroughGuard { private const float MaxFreezeSec = 3f; private const float SupportRayUp = 0.3f; private const float SupportRayDown = 1f; private static int s_groundMask; private static bool s_maskReady; private static int GroundMask() { if (!s_maskReady) { s_groundMask = LayerMask.GetMask(new string[6] { "Default", "static_solid", "Default_small", "piece", "terrain", "vehicle" }); s_maskReady = true; } return s_groundMask; } [HarmonyPatch(typeof(ItemDrop), "Awake")] [HarmonyPostfix] public static void ItemDrop_Awake(ItemDrop __instance) { Begin(((Object)(object)__instance != (Object)null) ? ((Component)__instance).gameObject : null); } [HarmonyPatch(typeof(TombStone), "Awake")] [HarmonyPostfix] public static void TombStone_Awake(TombStone __instance) { Begin(((Object)(object)__instance != (Object)null) ? ((Component)__instance).gameObject : null); } private static void Begin(GameObject go) { if (!((Object)(object)go == (Object)null) && !((Object)(object)FiresGhettoNetworkMod.Instance == (Object)null)) { ConfigEntry configEnableFallThroughGuard = FiresGhettoNetworkMod.ConfigEnableFallThroughGuard; if (configEnableFallThroughGuard == null || configEnableFallThroughGuard.Value) { ((MonoBehaviour)FiresGhettoNetworkMod.Instance).StartCoroutine(Guard(go)); } } } private static IEnumerator Guard(GameObject go) { yield return null; if ((Object)(object)go == (Object)null) { yield break; } ZNetView component = go.GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner()) { yield break; } Rigidbody component2 = go.GetComponent(); if ((Object)(object)component2 == (Object)null || component2.isKinematic || HasSupport(go.transform.position)) { yield break; } component2.isKinematic = true; component2.linearVelocity = Vector3.zero; component2.angularVelocity = Vector3.zero; float waited = 0f; while (waited < 3f) { yield return (object)new WaitForFixedUpdate(); if ((Object)(object)go == (Object)null) { yield break; } waited += Time.fixedDeltaTime; if (HasSupport(go.transform.position)) { break; } } if (!((Object)(object)go == (Object)null)) { Rigidbody component3 = go.GetComponent(); ZNetView component4 = go.GetComponent(); if ((Object)(object)component3 != (Object)null && (Object)(object)component4 != (Object)null && component4.IsValid() && component4.IsOwner()) { component3.isKinematic = false; } } } private static bool HasSupport(Vector3 pos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) return Physics.Raycast(pos + Vector3.up * 0.3f, Vector3.down, 1.3f, GroundMask(), (QueryTriggerInteraction)1); } } [HarmonyPatch] public static class PieceTypeAudit { private static bool s_started; [HarmonyPatch(typeof(ZNetScene), "Awake")] [HarmonyPostfix] public static void ZNetScene_Awake_Postfix() { if (!s_started && !((Object)(object)FiresGhettoNetworkMod.Instance == (Object)null)) { s_started = true; ((MonoBehaviour)FiresGhettoNetworkMod.Instance).StartCoroutine(RunWhenPrefabsSettle()); } } private static IEnumerator RunWhenPrefabsSettle() { int last = -1; int stableTicks = 0; for (int i = 0; i < 60; i++) { ZNetScene instance = ZNetScene.instance; int num = (((Object)(object)instance != (Object)null) ? instance.m_prefabs.Count : 0); if (num > 0 && num == last) { int num2 = stableTicks + 1; stableTicks = num2; if (num2 >= 3) { break; } } else { stableTicks = 0; } last = num; yield return (object)new WaitForSeconds(1f); } Audit(); } private static void Audit() { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Invalid comparison between Unknown and I4 //IL_00bb: Unknown result type (might be due to invalid IL or missing references) ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { LoggerOptions.LogWarning("[PieceAudit] ZNetScene null at audit time - aborting."); return; } int num = 0; int num2 = 0; List list = new List(); foreach (GameObject prefab in instance.m_prefabs) { if ((Object)(object)prefab == (Object)null || (Object)(object)prefab.GetComponent() == (Object)null) { continue; } ZNetView component = prefab.GetComponent(); if (!((Object)(object)component == (Object)null)) { num++; if ((int)component.m_type != 2) { num2++; bool flag = (Object)(object)prefab.GetComponentInChildren() != (Object)null; bool flag2 = (Object)(object)prefab.GetComponent() != (Object)null; list.Add($" {((Object)prefab).name} type={component.m_type} collider={flag} wearNTear={flag2}"); } } } LoggerOptions.LogMessage($"[PieceAudit] Scanned {num} build-piece prefabs; {num2} are NOT Solid."); if (num2 <= 0) { return; } LoggerOptions.LogWarning("[PieceAudit] NON-SOLID pieces below. collider=True + wearNTear=True = a structural piece you can stand on that instantiates in the item tier -> prime fall-through suspect:"); foreach (string item in list) { LoggerOptions.LogWarning(item); } } } [HarmonyPatch] public static class ServerDisconnectDiagnostics { private const float BurstWindowSec = 10f; private const int BurstWarnThreshold = 3; private static readonly Dictionary _connectedSince = new Dictionary(); private static readonly List _recentDisconnects = new List(); [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPostfix] public static void OnNewConnection_StampConnectTime(ZNetPeer peer) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && peer?.m_socket != null) { _connectedSince[peer.m_socket] = Time.realtimeSinceStartup; } } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPrefix] public static void Disconnect_LogPeer(ZNetPeer peer) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && peer != null) { float realtimeSinceStartup = Time.realtimeSinceStartup; string text = "duration=unknown"; if (peer.m_socket != null && _connectedSince.TryGetValue(peer.m_socket, out var value)) { text = $"connected={realtimeSinceStartup - value:F0}s"; _connectedSince.Remove(peer.m_socket); } int num = CountRecentDisconnectsAndRecord(realtimeSinceStartup); string text2 = (string.IsNullOrEmpty(peer.m_playerName) ? "" : peer.m_playerName); string text3 = SafeEndpoint(peer); LoggerOptions.LogMessage($"[Disconnect] peer={text2} uid={peer.m_uid} {text3} {text} " + $"(#{num} in last {10f:F0}s, {ConnectedPeerCount()} still connected)."); if (num >= 3) { LoggerOptions.LogWarning($"[Disconnect] {num} peers dropped within {10f:F0}s — looks like a mass " + "timeout, not individual link failures. The usual cause is a main-thread stall (a long world save, a GC pause, or a config-reload storm) that freezes the server past the Steam connection timeout. Check the save duration / frame hitches right before this window."); } } } private static int CountRecentDisconnectsAndRecord(float now) { _recentDisconnects.Add(now); float num = now - 10f; int i; for (i = 0; i < _recentDisconnects.Count && _recentDisconnects[i] < num; i++) { } if (i > 0) { _recentDisconnects.RemoveRange(0, i); } return _recentDisconnects.Count; } private static int ConnectedPeerCount() { ZNet instance = ZNet.instance; return ((instance == null) ? ((int?)null) : instance.GetConnectedPeers()?.Count).GetValueOrDefault(); } private static string SafeEndpoint(ZNetPeer peer) { try { ISocket socket = peer.m_socket; string text = ((socket != null) ? socket.GetHostName() : null); return string.IsNullOrEmpty(text) ? "endpoint=?" : ("endpoint=" + text); } catch { return "endpoint=?"; } } } [HarmonyPatch] public static class ServerStabilityPatches { private static readonly int s_requestResponsHash = StringExtensionMethods.GetStableHashCode("RequestRespons"); [HarmonyPatch(typeof(Humanoid), "UpdateAttack")] [HarmonyPrefix] public static void Humanoid_UpdateAttack_NullGuard_Prefix(Humanoid __instance) { if (!((Object)(object)__instance == (Object)null) && __instance.m_currentAttack != null && !((Object)(object)__instance.m_currentAttack.m_character != (Object)null)) { __instance.m_currentAttack = null; } } [HarmonyPatch(typeof(WearNTear), "UpdateSupport")] [HarmonyPrefix] public static void WearNTear_UpdateSupport_ReinitColliders_Prefix(WearNTear __instance) { if (!((Object)(object)__instance == (Object)null) && __instance.m_colliders != null && __instance.m_bounds == null) { __instance.SetupColliders(); } } [HarmonyPatch(typeof(ZRoutedRpc), "RouteRPC")] [HarmonyPrefix] public static void ZRoutedRpc_RouteRPC_ShipRequestRespons_Prefix(RoutedRPCData rpcData) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (rpcData == null || rpcData.m_methodHash != s_requestResponsHash || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || rpcData.m_parameters == null) { return; } int pos = rpcData.m_parameters.GetPos(); bool flag; try { flag = rpcData.m_parameters.ReadBool(); } catch { return; } rpcData.m_parameters.SetPos(pos); if (flag) { ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(rpcData.m_targetZDO) : null); if (val != null) { val.SetOwner(rpcData.m_targetPeerID); LoggerOptions.LogInfo($"RequestRespons: transferred ship ZDO {rpcData.m_targetZDO} to peer {rpcData.m_targetPeerID}."); } } } } public static class ServerStatusDiagnostics { private const float IntervalClampMinSec = 10f; private const float IntervalClampMaxSec = 3600f; private const float DefaultIntervalSec = 60f; private const float DefaultAILODNearMeters = 100f; private const float DefaultAILODFarMeters = 300f; private static int s_lastSeenUpdateCount; public static int s_uai_examined; public static int s_uai_bail_nviewNull; public static int s_uai_bail_nviewInvalid; public static int s_uai_bail_zdoNull; public static int s_uai_bail_charNull; public static int s_uai_passThrough_isOwner; public static int s_uai_passThrough_notOwner; public static int s_mai_examined; public static int s_co_calls; public static int s_co_nullReturns; public static int s_cdo_passes; public static int s_cdo_bail_noPeers; public static int s_cdo_bail_nre; public static long s_cdo_nearTotal; public static long s_cdo_distantTotal; public static long s_cdo_distinctNearTotal; public static long s_cdo_distinctDistantTotal; public static int s_cdo_areaReadyTrue; public static int s_cdo_areaReadyFalse; public static int s_cdo_orphansPruned; public static int s_iaal_calls; public static int s_iaal_resultTrue; public static int s_iaal_resultFalse; public static int s_iaal_minMissingZones = int.MaxValue; public static int s_iaal_maxMissingZones; public static int s_iaal_lastMissingZoneX; public static int s_iaal_lastMissingZoneY; public static int s_so_passes; public static int s_so_zdosProcessed; public static int s_so_transfersToServer; public static int s_so_releases; public static int s_ailod_examined; public static int s_ailod_playerOrTamed; public static int s_ailod_decidedNear; public static int s_ailod_decidedMidBand; public static int s_ailod_decidedFarRan; public static int s_ailod_decidedFarSkipped; public static int s_ailod_peersLastSeen; public static float s_ailod_minNearestDist = float.MaxValue; public static float s_ailod_maxNearestDist; private static float s_nextEmitTime; private static float s_lastEmitTime; public static void TryEmit() { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsDedicated()) { float realtimeSinceStartup = Time.realtimeSinceStartup; float num = Mathf.Clamp(FiresGhettoNetworkMod.ConfigDiagnosticIntervalSec?.Value ?? 60f, 10f, 3600f); if (s_nextEmitTime <= 0f) { SeedFirstWindow(realtimeSinceStartup, num); } else if (!(realtimeSinceStartup < s_nextEmitTime)) { EmitConsolidatedLine(realtimeSinceStartup, realtimeSinceStartup - s_lastEmitTime); ResetWindowCounters(); s_lastEmitTime = realtimeSinceStartup; s_nextEmitTime = realtimeSinceStartup + num; } } } private static void SeedFirstWindow(float now, float interval) { s_lastEmitTime = now; s_nextEmitTime = now + interval; } private static void EmitConsolidatedLine(float now, float windowSec) { int updateCount = MonoUpdaters.UpdateCount; int value = updateCount - s_lastSeenUpdateCount; s_lastSeenUpdateCount = updateCount; int value2 = BaseAI.Instances?.Count ?? (-1); int value3 = BaseAI.BaseAIInstances?.Count ?? (-1); int value4 = Character.Instances?.Count ?? (-1); ZNet instance = ZNet.instance; int valueOrDefault = ((instance == null) ? ((int?)null) : instance.GetPeers()?.Count).GetValueOrDefault(); float value5 = Mathf.Round(windowSec); StringBuilder stringBuilder = new StringBuilder(640); stringBuilder.Append("[ServerStatus] +").Append(value5).Append("s") .Append(" | tick: ") .Append(updateCount) .Append(" (+") .Append(value) .Append(")") .Append(" | peers: ") .Append(valueOrDefault) .Append(" | ai lists: ") .Append(value2) .Append(" BaseAI / ") .Append(value3) .Append(" BaseAIInst / ") .Append(value4) .Append(" Char") .Append(" | BaseAI.UpdateAI: ") .Append(s_uai_examined) .Append(" ex, owner=") .Append(s_uai_passThrough_isOwner) .Append(" notOwner=") .Append(s_uai_passThrough_notOwner) .Append(" bail(nv=") .Append(s_uai_bail_nviewNull) .Append(",inv=") .Append(s_uai_bail_nviewInvalid) .Append(",zdo=") .Append(s_uai_bail_zdoNull) .Append(",char=") .Append(s_uai_bail_charNull) .Append(")") .Append(" | MonsterAI.UpdateAI: ") .Append(s_mai_examined) .Append(" ex") .Append(" | CreateObject: ") .Append(s_co_calls - s_co_nullReturns) .Append(" ok, ") .Append(s_co_nullReturns) .Append(" null") .Append(" | CreateDestroyObjects: ") .Append(FormatCreateDestroyObjectsSegment()) .Append(" | IsActiveAreaLoaded: ") .Append(FormatIsActiveAreaLoadedSegment()) .Append(" | ServerOwnership: ") .Append(s_so_passes) .Append(" passes, ") .Append(s_so_zdosProcessed) .Append(" zdos, ") .Append(s_so_transfersToServer) .Append(" toServer, ") .Append(s_so_releases) .Append(" released"); ConfigEntry configShowAILODInServerStatus = FiresGhettoNetworkMod.ConfigShowAILODInServerStatus; if (configShowAILODInServerStatus == null || configShowAILODInServerStatus.Value) { AppendAILODSegment(stringBuilder); } LoggerOptions.LogMessage(stringBuilder.ToString()); } private static void ResetWindowCounters() { s_uai_examined = 0; s_uai_bail_nviewNull = 0; s_uai_bail_nviewInvalid = 0; s_uai_bail_zdoNull = 0; s_uai_bail_charNull = 0; s_uai_passThrough_isOwner = 0; s_uai_passThrough_notOwner = 0; s_mai_examined = 0; s_co_calls = 0; s_co_nullReturns = 0; s_cdo_passes = 0; s_cdo_bail_noPeers = 0; s_cdo_bail_nre = 0; s_cdo_nearTotal = 0L; s_cdo_distantTotal = 0L; s_cdo_distinctNearTotal = 0L; s_cdo_distinctDistantTotal = 0L; s_cdo_areaReadyTrue = 0; s_cdo_areaReadyFalse = 0; s_cdo_orphansPruned = 0; s_iaal_calls = 0; s_iaal_resultTrue = 0; s_iaal_resultFalse = 0; s_iaal_minMissingZones = int.MaxValue; s_iaal_maxMissingZones = 0; s_iaal_lastMissingZoneX = 0; s_iaal_lastMissingZoneY = 0; s_so_passes = 0; s_so_zdosProcessed = 0; s_so_transfersToServer = 0; s_so_releases = 0; s_ailod_examined = 0; s_ailod_playerOrTamed = 0; s_ailod_decidedNear = 0; s_ailod_decidedMidBand = 0; s_ailod_decidedFarRan = 0; s_ailod_decidedFarSkipped = 0; s_ailod_peersLastSeen = 0; s_ailod_minNearestDist = float.MaxValue; s_ailod_maxNearestDist = 0f; } private static string FormatCreateDestroyObjectsSegment() { if (s_cdo_passes + s_cdo_bail_noPeers + s_cdo_bail_nre == 0) { return "noCalls"; } string text = ((s_cdo_orphansPruned > 0) ? $", orphansPruned={s_cdo_orphansPruned}" : ""); return $"{s_cdo_passes} passes (bail noPeers={s_cdo_bail_noPeers}, nre={s_cdo_bail_nre}), " + "near=" + FormatBigCount(s_cdo_nearTotal) + " (distinct " + FormatBigCount(s_cdo_distinctNearTotal) + "), distant=" + FormatBigCount(s_cdo_distantTotal) + " (distinct " + FormatBigCount(s_cdo_distinctDistantTotal) + "), " + $"gate true={s_cdo_areaReadyTrue} false={s_cdo_areaReadyFalse}" + text; } private static string FormatIsActiveAreaLoadedSegment() { if (s_iaal_calls == 0) { return "noCalls"; } float num = 100f * (float)s_iaal_resultTrue / (float)s_iaal_calls; string arg = ((s_iaal_resultFalse > 0) ? $", missing[min={s_iaal_minMissingZones} max={s_iaal_maxMissingZones} lastMissCoord=({s_iaal_lastMissingZoneX},{s_iaal_lastMissingZoneY})]" : ""); return $"{s_iaal_calls} calls, {num:F1}% true{arg}"; } private static void AppendAILODSegment(StringBuilder sb) { if (s_ailod_examined == 0) { sb.Append(" | AILOD: idle"); return; } string value = ((s_ailod_minNearestDist < float.MaxValue) ? $"{s_ailod_minNearestDist:F0}-{s_ailod_maxNearestDist:F0}m" : "n/a"); float num = FiresGhettoNetworkMod.ConfigAILODNearDistance?.Value ?? 100f; float num2 = FiresGhettoNetworkMod.ConfigAILODFarDistance?.Value ?? 300f; sb.Append(" | AILOD: ").Append(s_ailod_examined).Append(" ex") .Append(" (skipTamed=") .Append(s_ailod_playerOrTamed) .Append(")") .Append(", near=") .Append(s_ailod_decidedNear) .Append(" mid=") .Append(s_ailod_decidedMidBand) .Append(" farRan=") .Append(s_ailod_decidedFarRan) .Append(" farSkipped=") .Append(s_ailod_decidedFarSkipped) .Append(", nearestPeer ") .Append(value) .Append(" (gate ") .Append(num.ToString("F0")) .Append("/") .Append(num2.ToString("F0")) .Append("m)"); } private static string FormatBigCount(long n) { if (n >= 1000000) { return ((double)n / 1000000.0).ToString("F2") + "M"; } if (n >= 1000) { return ((double)n / 1000.0).ToString("F1") + "k"; } return n.ToString(); } } [HarmonyPatch] public static class ShipFixesGroup { [HarmonyPatch(typeof(Ship), "CustomFixedUpdate")] [HarmonyPostfix] public static void CustomFixedUpdate_Postfix(Ship __instance) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) if (!FiresGhettoNetworkMod.ConfigEnableShipFixes.Value || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsDedicated() || (Object)(object)__instance.m_nview == (Object)null || !__instance.m_nview.IsValid() || !__instance.m_nview.IsOwner()) { return; } ZDO zDO = __instance.m_nview.GetZDO(); if (zDO != null) { Speed val = (Speed)zDO.GetInt(ZDOVars.s_forward, 0); if ((int)__instance.GetSpeedSetting() == 0 && (int)val != 0) { AccessTools.Field(typeof(Ship), "m_speed").SetValue(__instance, val); } float num = zDO.GetFloat(ZDOVars.s_rudder, 0f); if (__instance.GetRudderValue() == 0f && num != 0f) { AccessTools.Field(typeof(Ship), "m_rudderValue").SetValue(__instance, num); } } } [HarmonyPatch(typeof(Ship), "UpdateOwner")] [HarmonyPrefix] public static bool UpdateOwner_Prefix() { if (!FiresGhettoNetworkMod.ConfigEnableShipFixes.Value) { return true; } if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsDedicated()) { return true; } return false; } } public static class WearNTearClassifier { public static bool IsFullyInvulnerable(WearNTear wnt) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)wnt == (Object)null) { return false; } if (IsZeroOrIgnored(wnt.m_damages.m_blunt) && IsZeroOrIgnored(wnt.m_damages.m_slash) && IsZeroOrIgnored(wnt.m_damages.m_pierce) && IsZeroOrIgnored(wnt.m_damages.m_chop) && IsZeroOrIgnored(wnt.m_damages.m_pickaxe) && IsZeroOrIgnored(wnt.m_damages.m_fire) && IsZeroOrIgnored(wnt.m_damages.m_frost) && IsZeroOrIgnored(wnt.m_damages.m_lightning) && IsZeroOrIgnored(wnt.m_damages.m_poison)) { return IsZeroOrIgnored(wnt.m_damages.m_spirit); } return false; } private static bool IsZeroOrIgnored(DamageModifier mod) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)mod != 3) { return (int)mod == 4; } return true; } } [HarmonyPatch] public static class WearNTearClientSupportPatches { private static readonly FieldRef _supportField = AccessTools.FieldRefAccess("m_support"); [HarmonyReversePatch(/*Could not decode attribute arguments.*/)] [HarmonyPatch(typeof(WearNTear), "GetMaxSupport")] public static float CallGetMaxSupport(WearNTear self) { throw new NotImplementedException("Harmony reverse patch failed for WearNTear.GetMaxSupport"); } [HarmonyPatch(typeof(WearNTear), "UpdateSupport")] [HarmonyPrefix] public static bool UpdateSupport_Prefix(WearNTear __instance) { ConfigEntry configEnableInvulnerableSupportSkip = FiresGhettoNetworkMod.ConfigEnableInvulnerableSupportSkip; if (configEnableInvulnerableSupportSkip == null || !configEnableInvulnerableSupportSkip.Value) { return true; } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated()) { return true; } if (!WearNTearClassifier.IsFullyInvulnerable(__instance)) { return true; } try { float num = CallGetMaxSupport(__instance); _supportField.Invoke(__instance) = num; ZNetView nview = __instance.m_nview; if ((Object)(object)nview != (Object)null && nview.IsValid() && nview.IsOwner()) { nview.GetZDO().Set(ZDOVars.s_support, num); } } catch (Exception ex) { LoggerOptions.LogWarning("[WNT-Support] Skip threw, falling back to vanilla: " + ex.Message); return true; } return false; } } [HarmonyPatch] public static class WearNTearServerPatches { [HarmonyPatch(typeof(WearNTear), "UpdateWear")] [HarmonyPrefix] public static bool UpdateWear_Prefix(WearNTear __instance) { if (!FiresGhettoNetworkMod.ConfigEnableWNTServerOptimization.Value) { return true; } if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsDedicated()) { return true; } ZNetView nview = __instance.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner()) { return true; } if (WearNTearClassifier.IsFullyInvulnerable(__instance)) { return false; } if (__instance.m_createTime >= 0f && Time.time - __instance.m_createTime <= 30f) { return true; } if (__instance.m_inAshlands || __instance.m_rainWet) { return true; } float health = __instance.m_health; if (nview.GetZDO().GetFloat(ZDOVars.s_health, health) < health) { return true; } return false; } } [HarmonyPatch] public static class ZDODeltaPatches { private sealed class ZDOSnapshot { public readonly Dictionary floats = new Dictionary(); public readonly Dictionary vec3s = new Dictionary(); public readonly Dictionary quats = new Dictionary(); public readonly Dictionary ints = new Dictionary(); public readonly Dictionary longs = new Dictionary(); public uint dataRevision; public float lastKeyframeTime; } private const float MaxDeltaWindowSec = 5f; private static readonly Dictionary> _peerSnapshots = new Dictionary>(); private static long _currentPeerUID; private static bool _contextActive; private static bool _pendingKeyframe; private static bool s_yieldToForeignSerializer; private static bool s_foreignScanDone; [HarmonyPatch(typeof(ZNet), "Start")] [HarmonyPostfix] public static void ZNet_Start_DetectForeignSerializer() { if (s_foreignScanDone) { return; } s_foreignScanDone = true; try { MethodInfo methodInfo = AccessTools.Method(typeof(ZDO), "Serialize", (Type[])null, (Type[])null); if (methodInfo == null) { return; } Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo); if (patchInfo?.Prefixes == null) { return; } foreach (Patch prefix in patchInfo.Prefixes) { if (!(prefix.owner == "com.Fire.FiresGhettoNetworkMod") && !(prefix.PatchMethod == null) && !(prefix.PatchMethod.ReturnType != typeof(bool))) { s_yieldToForeignSerializer = true; LoggerOptions.LogWarning("[ZDODelta] '" + prefix.owner + "' already replaces ZDO.Serialize (" + prefix.PatchMethod.DeclaringType?.FullName + "." + prefix.PatchMethod.Name + "). FGN ZDO delta compression DISABLED to avoid double-writing the package and corrupting the wire — the other mod's serialization optimization stays in effect."); return; } } LoggerOptions.LogInfo("[ZDODelta] No foreign ZDO.Serialize replacer detected — delta compression active."); } catch (Exception ex) { s_yieldToForeignSerializer = true; LoggerOptions.LogWarning("[ZDODelta] Foreign-serializer scan failed (" + ex.Message + "); delta disabled as a precaution."); } } [HarmonyPatch(typeof(ZDOMan), "SendZDOs")] [HarmonyPrefix] public static void SendZDOs_Prefix(ZDOPeer peer, bool flush) { if (FiresGhettoNetworkMod.ConfigEnableZDODelta == null || !FiresGhettoNetworkMod.ConfigEnableZDODelta.Value || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsDedicated()) { _contextActive = false; return; } _currentPeerUID = peer.m_peer.m_uid; _contextActive = true; } [HarmonyPatch(typeof(ZDOMan), "SendZDOs")] [HarmonyPostfix] public static void SendZDOs_Postfix(ZDOPeer peer, bool flush) { _contextActive = false; } [HarmonyPatch(typeof(ZDO), "Serialize")] [HarmonyPrefix] public static bool ZDO_Serialize_Prefix(ZDO __instance, ZPackage pkg) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) if (s_yieldToForeignSerializer) { return true; } if (!_contextActive) { return true; } if (!_peerSnapshots.TryGetValue(_currentPeerUID, out var value)) { _pendingKeyframe = true; return true; } if (!value.TryGetValue(__instance.m_uid, out var value2)) { _pendingKeyframe = true; return true; } if (Time.realtimeSinceStartup - value2.lastKeyframeTime > 5f) { _pendingKeyframe = true; return true; } _pendingKeyframe = false; WriteDelta(__instance, pkg, value2); return false; } [HarmonyPatch(typeof(ZDO), "Serialize")] [HarmonyPostfix] public static void ZDO_Serialize_Postfix(ZDO __instance) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (!s_yieldToForeignSerializer && _contextActive) { if (!_peerSnapshots.TryGetValue(_currentPeerUID, out var value)) { value = new Dictionary(); _peerSnapshots[_currentPeerUID] = value; } if (!value.TryGetValue(__instance.m_uid, out var value2)) { value2 = new ZDOSnapshot(); value[__instance.m_uid] = value2; } CaptureSnapshot(__instance, value2); value2.dataRevision = __instance.DataRevision; if (_pendingKeyframe) { value2.lastKeyframeTime = Time.realtimeSinceStartup; _pendingKeyframe = false; } } } [HarmonyPatch(typeof(ZDOMan), "RemovePeer")] [HarmonyPostfix] public static void RemovePeer_Postfix(ZNetPeer netPeer) { if (netPeer != null) { _peerSnapshots.Remove(netPeer.m_uid); } } private static void WriteDelta(ZDO zdo, ZPackage pkg, ZDOSnapshot snap) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Invalid comparison between Unknown and I4 //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Expected I4, but got Unknown //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Expected I4, but got Unknown //IL_01f6: Unknown result type (might be due to invalid IL or missing references) List> floats = ZDOExtraData.GetFloats(zdo.m_uid); List> vec3s = ZDOExtraData.GetVec3s(zdo.m_uid); List> quaternions = ZDOExtraData.GetQuaternions(zdo.m_uid); List> ints = ZDOExtraData.GetInts(zdo.m_uid); List> longs = ZDOExtraData.GetLongs(zdo.m_uid); List> strings = ZDOExtraData.GetStrings(zdo.m_uid); List> byteArrays = ZDOExtraData.GetByteArrays(zdo.m_uid); ZDOConnection connection = ZDOExtraData.GetConnection(zdo.m_uid); List> list = DiffFloats(floats, snap.floats); List> list2 = DiffVec3s(vec3s, snap.vec3s); List> list3 = DiffQuats(quaternions, snap.quats); List> list4 = DiffInts(ints, snap.ints); List> list5 = DiffLongs(longs, snap.longs); bool flag = connection != null && (int)connection.m_type > 0; Quaternion val = zdo.GetRotation(); Vector3 eulerAngles = ((Quaternion)(ref val)).eulerAngles; val = Quaternion.identity; bool flag2 = eulerAngles != ((Quaternion)(ref val)).eulerAngles; ushort num = 0; if (flag) { num |= 1; } if (list.Count > 0) { num |= 2; } if (list2.Count > 0) { num |= 4; } if (list3.Count > 0) { num |= 8; } if (list4.Count > 0) { num |= 0x10; } if (list5.Count > 0) { num |= 0x20; } if (strings.Count > 0) { num |= 0x40; } if (byteArrays.Count > 0) { num |= 0x80; } int num2 = num; if (zdo.Persistent) { num2 |= 0x100; } if (zdo.Distant) { num2 |= 0x200; } num2 |= zdo.Type << 10; if (flag2) { num2 |= 0x1000; } ushort num3 = (ushort)num2; pkg.Write(num3); pkg.Write(zdo.GetPrefab()); if (flag2) { pkg.Write(eulerAngles); } if ((num3 & 0xFF) != 0) { if (flag) { pkg.Write((byte)(int)connection.m_type); pkg.Write(connection.m_target); } WriteFloatBlock(pkg, list); WriteVec3Block(pkg, list2); WriteQuatBlock(pkg, list3); WriteIntBlock(pkg, list4); WriteLongBlock(pkg, list5); WriteStringBlock(pkg, strings); WriteByteBlock(pkg, byteArrays); } } private static List> DiffFloats(List> current, Dictionary snap) { List> list = new List>(); foreach (KeyValuePair item in current) { if (!snap.TryGetValue(item.Key, out var value) || value != item.Value) { list.Add(item); } } return list; } private static List> DiffVec3s(List> current, Dictionary snap) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) List> list = new List>(); foreach (KeyValuePair item in current) { if (!snap.TryGetValue(item.Key, out var value) || value != item.Value) { list.Add(item); } } return list; } private static List> DiffQuats(List> current, Dictionary snap) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) List> list = new List>(); foreach (KeyValuePair item in current) { if (!snap.TryGetValue(item.Key, out var value) || value != item.Value) { list.Add(item); } } return list; } private static List> DiffInts(List> current, Dictionary snap) { List> list = new List>(); foreach (KeyValuePair item in current) { if (!snap.TryGetValue(item.Key, out var value) || value != item.Value) { list.Add(item); } } return list; } private static List> DiffLongs(List> current, Dictionary snap) { List> list = new List>(); foreach (KeyValuePair item in current) { if (!snap.TryGetValue(item.Key, out var value) || value != item.Value) { list.Add(item); } } return list; } private static void WriteFloatBlock(ZPackage pkg, List> list) { if (list.Count == 0) { return; } pkg.Write((byte)list.Count); foreach (KeyValuePair item in list) { pkg.Write(item.Key); pkg.Write(item.Value); } } private static void WriteVec3Block(ZPackage pkg, List> list) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (list.Count == 0) { return; } pkg.Write((byte)list.Count); foreach (KeyValuePair item in list) { pkg.Write(item.Key); pkg.Write(item.Value); } } private static void WriteQuatBlock(ZPackage pkg, List> list) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (list.Count == 0) { return; } pkg.Write((byte)list.Count); foreach (KeyValuePair item in list) { pkg.Write(item.Key); pkg.Write(item.Value); } } private static void WriteIntBlock(ZPackage pkg, List> list) { if (list.Count == 0) { return; } pkg.Write((byte)list.Count); foreach (KeyValuePair item in list) { pkg.Write(item.Key); pkg.Write(item.Value); } } private static void WriteLongBlock(ZPackage pkg, List> list) { if (list.Count == 0) { return; } pkg.Write((byte)list.Count); foreach (KeyValuePair item in list) { pkg.Write(item.Key); pkg.Write(item.Value); } } private static void WriteStringBlock(ZPackage pkg, List> list) { if (list.Count == 0) { return; } pkg.Write((byte)list.Count); foreach (KeyValuePair item in list) { pkg.Write(item.Key); pkg.Write(item.Value); } } private static void WriteByteBlock(ZPackage pkg, List> list) { if (list.Count == 0) { return; } pkg.Write((byte)list.Count); foreach (KeyValuePair item in list) { pkg.Write(item.Key); pkg.Write(item.Value); } } private static void CaptureSnapshot(ZDO zdo, ZDOSnapshot snap) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) snap.floats.Clear(); snap.vec3s.Clear(); snap.quats.Clear(); snap.ints.Clear(); snap.longs.Clear(); foreach (KeyValuePair @float in ZDOExtraData.GetFloats(zdo.m_uid)) { snap.floats[@float.Key] = @float.Value; } foreach (KeyValuePair vec in ZDOExtraData.GetVec3s(zdo.m_uid)) { snap.vec3s[vec.Key] = vec.Value; } foreach (KeyValuePair quaternion in ZDOExtraData.GetQuaternions(zdo.m_uid)) { snap.quats[quaternion.Key] = quaternion.Value; } foreach (KeyValuePair @int in ZDOExtraData.GetInts(zdo.m_uid)) { snap.ints[@int.Key] = @int.Value; } foreach (KeyValuePair @long in ZDOExtraData.GetLongs(zdo.m_uid)) { snap.longs[@long.Key] = @long.Value; } } } [HarmonyPatch] public static class ZDOMemoryManager { public static ConfigEntry ConfigMaxZDOs; private static float startupTimer; private static bool startupComplete; private static bool warningShown; private const float STARTUP_GRACE_PERIOD = 600f; public static void Init(ConfigFile config) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown ConfigMaxZDOs = config.Bind("12 - Advanced", "Max Active ZDOs", 10000000, new ConfigDescription("Force ZDO cleanup if active ZDOs exceed this after startup (0 = disabled).\nStartup grace period (10 min) prevents spam during world load.\nThis feature is CLIENT-ONLY and will not run on dedicated servers.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 2000000), Array.Empty())); } [HarmonyPatch(typeof(ZDOMan), "Update")] [HarmonyPostfix] private static void CleanupIfTooBig(ZDOMan __instance, float dt) { if ((Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer()) || ConfigMaxZDOs.Value <= 0) { return; } if (!startupComplete) { startupTimer += dt; if (startupTimer >= 600f) { startupComplete = true; LoggerOptions.LogInfo("ZDO cleanup grace period ended — normal monitoring enabled."); } return; } FieldInfo fieldInfo = AccessTools.Field(typeof(ZDOMan), "m_objectsByID"); if (fieldInfo == null) { return; } Dictionary dictionary = (Dictionary)fieldInfo.GetValue(__instance); if (dictionary != null && dictionary.Count > ConfigMaxZDOs.Value) { if (!warningShown) { LoggerOptions.LogWarning($"ZDO pool too big ({dictionary.Count} > {ConfigMaxZDOs.Value}) — forcing cleanup..."); LoggerOptions.LogWarning("You have been Exploring a LOT. You should log out to free up RAM."); warningShown = true; } int count = dictionary.Count; AccessTools.Method(typeof(ZDOMan), "RemoveOrphanNonPersistentZDOS", (Type[])null, (Type[])null).Invoke(__instance, null); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); dictionary = (Dictionary)fieldInfo.GetValue(__instance); LoggerOptions.LogInfo($"Cleanup complete: {count} → {dictionary?.Count ?? 0} ZDOs"); } } } [HarmonyPatch] public static class ZDOThrottlingPatches { private const float DISTANT_PENALTY = 500f; private static readonly int PlayerPrefabHash = StringExtensionMethods.GetStableHashCode("Player"); [HarmonyPatch(typeof(ZDOMan), "ServerSortSendZDOS")] [HarmonyPostfix] public static void ServerSortSendZDOS_Postfix(List objects, Vector3 refPos, ZDOPeer peer) { //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsDedicated() || FiresGhettoNetworkMod.ConfigEnableZDOThrottling == null) { return; } bool value = FiresGhettoNetworkMod.ConfigEnableZDOThrottling.Value; float num = 0f; if (value) { float value2 = FiresGhettoNetworkMod.ConfigZDOThrottleDistance.Value; num = value2 * value2; } bool flag = PlayerPositionSyncPatches.ConfigEnablePlayerPositionBoost != null && PlayerPositionSyncPatches.ConfigEnablePlayerPositionBoost.Value; float num2 = (flag ? Mathf.Max(1f, PlayerPositionSyncPatches.ConfigPlayerPositionUpdateMultiplier.Value) : 1f); if ((!value && !flag) || ((FiresGhettoNetworkMod.ConfigEnableAdaptiveThrottling == null || FiresGhettoNetworkMod.ConfigEnableAdaptiveThrottling.Value) && !SendCongestion.IsPeerCongested(peer))) { return; } bool flag2 = false; foreach (ZDO @object in objects) { if (flag && IsPlayerZDO(@object)) { if (peer != null && peer.m_zdos.ContainsKey(@object.m_uid)) { @object.m_tempSortValue = Mathf.Max(0f, @object.m_tempSortValue / num2); flag2 = true; } } else if (value && (int)@object.Type == 0) { Vector3 position = @object.GetPosition(); float num3 = position.x - refPos.x; float num4 = position.z - refPos.z; if (num3 * num3 + num4 * num4 > num) { @object.m_tempSortValue += 500f; flag2 = true; } } } if (flag2) { objects.Sort((ZDO x, ZDO y) => x.m_tempSortValue.CompareTo(y.m_tempSortValue)); } } private static bool IsPlayerZDO(ZDO zdo) { if (zdo != null) { return zdo.m_prefab == PlayerPrefabHash; } return false; } } public static class FiresLogger { private static readonly FiresLog Log = new FiresLog("FiresGhettoNetworkMod", () => FiresGhettoNetworkMod.ConfigLogLevel != null && FiresGhettoNetworkMod.ConfigLogLevel.Value == LogLevel.Info); public static bool VerboseEnabled => Log.VerboseEnabled; public static void LogInfo(string message) { Log.Info(message); } public static void LogVerbose(string message) { Log.Verbose(message); } public static void LogWarning(string message) { Log.Warning(message); } public static void LogError(string message) { Log.Error(message); } } [HarmonyPatch] internal static class FiresLogColorPatch { private const ConsoleColor DefaultColor = ConsoleColor.Magenta; private static readonly (string keyword, ConsoleColor color)[] s_classColorRules = new(string, ConsoleColor)[35] { ("RpcRouter", ConsoleColor.Blue), ("RpcHandler", ConsoleColor.Blue), ("Rpc", ConsoleColor.Blue), ("Network", ConsoleColor.Blue), ("Compression", ConsoleColor.Blue), ("ZDO", ConsoleColor.DarkMagenta), ("BigZdo", ConsoleColor.DarkMagenta), ("Delta", ConsoleColor.DarkMagenta), ("Memory", ConsoleColor.DarkMagenta), ("ServerOwnership", ConsoleColor.DarkGreen), ("ServerAuthority", ConsoleColor.DarkGreen), ("ServerStability", ConsoleColor.DarkGreen), ("Server", ConsoleColor.DarkGreen), ("AutoTune", ConsoleColor.DarkYellow), ("Tune", ConsoleColor.DarkYellow), ("Probe", ConsoleColor.DarkYellow), ("Tier", ConsoleColor.DarkYellow), ("ZoneLoad", ConsoleColor.DarkCyan), ("Throttle", ConsoleColor.DarkCyan), ("Cleanup", ConsoleColor.DarkCyan), ("AILOD", ConsoleColor.DarkCyan), ("MonsterAi", ConsoleColor.DarkCyan), ("Zone", ConsoleColor.DarkCyan), ("BulkTransfer", ConsoleColor.Yellow), ("Queue", ConsoleColor.Yellow), ("Ship", ConsoleColor.Cyan), ("Vehicle", ConsoleColor.Cyan), ("WearNTear", ConsoleColor.DarkRed), ("Wnt", ConsoleColor.DarkRed), ("Diagnostic", ConsoleColor.DarkGray), ("Heartbeat", ConsoleColor.DarkGray), ("Status", ConsoleColor.DarkGray), ("PatchVerify", ConsoleColor.DarkGray), ("LoadSummary", ConsoleColor.Cyan), ("Banner", ConsoleColor.Cyan) }; private static readonly string[] s_ourModTags = new string[6] { "[FiresGhettoNetworkMod", "[FiresGhetto", "[FiresAdminTerrain", "[FiresAdminPrefabs", "[FiresNPCs", "[VerdantsAscent" }; private static readonly Regex s_classNameRx = new Regex("^\\s*\\[(?:Fires[A-Za-z0-9_:]*|VerdantsAscent[A-Za-z0-9_:]*)\\]\\s*\\[([^\\]]+)\\]", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static bool s_reflectionResolved; private static Func s_consoleStreamGetter; private static Action s_setConsoleColor; private const string OwnerKey = "FiresColorPatch.Owner"; private const string MyOwnerName = "FiresGhettoNetworkMod"; private static void EnsureReflection() { if (s_reflectionResolved) { return; } s_reflectionResolved = true; try { Type type = typeof(ConsoleLogListener).Assembly.GetType("BepInEx.ConsoleManager", throwOnError: false); if (type == null) { return; } PropertyInfo property = type.GetProperty("ConsoleStream", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { MethodInfo getMethod = property.GetGetMethod(nonPublic: true); if (getMethod != null) { s_consoleStreamGetter = (Func)Delegate.CreateDelegate(typeof(Func), getMethod); } } MethodInfo method = type.GetMethod("SetConsoleColor", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(ConsoleColor) }, null); if (method != null) { s_setConsoleColor = (Action)Delegate.CreateDelegate(typeof(Action), method); } } catch { } } [HarmonyPatch(typeof(ConsoleLogListener), "LogEvent")] [HarmonyPrefix] private static bool LogEvent_Prefix(LogEventArgs eventArgs) { //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Invalid comparison between Unknown and I4 //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Invalid comparison between Unknown and I4 //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Invalid comparison between Unknown and I4 try { if (eventArgs == null) { return true; } string text = eventArgs.Data?.ToString(); if (string.IsNullOrEmpty(text)) { return true; } string text2 = AppDomain.CurrentDomain.GetData("FiresColorPatch.Owner") as string; if (text2 == null) { AppDomain.CurrentDomain.SetData("FiresColorPatch.Owner", "FiresGhettoNetworkMod"); text2 = "FiresGhettoNetworkMod"; } if (text2 != "FiresGhettoNetworkMod") { return true; } bool flag = false; for (int i = 0; i < s_ourModTags.Length; i++) { if (text.IndexOf(s_ourModTags[i], StringComparison.Ordinal) >= 0) { flag = true; break; } } if (!flag) { return true; } ConsoleColor obj = ConsoleColor.Magenta; Match match = s_classNameRx.Match(text); if (match.Success) { string value = match.Groups[1].Value; for (int j = 0; j < s_classColorRules.Length; j++) { if (value.IndexOf(s_classColorRules[j].keyword, StringComparison.OrdinalIgnoreCase) >= 0) { obj = s_classColorRules[j].color; break; } } } if ((int)eventArgs.Level == 2 || (int)eventArgs.Level == 1 || (int)eventArgs.Level == 4) { return true; } EnsureReflection(); if (s_consoleStreamGetter == null || s_setConsoleColor == null) { return true; } if (!(s_consoleStreamGetter() is TextWriter textWriter)) { return true; } try { s_setConsoleColor(obj); textWriter.Write(eventArgs.ToStringLine()); } finally { s_setConsoleColor(ConsoleColor.Gray); } return false; } catch { return true; } } public static void Install(Harmony harmony) { try { harmony.PatchAll(typeof(FiresLogColorPatch)); } catch (Exception ex) { Debug.LogWarning((object)("[FiresGhettoNetworkMod] FiresLogColorPatch install failed: " + ex.Message)); } } } public static class VAGhettoBanner { private readonly struct Segment { public readonly string Text; public readonly ConsoleColor Color; public Segment(string text, ConsoleColor color) { Text = text; Color = color; } } private static bool s_reflectionResolved; private static Func s_consoleStreamGetter; private static Action s_setConsoleColor; private const ConsoleColor Spark = ConsoleColor.Yellow; private const ConsoleColor SparkHi = ConsoleColor.White; private const ConsoleColor Tower = ConsoleColor.DarkGray; private const ConsoleColor TowerHi = ConsoleColor.Gray; private const ConsoleColor Signal = ConsoleColor.Cyan; private const ConsoleColor SignalDim = ConsoleColor.DarkCyan; private const ConsoleColor Brand = ConsoleColor.Cyan; private const ConsoleColor Tagline = ConsoleColor.Magenta; private const ConsoleColor Bar = ConsoleColor.Green; private const ConsoleColor BarMid = ConsoleColor.DarkYellow; private const ConsoleColor BarEmpty = ConsoleColor.DarkGray; private static readonly Segment[][] s_compactLines = new Segment[12][] { new Segment[1] { new Segment(" ⚡", ConsoleColor.White) }, new Segment[1] { new Segment(" ⚡ ⚡ ⚡", ConsoleColor.Yellow) }, new Segment[1] { new Segment(" ╲│╱", ConsoleColor.Gray) }, new Segment[1] { new Segment(" │", ConsoleColor.DarkGray) }, new Segment[1] { new Segment(" │", ConsoleColor.DarkGray) }, new Segment[1] { new Segment(" ╔══════════════════════════╗", ConsoleColor.DarkGray) }, new Segment[7] { new Segment(" ║ ", ConsoleColor.DarkGray), new Segment("[", ConsoleColor.Gray), new Segment("█████████████", ConsoleColor.Green), new Segment("███", ConsoleColor.DarkYellow), new Segment("░░", ConsoleColor.DarkGray), new Segment("]", ConsoleColor.Gray), new Segment(" ║", ConsoleColor.DarkGray) }, new Segment[3] { new Segment(" ║ ", ConsoleColor.DarkGray), new Segment("\ud83d\udce1 SIGNAL LOCKED", ConsoleColor.Cyan), new Segment(" ║", ConsoleColor.DarkGray) }, new Segment[1] { new Segment(" ╚══════════════════════════╝", ConsoleColor.DarkGray) }, new Segment[1] { new Segment(" GHETTO NETWORKING", ConsoleColor.Cyan) }, new Segment[0], new Segment[1] { new Segment(" Fires Ghetto Networking Loaded", ConsoleColor.Magenta) } }; private static readonly (string text, ConsoleColor color)[] s_bigLines = new(string, ConsoleColor)[83] { ("", ConsoleColor.Gray), ("%%%%%%%%%@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%%%%%%%%%%%%%@", ConsoleColor.Cyan), ("%%%%%%@%%%%%%%%%%%%%%%@@@%%%%%@@@%@%%%%%%%%%%%%%%%%@%%%%%%%%%%%@@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@", ConsoleColor.DarkCyan), ("%%%%%%%%%%%%%%%%%%%%%%@@@%%%%%@%%@@%@%%%%%%%%%%@%%%@%%%%%%@%%@@@%%%%%@@@%%%%%%%%%%%%%%%%%%%@@%%%@@", ConsoleColor.Cyan), ("%%%%%%%%%@%%%%%%@%%%%@@@%%@%@%%%%%%%%%%%@%%%%%%%%@%%%%%%%%%%%@@@%%%%%%%%%%%%%%%%%%%%%%%@%%%%%@%%%%%%", ConsoleColor.DarkCyan), ("%%%%%%%@%%%%%%%%%%%%%%@@%%%%@%%%%%@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%@%%%%%%%%%%@@%%", ConsoleColor.Blue), ("%%%%%%%%%%%%%%%%%%%%%%@@%%@@%%%@%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%%%%%%%%%@@%%%%%%%%%@@@@%%%@%%", ConsoleColor.DarkBlue), ("@%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%%%%%%%%%@%%%%%%%%%%%%%%%@%@%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%@@@%%%%%%", ConsoleColor.DarkCyan), ("@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@@%%%%%%%@%%%%%%%%%%%%%%%%%%%%%%%%%%%@@@%%%%%%%%%%%%%%%%%%%%%%%%%@@@", ConsoleColor.Cyan), ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%%%%%%%%%%%%%%%%@%%%%%@@@", ConsoleColor.Yellow), ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@@%%%@", ConsoleColor.White), ("%%%%@@%%%%%%%%%%%@@%%%%%%%%%%%%%%%%%@%%%%%%%@%%%%%%%%%%%%%@%%%%%%%%%%%%%%%%%%%%%%%%@@@%@@@@%@@%%%@%@", ConsoleColor.DarkGray), ("%@%@@@@@%@@%%%%%%%%%*@@%%%%%%%%%%%@@%%%%%%@@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*%%%@@@%%@@%@@%%%%%%%%", ConsoleColor.Gray), ("%%%%%@@@@@@@@%%%%@%%%*#%%%%%%%%%%%%=*@%%%@@@@%%%%%%%%%%%%%%%%%*=%@%%%%%%%%%%**%@%@@@@%%@%%%%%%%%%%%%", ConsoleColor.DarkGray), ("%%%%%%@@@%%%%%%%%%%%@@#-%@%%%@%%%@@%+#%%%%@@%%%%%%%%%@@@%%%%%#+%%%%%%%%%%@#-%@%%%%%%%%%%%%%%%%%%%@%%", ConsoleColor.Yellow), ("@@@%@@%%%%%%%%%%%%%%@@@@-=%@%%%%%%%%%*%%@@%%%%%@%-%%%@@@%@%%%#@%%%%%%%%@%--%%@%%%%@%%%%%%%%%@@%%%@%%", ConsoleColor.DarkYellow), ("%@@%@@@%%%%%%%%%%%%%%@@@@+.+%%%%%%%%%%%%%%%%%%%%%=%@%%%%@@%%%%%%%%%%%%%+.+%%%%%%%@%%%%%%%%%%%%%%%%%%", ConsoleColor.Yellow), ("%%%@@@%%%%%%%%%%%%%%@%%%%%*.:#@%%%%%%%%%%%%%%%%%%=%@%%%%%%%%%%%%%%%@@*.:#%@%%%%%%%@@@%@%@%%%%%%%%%%%", ConsoleColor.White), ("%@@@%%%%%%%%%%%@%@@%%%%%%%%%:.-%@%==%%%%%%%%%%%%%=%%%%%@%%%%%@%-=%%#:.-%%%@%%%%%%%%%%%%%@@%%%%%%%@%@", ConsoleColor.Cyan), ("%@@@@@%@@%%%%%%%@@@%%%%@@%-:=-..=*..-%%@%##@%%%%%=%%%%%%#%%%%%-..#=..=-.-%@@%%%%@%%@@%%%%%%%%%@@%%%%", ConsoleColor.DarkCyan), ("%@@@%%%@%%%@@@%@@@@%%%%%@@@*.........-%%%%#%@%%%%=@%%%@%%@%%%:.........#@@@@%%%%%%%%%%@@@%%%%@@@%%%%", ConsoleColor.DarkGray), ("%%%%%%%%%%%@%%@@@%%%%%@%%@%%%=........:%%%%%%@%%@*@%%%%%@@@#:........=%%@%%%%%%%%%%%%@@@@%%%%%%%%%%%", ConsoleColor.Gray), ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@%:...*=..:#@%%%%%%%#@%%%%%%%#:..+*...:%%@@%%%%%%%%%%%%%%@@@%%%%@%%%%%@", ConsoleColor.DarkGray), ("%%%%%%@%%%%%%@%%%%%%%%%%@@@@@@@@+.-%%%+..#%%%%%%%%@%%%@@%*.:+%%%-.*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", ConsoleColor.Gray), ("%%%%%%%%%%%%%%%%%%%%%@@%%%@%%@@@@%#@%%%%*:*%%%%%%%%%%@@@*:*%%%%@#%%%%%%%%%%@%%%%%%%%@@%%%%%%%%%%%%%%", ConsoleColor.DarkCyan), ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@@@@%%%%%%%@@**%%%%%%%%%%%*#%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", ConsoleColor.Cyan), ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%@%=-::::=%%%@%%%%@%%%%%%%%%%%%%%%%%%%%%@%%@@@%%%%@@%%%%%", ConsoleColor.DarkGray), ("@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%%%%%%%-:-%%%%%-:=%%%%%%@@%%%%@%%%%%%%%%%@%%%%@@@%@%%%%%@%%%%%%", ConsoleColor.Gray), ("%%%%%%%%%%%%%%%@%%%%%%%%@%%%%%%#*-=%%%%%%%%%:-%%+++%%-:%%%%%%%%%=-*#%%@%%%%%%%%%%%%%%%@%%%%%%@%%%%%%", ConsoleColor.DarkGray), ("%%%%%%%@@@@%%%%%%%%%%%%%@%#=:....-%%%%%@@%%%:=%%...%%=:%@%%%%%%%%-....:=#%%%%%%%%%%%%%%%%%%%%%%%%%%%", ConsoleColor.Cyan), ("%%%%%%%%%%%%%%%%%%@%#+:..........:::..:+#%%%:=%#:::%%=:%%%#+:..:::.........:-*#%%%%%%%%%%%%@%%%%%%%%", ConsoleColor.DarkCyan), ("%%%%%%%%%%%%%%%%%#####%%%%%%*.....-+%%%%%%%%:=%#:::%%=:%%%%@@%%+-.....*%%%%%%#####%%%%%%%%%@@@@@%%%%", ConsoleColor.Gray), ("@@@%%%%%%@@@%%%%%%%%%%%%%%@#..-*%@%@%%%%%%%%:=%#.:.%%=:%%%%%%%%%%%%*-..#%%%%%@@%%%@%%%%%%%%%%%%@%@%%", ConsoleColor.DarkGray), ("%%%%%%%%%%%%%%%%%%%%%%%%%%@#%@@%@%%@@@@%=---:=%#:..%%=:---=%@%%%%%%%%@%#%%%%%%%%%%%%%%%%%%%%%%%%%%%%", ConsoleColor.DarkCyan), ("%%%%%%%%%%%@@%%%%@@@@@%%%%%%%%%%%%%%%%@=:#%%%%%%%%%%%%%%%#:+%%%%%%%%%%%%%@@@%%%%@%%%%%%%%%%%%%%%%%%%", ConsoleColor.Cyan), ("%%%%%%%%%%%%%%%@%%%%%@%%%@@%@%%@%%%%%%%=:#%%***********%%#:+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", ConsoleColor.DarkCyan), ("@@@@@@%%%%%@@%%@%%@@%%%%%@@%=:%@%%%#-#%=:#%%####%%%%###%%#:+%#-#@@%%%:=%%%%%@@%%%%%%%%%%%%%%%%%%%%%@", ConsoleColor.Gray), ("@@%%@@%%%%@@%%@@%%%%%%%%%@*..:%@#-.=%%%=:*##%%%#####%%%##*:+%%%-.-#@%..:#%%%%%%%%%%%%%%%%%%%%%%%%%%%", ConsoleColor.DarkGray), ("%%%%%%%%%%%%%%%%%@@@@@%@%-....=...*%%%+-::::%%+:::::*%#::::-+@%%*...=....-%@%%%%%%%%%%%%%%%%%%@@%%%%", ConsoleColor.DarkCyan), ("@%%%%%%%%%%%%%%%%%@@@@%+........-%%%%*:*%%%%%%%%%%%%%%%%%%%+:*%@%%-........+@@%%%%%%%%%%@%%%%%@@%%%%", ConsoleColor.Cyan), ("%%%%%%%%%%%%%%%%%%%%@#:...:....*@@%%%*:*%%***************%%+:*%%%%%*....:...:#@%%@%%%%%%@%%%%%%%%%%%", ConsoleColor.Gray), ("%%%%%%%@%%%%%%%%%@%%-..:#%=..-%%%%%%%*:*%%***************%%+.*%%%%%@%-..+%*:..-%%%%%%%%%%%%%@%%%%%%%", ConsoleColor.DarkGray), ("%@%%%%%@@@%%%@%%%@+..=%@@%=.*%%%%%%%%*:*%%%%%%%%%%%%%%%%%%%+:*%%%@@%@%*.+@%@%=..*@%%%%%%@%@@@@%%%%%%", ConsoleColor.DarkCyan), ("@@%%%%%@@@%%%%@@%::#%@%%%@*%%%%%%%%%%%-:::%%=.........=%%:::=%%%%@%@@@@%*@%%%%%#:-%@%%%%%%%@%%%@%%%@", ConsoleColor.Cyan), ("@@@%%%%%%%%%%@%==%@@@%%%%%@@%%%%%%%%%%%+:=%%%*..=%-.:*%%%-:*@@@@@%%%%@@@@@@@%%%@@%++%%%%%%%%%%@@%%%%", ConsoleColor.DarkCyan), ("@@@%%%%@@%%%@##%@@@%%%%%%%%%%%%%%%@%%%%::%%=*%%%=.=%%%*=%%::@@@%%%@@%%%%@@@%%%%%%@@@##%%%%%%%%%%%%%%", ConsoleColor.Gray), ("%@%%%%%%@%@%%%%%@@%%%%%%%%%%@@@%%@@%%%+:=%%...:%%%%%:...%%=:+%%%%@@@%%%@@%%%%%%%%@%%%%%%%%%%%%%%%%@%", ConsoleColor.DarkGray), ("%%%%%%%%@@@@@%%%%@%%%%%%%@@%%%%@%%%@%%::%%=..=%%%+%%%=..=%%::%%%%%%@%@@@@%@@%%@@%%%%%%%%%%%%%%%%%%%@", ConsoleColor.DarkCyan), ("%@%%%%%%%%@@%%@@%%@%%%%%%%%%%%@@%%%%%=:+%#.%%%#..:..%%%%.%%=:=%%%%%@@@@%%%@@@@%%%%%%@%%%%%%%%%%%%%%@", ConsoleColor.Cyan), ("@@%%%%%%%%%%%@%@%@@@%%%%%%%%%@@@@%%%%:.%%%%%=.:*%%%+:.+%%%%%::%@%%%%%%%%@@%@%%@@%%%%%%%%%%%%%%%%%%%@", ConsoleColor.DarkCyan), ("@%%%%%%%%%%%%%@%%%@%%%%%%%%@@@%%%%%%-:+%%%#..-%%%%%%%-..%%%%+:-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", ConsoleColor.Cyan), ("%%%%%%%%%%%%%%%@@@%%%%%%%%%%%%%%%%@%:.%%=+%%%=.:#%*..=%%%+=%%.:%%%%%@%%%%%%%%%%%%@@@@%%%%%%%%%%%%%%@", ConsoleColor.DarkCyan), ("%%@@%%%@@%@%@%%%@%%@@%%%%%%%%%%%%%%-:*@#.:.:%%%#:.:#%%#:.:.#%*:-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@", ConsoleColor.Gray), ("@%%%%@@@%%%%%%%%%%%%%%%%%%%%%%%%%%#:.%%-.%%+..+%%%%%=..*%%.-%%.:#%%%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@", ConsoleColor.DarkGray), ("@%%%%%@@%%%%%%@@%%%%%%%%%%%%%%%%%%:.#%#.+%#-.:#%%%%%#:.-%%=.#%*.:%%%%%%%%%%%%%@@%@@@%%%%%%%%@@%%%%%%", ConsoleColor.DarkCyan), ("%%%%%%%%%%%%%%%%%%%%@%%%%%@@@@%%%*:.%%::+..+%%%=.:.=%%%+..+:-%%.:*%%%%%%%@%%%%%%%@@@@%%%@%%%%@%%%%%%", ConsoleColor.Cyan), ("%%%%%%@%%%%%%%%%%%%%%%%%%%%%@%%%%:.*%*..:#%%#:.:*@*:.:#%%#:..*%#.:%@%%%%%%%%@%%%%%@@%%%%%%%%%%@%%@@@", ConsoleColor.DarkCyan), ("%%%%%%%%%%%%%%%%@%%%%%%%%%%%%@%%+::%%.+%%%=.:=%%@@%%%=:.=%%%=:%%::+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@", ConsoleColor.Gray), ("%%%%%%%%%%%%%%@@@%%%%%%%%%%%%%%%:.#%%%%#..:*%%%%%%%%%%%*:.:#%%%%#.:%%%%%%@@%%%%%%%@@@%%%%%@@%%%%%%%%", ConsoleColor.DarkGray), ("%%@@%@%%%%%%%%%@%%%%%%%%%%%%%%%=:-%%%=.:-%%%%%%%%%%%%%%%%%-..=%%%-:=%%%%%%%%%%%%%%%%%@%%%%%@%%%%%@%@", ConsoleColor.DarkCyan), ("%%%%%@@@%%%%%%%%@%%%%%%%%%%%%%%:.*#.::*%@@%%%%%%%%%%%%%%%%%%*:..%*.:%%%%%@%%%%%%%%%%@@%%%%%%%@%%%%%%", ConsoleColor.Cyan), ("%@%%%%@@%%%%%%%%@@%%%%%%%%%%%%-::::=%@%%%%%%%%%%%%%%%%%%%%%%%%%-::::-%%%%%%%%%%%%%%%@%%%%%@@%%%%%%%%", ConsoleColor.DarkCyan), ("%@%%%%%%%%%%%%%@@@%%%%%%%%%%%%@@%@@@@%%%%%%%%%%%%@@%%%%%%%%%%%%%%%%@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", ConsoleColor.Gray), ("%%%%%%%%%%%%%%%@%@%%%%%%@@%%##########%%%%%%%###########%%%####%%%%%%%%%##%%%%%%%%%%%%%@@@%%%%%%%%%%", ConsoleColor.DarkGray), ("%%%%%%%%%%%%%%@%%%%%%%%%@=::::::::::::=%%+::::::::::::::%%=:::::+@%%%%%=::+%%%%%%%%%%%%@@@%%%%%%%%%%", ConsoleColor.Gray), ("%%%%%%%%%%%%%%@@%%%%%%%%*::-%%%%@%%%%%%%#:::%%%%@%%%@%%%%%=::+-:::%%%@%=::*%%%%%%%%%@@%%@%%%%%%%%%%%", ConsoleColor.DarkGray), ("%%%%%%%%@@@%%@@@%%%%%%@%*::-**********%%*::+%%%#********%%=::+@*:::#%%%=::+@%%%%%%%%@@@%%%%%%%%%%%%%", ConsoleColor.DarkCyan), ("%%%%%%%%%%%%%%@%%%%%%%%%+:::::::::::::+%*::+%%%+::::::::@%=::+@%%-::=@@=::+@@@%%%@%%%%%%%%%%%%%%%%@@", ConsoleColor.Cyan), ("%%%%%%%%%%%@%%%%%%%%%%%%+::=@@@%%@%%%%%%*::-%%%%%%%%%:::%@=::+@%%%+:::%=::+@%%%%%%%%%%%%%%%%%%%%%%%%", ConsoleColor.Cyan), ("%%%%%%%%%%%%%%@@%%%%%%@@*::=@%%%%%@%%%%%%:::::------::::%%=::+%%%%%#::::::+%%%%%%%%%%%%%%%@@@%%%%%%@", ConsoleColor.Cyan), ("%%%%%%%%%%%%@@@@@%%%%%%%*::=@%%%%%%%%%%%%%#-:::::::::::*%%+::*%@%%%@%-::::#%%@@%%%%%%%%%%%%%%@%%%%%%", ConsoleColor.Cyan), ("@@%%%%%%%%%%%@@%%%%%%%%%%@@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%%%%%%%%@@@@@@%%%%%%%%%%@%%%@%%%%", ConsoleColor.DarkCyan), ("%%%%%%%%%%%%%%@%%%%%%%%%%%%%%%%%%%%%%%@@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@%%%%%%%%%%@@%%%%%%%", ConsoleColor.DarkCyan), ("%%%%%%%%%%%%%%%@%%%%%%%%%%%%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@%%%%%%%%%%%%%%%@@%@@%%%%%%%%@@%%%%@%", ConsoleColor.DarkGray), ("%%%%%%%%%%%%%%%%%%%%@%%%%%%@@@@@%%%%%%@%@@%%%%@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%%%%%%%%%%%%@@@%%", ConsoleColor.DarkGray), ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@@%%%%@@@@@%%%%%@@%%%%%%%%%%%%%%@@@%%%%%%%%%%@@@%%%%%%%@@@%%%%%%%%%%@%%", ConsoleColor.DarkGray), ("%%%%%%%%%%%%%%%%%@@%%%%%%%%%@%%%%%@@@@@%%%%%%@%%%%%%%%%%%%%%%%%%@@@%%%%%%%%%%%%%%%%%%@@@@@%%%%%%@%%", ConsoleColor.DarkGray), ("%%%@@%%@%%%%@@%%%%@%%%%%@@%%%%%@@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@@@@%%%%%%%%%%%%%%%@@@@%%@@%%%%", ConsoleColor.DarkGray), ("@%%%%%%%%@@@@@@%%%%%@@%%%%%%%%%@%%%@%%%%%%%%@%@%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%%%%%%%@@@%%%%%%%%%%%%%%", ConsoleColor.DarkGray), ("%%%%%%%%%@@@@@%%%%%%%%%%%%%%%%%@@%%%%%@%%%%%@@@%%%%%@@%%%%%%%%%%%%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", ConsoleColor.DarkGray), ("%%%%%%%%%%%%@@@@%%%@%%%@@@@%%@@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%@@%%%%%%%%%%%@@%%%%%%%%%%%%%%@", ConsoleColor.DarkGray), ("%%%%%%%%%%%%@@@@%%%%%%%%%@@@%@@%%%%%%%%%%%%%%%%%%@%%%%%%%%%%%%%%%%%%%%@@%%%%%%%%%%%%@%%%@@%%%%%%%%%@", ConsoleColor.DarkGray), ("", ConsoleColor.Gray) }; public static void Print() { try { Debug.Log((object)"[FiresGhettoNetworkMod] Fires Ghetto Networking Loaded."); } catch { } try { if (!TryWriteSegmented(s_compactLines)) { WritePlainFallbackSegmented(s_compactLines); } } catch { try { WritePlainFallbackSegmented(s_compactLines); } catch { } } } public static void PrintBig() { try { Debug.Log((object)"[FiresGhettoNetworkMod] Fires Ghetto Networking Loading..."); } catch { } try { if (!TryWriteColoredBig()) { WritePlainFallbackBig(); } } catch { try { WritePlainFallbackBig(); } catch { } } } private static bool TryWriteSegmented(Segment[][] lines) { EnsureReflection(); if (s_consoleStreamGetter == null || s_setConsoleColor == null) { return false; } if (!(s_consoleStreamGetter() is TextWriter textWriter)) { return false; } try { s_setConsoleColor(ConsoleColor.Gray); textWriter.WriteLine(); foreach (Segment[] array in lines) { for (int j = 0; j < array.Length; j++) { Segment segment = array[j]; s_setConsoleColor(segment.Color); textWriter.Write(segment.Text); } textWriter.WriteLine(); } } finally { try { s_setConsoleColor(ConsoleColor.Gray); } catch { } } return true; } private static void WritePlainFallbackSegmented(Segment[][] lines) { try { StringBuilder stringBuilder = new StringBuilder(); foreach (Segment[] obj in lines) { stringBuilder.Clear(); Segment[] array = obj; for (int j = 0; j < array.Length; j++) { Segment segment = array[j]; stringBuilder.Append(segment.Text); } Debug.Log((object)$"[FiresGhettoNetworkMod] {stringBuilder}"); } } catch { } } private static bool TryWriteColoredBig() { EnsureReflection(); if (s_consoleStreamGetter == null || s_setConsoleColor == null) { return false; } if (!(s_consoleStreamGetter() is TextWriter textWriter)) { return false; } try { s_setConsoleColor(ConsoleColor.Gray); textWriter.WriteLine(); (string, ConsoleColor)[] array = s_bigLines; for (int i = 0; i < array.Length; i++) { (string, ConsoleColor) tuple = array[i]; s_setConsoleColor(tuple.Item2); textWriter.WriteLine(tuple.Item1); } } finally { try { s_setConsoleColor(ConsoleColor.Gray); } catch { } } return true; } private static void WritePlainFallbackBig() { try { (string, ConsoleColor)[] array = s_bigLines; for (int i = 0; i < array.Length; i++) { (string, ConsoleColor) tuple = array[i]; Debug.Log((object)("[FiresGhettoNetworkMod] " + tuple.Item1)); } } catch { } } private static void EnsureReflection() { if (s_reflectionResolved) { return; } s_reflectionResolved = true; try { Type type = typeof(ConsoleLogListener).Assembly.GetType("BepInEx.ConsoleManager", throwOnError: false); if (type == null) { return; } PropertyInfo property = type.GetProperty("ConsoleStream", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { MethodInfo getMethod = property.GetGetMethod(nonPublic: true); if (getMethod != null) { s_consoleStreamGetter = (Func)Delegate.CreateDelegate(typeof(Func), getMethod); } } MethodInfo method = type.GetMethod("SetConsoleColor", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(ConsoleColor) }, null); if (method != null) { s_setConsoleColor = (Action)Delegate.CreateDelegate(typeof(Action), method); } } catch { } } } public static class VAGhettoLoadSummary { private const string Tag = "[LoadSummary]"; private const int InnerWidth = 30; public static bool VerboseEnabled => FiresLogger.VerboseEnabled; public static void EmitRpcRouter(int handlersRegistered, float aoiRadius, bool aoiEnabled) { EmitMiniBox("\ud83d\udce1 RPC ROUTER", new string[2] { $"{handlersRegistered} handlers wired", aoiEnabled ? $"AoI {aoiRadius:F0}m" : "AoI disabled" }); } public static void EmitAutoTune(string mode, string tier) { EmitMiniBox("\ud83d\udd27 AUTOTUNE", new string[2] { "mode: " + mode, "tier: " + tier }); } public static void EmitServerAuthority(string ownership, bool zdoThrottle, bool aiLod, bool wntOpt) { EmitMiniBox("\ud83c\udfdb SERVER AUTH", new string[3] { "own: " + ownership, "zdo throttle: " + (zdoThrottle ? "ON" : "off"), "AILOD: " + (aiLod ? "ON" : "off") + " · WNT: " + (wntOpt ? "ON" : "off") }); } public static void EmitPatches(string side, int classesAttached) { EmitMiniBox("⚙ PATCHES", new string[2] { "side: " + side, $"{classesAttached} patch class(es)" }); } private static void EmitMiniBox(string title, string[] lines) { try { int length = title.Length; int count = Math.Max(0, 30 - length - 5); string text = "╭─── " + title + " " + new string('─', count) + "╮"; string text2 = "╰" + new string('─', 30) + "╯"; Debug.Log((object)("[FiresGhettoNetworkMod] [LoadSummary] " + text)); for (int i = 0; i < lines.Length; i++) { string text3 = lines[i] ?? string.Empty; if (text3.Length > 28) { text3 = text3.Substring(0, 28); } text3 = text3.PadRight(28); Debug.Log((object)("[FiresGhettoNetworkMod] [LoadSummary] │ " + text3 + " │")); } Debug.Log((object)("[FiresGhettoNetworkMod] [LoadSummary] " + text2)); } catch (Exception ex) { try { Debug.Log((object)("[FiresGhettoNetworkMod] [LoadSummary] (summary render failed: " + ex.Message + ") " + title + ": " + string.Join(", ", lines ?? new string[0]))); } catch { } } } } } namespace FiresGhettoNetworkMod.AutoTune { public static class AutoTuneCache { public sealed class CacheEntry { public Tier Tier { get; set; } public int PingMedianMs { get; set; } public DateTime TimestampUtc { get; set; } public string HardwareHash { get; set; } = string.Empty; } private const int FileMagic = 1095649094; private const string FileName = "com.Fire.FiresGhettoNetworkMod_autotune.bin"; public const int SchemaVersion = 2; private static readonly TimeSpan DefaultTtl = TimeSpan.FromDays(7.0); private static Dictionary _entries; private static bool _loaded; private static string CachePath => Path.Combine(Paths.ConfigPath, "com.Fire.FiresGhettoNetworkMod_autotune.bin"); private static void EnsureLoaded() { if (_loaded) { return; } _loaded = true; _entries = new Dictionary(); if (!File.Exists(CachePath)) { return; } try { using FileStream input = File.OpenRead(CachePath); using BinaryReader binaryReader = new BinaryReader(input); int num = binaryReader.ReadInt32(); if (num != 1095649094) { LoggerOptions.LogInfo($"[AutoTune] Cache magic mismatch (0x{num:X8} != 0x{1095649094:X8}) — starting fresh."); return; } int num2 = binaryReader.ReadInt32(); if (num2 != 2) { LoggerOptions.LogInfo($"[AutoTune] Cache schema {num2} (expected {2}) — starting fresh."); return; } int num3 = binaryReader.ReadInt32(); if (num3 < 0 || num3 > 100000) { LoggerOptions.LogWarning($"[AutoTune] Cache entry count {num3} out of range — starting fresh."); return; } for (int i = 0; i < num3; i++) { string key = binaryReader.ReadString(); int tier = binaryReader.ReadInt32(); int pingMedianMs = binaryReader.ReadInt32(); long ticks = binaryReader.ReadInt64(); string text = binaryReader.ReadString(); _entries[key] = new CacheEntry { Tier = (Tier)tier, PingMedianMs = pingMedianMs, TimestampUtc = new DateTime(ticks, DateTimeKind.Utc), HardwareHash = (text ?? string.Empty) }; } } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] Failed to load cache (" + ex.GetType().Name + ": " + ex.Message + "); starting fresh."); _entries = new Dictionary(); } } private static void SaveToDisk() { try { using FileStream output = File.Create(CachePath); using BinaryWriter binaryWriter = new BinaryWriter(output); binaryWriter.Write(1095649094); binaryWriter.Write(2); binaryWriter.Write(_entries.Count); foreach (KeyValuePair entry in _entries) { binaryWriter.Write(entry.Key ?? string.Empty); binaryWriter.Write((int)entry.Value.Tier); binaryWriter.Write(entry.Value.PingMedianMs); binaryWriter.Write(entry.Value.TimestampUtc.Ticks); binaryWriter.Write(entry.Value.HardwareHash ?? string.Empty); } } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] Failed to write cache: " + ex.Message); } } public static CacheEntry TryGet(string serverKey, string currentHwHash, TimeSpan? ttl = null) { if (string.IsNullOrEmpty(serverKey)) { return null; } EnsureLoaded(); if (!_entries.TryGetValue(serverKey, out var value) || value == null) { return null; } TimeSpan timeSpan = ttl ?? DefaultTtl; if (DateTime.UtcNow - value.TimestampUtc > timeSpan) { return null; } if (!string.IsNullOrEmpty(currentHwHash) && !string.IsNullOrEmpty(value.HardwareHash) && value.HardwareHash != currentHwHash) { return null; } return value; } public static void Save(string serverKey, Tier tier, int pingMedianMs, string hwHash) { if (!string.IsNullOrEmpty(serverKey)) { EnsureLoaded(); _entries[serverKey] = new CacheEntry { Tier = tier, PingMedianMs = pingMedianMs, TimestampUtc = DateTime.UtcNow, HardwareHash = (hwHash ?? string.Empty) }; SaveToDisk(); } } public static void Forget(string serverKey) { if (!string.IsNullOrEmpty(serverKey)) { EnsureLoaded(); if (_entries.Remove(serverKey)) { SaveToDisk(); } } } public static void ForgetAll() { EnsureLoaded(); _entries.Clear(); SaveToDisk(); } } public static class AutoTuneConfig { public static ConfigEntry EnableClientAutoTune; public static ConfigEntry EnableServerAutoTune; public static ConfigEntry LogServerSuggestions; public static ConfigEntry RetuneOnEveryLogin; public static ConfigEntry LinkDowngradeCap; public static ConfigEntry ProbeStartDelaySeconds; public static ConfigEntry PlayerArrivalTimeoutSeconds; public static ConfigEntry ProbePingTimeoutSeconds; public static ConfigEntry ProbePingCount; public static ConfigEntry ProbeBandwidthPayloadBytes; public static ConfigEntry ProbePingAbortMs; public static ConfigEntry EnableRollingMonitor; public static ConfigEntry RollingMonitorIntervalMinutes; public static ConfigEntry RollingMonitorWindow; public static void Init(ConfigFile config) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Expected O, but got Unknown //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Expected O, but got Unknown //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Expected O, but got Unknown //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Expected O, but got Unknown EnableClientAutoTune = config.Bind("06 - Auto-Tune", "Enable Client Auto-Tune", true, "Run a brief network/hardware probe on first login to a server and pick a perf tier (LOW/MED/HIGH).\nEffective values shadow the BepInEx config — your manual settings are never overwritten.\nDisable to use only the values set above. CLIENT-SIDE only."); EnableServerAutoTune = config.Bind("06 - Auto-Tune", "Enable Server Auto-Tune", true, "When the mod runs on a dedicated server, score the host's CPU/RAM and APPLY a tier preset to\nZDO throttle / AI LOD / RPC AoI / queue size / extended zone radius / update rate / Steam buffers.\nDefault ON — the tier preset is conservative and the asymmetric send-rate pattern keeps slow\nclients safe (Min stays at the LOW baseline regardless of tier). Disable if you've manually tuned\nevery server knob and want your values used verbatim. SERVER-SIDE only."); LogServerSuggestions = config.Bind("06 - Auto-Tune", "Log Server Suggestions", true, "When on, the server logs its own self-tune tier + recommended values at startup, and periodically\nlogs the median tier reported by connected clients with suggested adjustments. Pure logging —\nno settings are changed. SERVER-SIDE only."); RetuneOnEveryLogin = config.Bind("06 - Auto-Tune", "Retune On Every Login", false, "When on, ignore the cached tier on disk and re-probe every time you join. Costs ~50KB of probe\ntraffic per join but reflects current ISP/route conditions. CLIENT-SIDE only."); LinkDowngradeCap = config.Bind("06 - Auto-Tune", "Link Downgrade Cap", 1, new ConfigDescription("Your machine's measured tier is the CEILING — a great connection can never push you above\nwhat your hardware can actually handle. Only a genuinely BAD connection pulls your tier DOWN,\nand this caps how far. 1 (default) = a bad link drops you one tier (e.g. a strong PC on a\n170ms link lands MED, not LOW). 0 = connection never lowers your tier (machine only). 2 = a\nbad link can drop you two tiers. Okay/medium ping costs nothing either way. CLIENT-SIDE only.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 2), Array.Empty())); ProbeStartDelaySeconds = config.Bind("07 - Auto-Tune - Probe", "Start Delay Seconds", 30f, new ConfigDescription("Wait this long AFTER the player has actually arrived in the world (Game.m_playerInitialSpawn\nfired) before starting the probe. Lets the post-spawn burst — inventory equip, ZDO\nzone-load for the spawn point, post-spawn mod work — subside so we don't sample latency\nwhile Valheim's own arrival traffic is queued ahead of our pings. 30s is the safe default;\na server with heavy mod-driven sync (large worlds, many players) may benefit from 45-60.\nWorst-case modpacks on slow servers may need 75-90s to fully settle. Lower only if you\nknow your fast-load mod handles arrival-burst traffic well.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 90f), Array.Empty())); PlayerArrivalTimeoutSeconds = config.Bind("07 - Auto-Tune - Probe", "Player Arrival Timeout Seconds", 180f, new ConfigDescription("Hard cap on how long to wait for the local player to actually finish spawning into the\nworld before forcing the probe to start anyway. The probe gates on Game.m_playerInitialSpawn\n(the same event that fires the '$text_player_arrived' chat message) so the probe doesn't\nstart while the player is still mid-load — that event normally fires within tens of\nseconds of connect, but heavy modpacks with large worlds + slow disks can take 90s+.\nThis timeout is the safety valve: if the spawn fails entirely we eventually proceed\nanyway rather than leave the client stuck at a degraded default tier forever. 180s\ncovers worst-case modpack loads on slow disks; raise only if you have repro evidence\nthe timeout is firing on a successful spawn.", (AcceptableValueBase)(object)new AcceptableValueRange(30f, 600f), Array.Empty())); ProbePingTimeoutSeconds = config.Bind("07 - Auto-Tune - Probe", "Ping Timeout Seconds", 5f, new ConfigDescription("How long to wait for a single ping echo before giving up. On timeout we default to LOW tier.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 15f), Array.Empty())); ProbePingCount = config.Bind("07 - Auto-Tune - Probe", "Ping Count", 10, new ConfigDescription("Number of pings to send for the latency probe. The probe also fires one untracked\nwarmup ping first (clears Steam TCP slow-start), and drops the single worst sample\nbefore computing stats so transient post-spawn queue contention can't tank the\nresult on a healthy link. 10 is the sweet spot — enough samples for outlier\ntrimming + IQR jitter to be stable; not so many that probe traffic becomes notable.", (AcceptableValueBase)(object)new AcceptableValueRange(5, 20), Array.Empty())); ProbePingAbortMs = config.Bind("07 - Auto-Tune - Probe", "Ping Abort Ms", 2000, new ConfigDescription("If any single ping round-trip exceeds this, we hard-default to LOW tier and skip the\nbandwidth probe entirely — the client is already struggling, no point making them prove\nit twice.", (AcceptableValueBase)(object)new AcceptableValueRange(500, 5000), Array.Empty())); ProbeBandwidthPayloadBytes = config.Bind("07 - Auto-Tune - Probe", "Bandwidth Probe Bytes", 131072, new ConfigDescription("Size of the bandwidth-test payload requested from server. Only runs if the latency probe\nputs the client at MED or HIGH tier — LOW-tier clients skip this entirely.\n\nLarger payloads measure throughput more honestly: a request-response with a small\npayload is dominated by RTT, not actual link capacity. With a 64ms RTT, a 32KB payload\nceilings at ~500 KB/s no matter what the link can deliver. 128KB at the same RTT can\nreport up to ~2 MB/s — the transfer time finally dominates the RTT. Default raised\nfrom 32KB → 128KB on 2026-05-11 because the small-payload probe was systematically\nunder-reporting on healthy links and incorrectly tier-downgrading.\nTradeoff: probe takes ~1.5s longer per session and consumes ~256KB of extra one-time\nbandwidth on the dedicated server.", (AcceptableValueBase)(object)new AcceptableValueRange(8192, 524288), Array.Empty())); EnableRollingMonitor = config.Bind("08 - Auto-Tune - Monitor", "Enable Rolling Monitor", true, "After the initial probe, keep periodically re-probing latency over the session and\nrefine the tier from a rolling average. Catches transient server overload (settling\nto a higher tier once the initial-sync flood ends) and ISP/route weather changes.\nEach re-probe is latency-only — no extra bandwidth probe — so cost is ~5KB per cycle.\nCLIENT-SIDE only."); RollingMonitorIntervalMinutes = config.Bind("08 - Auto-Tune - Monitor", "Re-Probe Interval Minutes", 5f, new ConfigDescription("How often to re-probe latency after the initial probe. Smaller values catch transient\nissues faster but cost more probe traffic; larger values are quieter but slower to react.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 30f), Array.Empty())); RollingMonitorWindow = config.Bind("08 - Auto-Tune - Monitor", "Rolling Window Size", 5, new ConfigDescription("Number of recent re-probe results held in the rolling buffer for averaging. Tier is\nthe most-common tier in the buffer; promotion requires 2 consecutive observations,\ndemotion requires 3 — biased to keep current tier rather than oscillate.", (AcceptableValueBase)(object)new AcceptableValueRange(3, 10), Array.Empty())); } } public static class AutoTuneProbe { private struct BwResult { public int Bytes; public long Ms; } public const string RPC_PING = "FiresGhetto.AutoTune.Ping"; public const string RPC_PONG = "FiresGhetto.AutoTune.Pong"; public const string RPC_BW_REQ = "FiresGhetto.AutoTune.BwReq"; public const string RPC_BW_RESP = "FiresGhetto.AutoTune.BwResp"; public const string RPC_TIER_REPORT = "FiresGhetto.AutoTune.TierReport"; private static readonly Dictionary _pingInflight = new Dictionary(); private static readonly Dictionary _bwInflight = new Dictionary(); private static readonly Dictionary _bwRequested = new Dictionary(); private static readonly Dictionary _pingResultsMs = new Dictionary(); private static readonly Dictionary _bwResults = new Dictionary(); private static int _nextSeq = 1; private static bool _probeRunning; private static bool _probeCompletedThisSession; private static bool _playerArrivedInWorld; private static Tier _latLastTier; private static int _latLastMedianMs; private static int _latLastP95Ms; private static int _latLastJitterMs; private static List _latLastRawMs = new List(); private static bool _latLastAborted; private static Tier _sessionMachineTier = Tier.Medium; private static bool _hasMachineTier; private static string _sessionServerKey = string.Empty; private static string _sessionHwHash = string.Empty; private static readonly Queue _rollingTiers = new Queue(); private static bool _rollingMonitorActive; private static Tier _pendingRollingTier; private static int _pendingRollingObservations; private static Coroutine _probeCoroutineHandle; private static Coroutine _rollingMonitorHandle; private static bool _probeAborted; private static EventInfo _playerInitialSpawnEvent; private static Action _playerInitialSpawnHandler; private static bool _playerInitialSpawnEventResolved; public static void OnPeerConnected(ZNetPeer peer) { if (peer != null && peer.m_rpc != null) { try { peer.m_rpc.Register("FiresGhetto.AutoTune.Ping", (Action)OnRpcPing); peer.m_rpc.Register("FiresGhetto.AutoTune.BwReq", (Action)OnRpcBwReq); peer.m_rpc.Register("FiresGhetto.AutoTune.Pong", (Action)OnRpcPong); peer.m_rpc.Register("FiresGhetto.AutoTune.BwResp", (Action)OnRpcBwResp); peer.m_rpc.Register("FiresGhetto.AutoTune.TierReport", (Action)ServerAutoTune.OnTierReport); } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] Failed to register probe RPCs on peer: " + ex.Message); return; } if ((!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()) && !_probeCompletedThisSession && !_probeRunning && !((Object)(object)FiresGhettoNetworkMod.Instance == (Object)null) && AutoTuneConfig.EnableClientAutoTune != null && AutoTuneConfig.EnableClientAutoTune.Value) { _playerArrivedInWorld = false; SubscribePlayerArrival(); _probeAborted = false; _probeCoroutineHandle = ((MonoBehaviour)FiresGhettoNetworkMod.Instance).StartCoroutine(RunClientProbe(peer)); } } } private static EventInfo ResolvePlayerInitialSpawnEvent() { if (_playerInitialSpawnEventResolved) { return _playerInitialSpawnEvent; } _playerInitialSpawnEventResolved = true; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type; try { type = assembly.GetType("Game", throwOnError: false); } catch { continue; } if (!(type == null)) { EventInfo eventInfo = type.GetEvent("m_playerInitialSpawn", BindingFlags.Static | BindingFlags.Public); if (eventInfo != null) { _playerInitialSpawnEvent = eventInfo; return eventInfo; } } } LoggerOptions.LogWarning("[AutoTune] Could not resolve Game.m_playerInitialSpawn via reflection — arrival wait will fall back to its safety timeout."); return null; } private static void SubscribePlayerArrival() { EventInfo eventInfo = ResolvePlayerInitialSpawnEvent(); if (eventInfo == null) { return; } if (_playerInitialSpawnHandler == null) { _playerInitialSpawnHandler = OnPlayerInitialSpawn; } try { eventInfo.RemoveEventHandler(null, _playerInitialSpawnHandler); eventInfo.AddEventHandler(null, _playerInitialSpawnHandler); } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] Failed to subscribe to Game.m_playerInitialSpawn: " + ex.Message); } } private static void UnsubscribePlayerArrival() { EventInfo playerInitialSpawnEvent = _playerInitialSpawnEvent; if (playerInitialSpawnEvent == null || _playerInitialSpawnHandler == null) { return; } try { playerInitialSpawnEvent.RemoveEventHandler(null, _playerInitialSpawnHandler); } catch { } } private static void OnPlayerInitialSpawn() { _playerArrivedInWorld = true; UnsubscribePlayerArrival(); LoggerOptions.LogInfo("[AutoTune] Game.m_playerInitialSpawn fired — player is in the world."); } public static void OnPeerDisconnected() { _probeAborted = true; FiresGhettoNetworkMod instance = FiresGhettoNetworkMod.Instance; if ((Object)(object)instance != (Object)null) { if (_probeCoroutineHandle != null) { try { ((MonoBehaviour)instance).StopCoroutine(_probeCoroutineHandle); } catch { } _probeCoroutineHandle = null; } if (_rollingMonitorHandle != null) { try { ((MonoBehaviour)instance).StopCoroutine(_rollingMonitorHandle); } catch { } _rollingMonitorHandle = null; } } _probeRunning = false; _probeCompletedThisSession = false; _rollingMonitorActive = false; _rollingTiers.Clear(); _pendingRollingTier = Tier.Low; _pendingRollingObservations = 0; _pingInflight.Clear(); _bwInflight.Clear(); _bwRequested.Clear(); _pingResultsMs.Clear(); _bwResults.Clear(); _playerArrivedInWorld = false; UnsubscribePlayerArrival(); } private static void OnRpcPing(ZRpc rpc, int seq) { try { rpc.Invoke("FiresGhetto.AutoTune.Pong", new object[1] { seq }); } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] Ping echo failed: " + ex.Message); } } private static void OnRpcBwReq(ZRpc rpc, int seq, int requestedBytes) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown int num = Mathf.Clamp(requestedBytes, 1024, 262144); try { ZPackage val = new ZPackage(); byte[] array = new byte[num]; for (int i = 0; i < num; i++) { array[i] = (byte)(i * 1103515245 + 12345 >> 8); } val.Write(array); rpc.Invoke("FiresGhetto.AutoTune.BwResp", new object[2] { seq, val }); } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] Bandwidth echo failed: " + ex.Message); } } private static void OnRpcPong(ZRpc rpc, int seq) { if (_pingInflight.TryGetValue(seq, out var value)) { value.Stop(); _pingResultsMs[seq] = value.ElapsedMilliseconds; _pingInflight.Remove(seq); } } private static void OnRpcBwResp(ZRpc rpc, int seq, ZPackage pkg) { if (_bwInflight.TryGetValue(seq, out var value)) { value.Stop(); _bwRequested.TryGetValue(seq, out var value2); int num = 0; try { byte[] obj = ((pkg != null) ? pkg.GetArray() : null); num = ((obj != null) ? obj.Length : value2); } catch { num = value2; } _bwResults[seq] = new BwResult { Bytes = num, Ms = value.ElapsedMilliseconds }; _bwInflight.Remove(seq); _bwRequested.Remove(seq); } } private static IEnumerator RunClientProbe(ZNetPeer serverPeer) { _probeRunning = true; float arrivalTimeout = AutoTuneConfig.PlayerArrivalTimeoutSeconds?.Value ?? 180f; float arrivalWait = 0f; while (!_playerArrivedInWorld && arrivalWait < arrivalTimeout) { if (_probeAborted || (Object)(object)ZNet.instance == (Object)null) { ProbeAbort("aborted (disconnect/shutdown) while waiting for player arrival"); yield break; } if (serverPeer == null || serverPeer.m_rpc == null) { ProbeAbort("peer dropped while waiting for player arrival"); yield break; } yield return null; arrivalWait += Time.unscaledDeltaTime; } if (!_playerArrivedInWorld) { LoggerOptions.LogWarning($"[AutoTune] Game.m_playerInitialSpawn never fired within {arrivalTimeout:0}s — proceeding with probe anyway, results may be unreliable."); } else { LoggerOptions.LogInfo($"[AutoTune] Player arrived after {arrivalWait:0.0}s — settling before probe."); } float spawnDelay = AutoTuneConfig.ProbeStartDelaySeconds?.Value ?? 10f; for (float waited = 0f; waited < spawnDelay; waited += Time.unscaledDeltaTime) { if (_probeAborted || (Object)(object)ZNet.instance == (Object)null) { ProbeAbort("aborted (disconnect/shutdown) during post-arrival settle"); yield break; } if (serverPeer == null || serverPeer.m_rpc == null) { ProbeAbort("peer dropped during post-arrival settle"); yield break; } yield return null; } int num = Mathf.Max(1, SystemInfo.processorCount); int num2 = Mathf.Max(0, SystemInfo.systemMemorySize); string text = SystemInfo.graphicsDeviceName ?? "unknown"; string hwHash = MakeHwHash(num, num2, text); Tier cpuTier = ScoreCpuTier(num, num2); string serverKey = MakeServerKey(serverPeer); if (!(AutoTuneConfig.RetuneOnEveryLogin?.Value ?? false)) { AutoTuneCache.CacheEntry cacheEntry = null; try { cacheEntry = AutoTuneCache.TryGet(serverKey, hwHash); } catch (FileNotFoundException ex) { LoggerOptions.LogWarning("[AutoTune] Cache dep missing (" + (ex.FileName ?? "Newtonsoft.Json") + "); running full probe instead of cached fast-path."); } catch (TypeLoadException ex2) { LoggerOptions.LogWarning("[AutoTune] Cache type load failed (" + ex2.Message + "); running full probe."); } catch (Exception ex3) { LoggerOptions.LogWarning("[AutoTune] Cache lookup threw " + ex3.GetType().Name + ": " + ex3.Message + "; running full probe."); } if (cacheEntry != null) { LoggerOptions.LogInfo($"[AutoTune] Using cached tier for {serverKey}: {cacheEntry.Tier} (probed {(int)(DateTime.UtcNow - cacheEntry.TimestampUtc).TotalDays}d ago)"); AutoTuneState.SetClient(cacheEntry.Tier, cacheEntry.PingMedianMs); ApplyClientTier(cacheEntry.Tier); SendTierReport(serverPeer, cacheEntry.Tier, cacheEntry.PingMedianMs); _probeCompletedThisSession = true; _probeRunning = false; _sessionServerKey = serverKey; _sessionHwHash = hwHash; _sessionMachineTier = cpuTier; _hasMachineTier = true; if ((Object)(object)FiresGhettoNetworkMod.Instance != (Object)null) { _rollingMonitorHandle = ((MonoBehaviour)FiresGhettoNetworkMod.Instance).StartCoroutine(RunRollingMonitor(serverPeer)); } yield break; } } LoggerOptions.LogMessage($"[AutoTune] Starting probe — cores={num}, RAM={num2}MB, GPU={text}"); _sessionServerKey = serverKey; _sessionHwHash = hwHash; yield return DoLatencyProbe(serverPeer); if (_latLastAborted) { LoggerOptions.LogMessage("[AutoTune] Initial latency probe aborted (raw=[" + string.Join(",", _latLastRawMs) + "]) — defaulting to LOW tier, skipping bandwidth probe."); Finalize(serverKey, hwHash, Tier.Low, _latLastMedianMs, serverPeer); yield break; } int pingMedian = _latLastMedianMs; Tier netTier = _latLastTier; LoggerOptions.LogMessage(string.Format("[AutoTune] Latency: raw=[{0}] trimmed→ median={1}ms p95={2}ms iqrJitter={3}ms → {4}", string.Join(",", _latLastRawMs), pingMedian, _latLastP95Ms, _latLastJitterMs, netTier)); Tier fpsTier = Tier.Medium; List frameMs = new List(300); float unscaledDeltaTime; for (float collected = 0f; collected < 5f; collected += unscaledDeltaTime) { yield return null; unscaledDeltaTime = Time.unscaledDeltaTime; if (unscaledDeltaTime > 0f && unscaledDeltaTime < 1f) { frameMs.Add(unscaledDeltaTime * 1000f); } } if (frameMs.Count > 0) { float num3 = MedianFloat(frameMs); float num4 = P95Float(frameMs); fpsTier = ScoreFpsTier(num3, num4); LoggerOptions.LogMessage($"[AutoTune] Frame time: median={num3:0.0}ms p95={num4:0.0}ms → {fpsTier}"); } float bwKbPerSec = -1f; bool bwProbeCompleted = false; if (netTier != Tier.Low) { int payloadBytes = AutoTuneConfig.ProbeBandwidthPayloadBytes?.Value ?? 32768; float collected = Mathf.Max(2f, (AutoTuneConfig.ProbePingTimeoutSeconds?.Value ?? 5f) * 1.5f); int sampleCount = 3; frameMs = new List(sampleCount); int timeouts = 0; for (int s = 0; s < sampleCount; s++) { if (serverPeer == null) { break; } if (serverPeer.m_rpc == null) { break; } int seq = NextSeq(); Stopwatch value = Stopwatch.StartNew(); _bwInflight[seq] = value; _bwRequested[seq] = payloadBytes; if (!TryInvoke(serverPeer, "FiresGhetto.AutoTune.BwReq", seq, payloadBytes)) { _bwInflight.Remove(seq); _bwRequested.Remove(seq); break; } float waitSec = 0f; while (_bwInflight.ContainsKey(seq) && waitSec < collected) { yield return null; waitSec += Time.unscaledDeltaTime; } BwResult value2; if (_bwInflight.ContainsKey(seq)) { _bwInflight.Remove(seq); _bwRequested.Remove(seq); timeouts++; } else if (_bwResults.TryGetValue(seq, out value2)) { _bwResults.Remove(seq); if (value2.Ms > 0) { float item = (float)value2.Bytes / 1024f / ((float)value2.Ms / 1000f); frameMs.Add(item); } } if (s < sampleCount - 1) { yield return (object)new WaitForSeconds(0.5f); } } if (frameMs.Count > 0) { float num5 = frameMs[0]; foreach (float item2 in frameMs) { if (item2 > num5) { num5 = item2; } } bwKbPerSec = num5; bwProbeCompleted = true; LoggerOptions.LogMessage(string.Format("[AutoTune] Bandwidth: samples=[{0}] KB/s, peak={1:0} KB/s, timeouts={2}", string.Join(",", frameMs.ConvertAll((float v) => v.ToString("0"))), bwKbPerSec, timeouts)); } else if (timeouts > 0) { LoggerOptions.LogMessage($"[AutoTune] Bandwidth probe: all {timeouts} samples timed out — treated as bandwidth-bad signal."); bwKbPerSec = 0f; bwProbeCompleted = true; } } Tier tier = MinTier(cpuTier, fpsTier); Tier tier2 = netTier; Tier tier3 = ComputeFinalTier(tier, tier2, bwKbPerSec, bwProbeCompleted); _sessionMachineTier = tier; _hasMachineTier = true; LoggerOptions.LogMessage(string.Format("[AutoTune] Final tier: machine={0} (cpu={1} fps={2}) latency={3} bw={4} → {5}", tier, cpuTier, fpsTier, tier2, bwProbeCompleted ? (bwKbPerSec.ToString("0") + "KB/s") : "n/a", tier3)); Finalize(serverKey, hwHash, tier3, pingMedian, serverPeer); if ((Object)(object)FiresGhettoNetworkMod.Instance != (Object)null) { _rollingMonitorHandle = ((MonoBehaviour)FiresGhettoNetworkMod.Instance).StartCoroutine(RunRollingMonitor(serverPeer)); } } private static Tier ComputeFinalTier(Tier machineTier, Tier latencyTier, float bwKbPerSec, bool bwProbeCompleted) { Tier tier = latencyTier; if (bwProbeCompleted && ScoreBwTier(bwKbPerSec) < tier) { tier = StepTowardLow(tier, 1); } if (tier != Tier.Low) { return machineTier; } int steps = AutoTuneConfig.LinkDowngradeCap?.Value ?? 1; return StepTowardLow(machineTier, steps); } private static Tier StepTowardLow(Tier tier, int steps) { int val = (int)(tier - Math.Max(0, steps)); return (Tier)Math.Max(0, val); } private static Tier ScoreBwTier(float kbPerSec) { if (kbPerSec >= 500f) { return Tier.High; } if (kbPerSec >= 200f) { return Tier.Medium; } return Tier.Low; } private static IEnumerator DoLatencyProbe(ZNetPeer serverPeer) { int pingCount = AutoTuneConfig.ProbePingCount?.Value ?? 10; float pingTimeoutSec = AutoTuneConfig.ProbePingTimeoutSeconds?.Value ?? 5f; int pingAbortMs = AutoTuneConfig.ProbePingAbortMs?.Value ?? 2000; _latLastAborted = false; _latLastRawMs = new List(pingCount); if (serverPeer != null && serverPeer.m_rpc != null) { int warmSeq = NextSeq(); _pingInflight[warmSeq] = Stopwatch.StartNew(); if (TryInvoke(serverPeer, "FiresGhetto.AutoTune.Ping", warmSeq)) { float warmWait = 0f; while (_pingInflight.ContainsKey(warmSeq) && warmWait < pingTimeoutSec) { yield return null; warmWait += Time.unscaledDeltaTime; } } _pingInflight.Remove(warmSeq); _pingResultsMs.Remove(warmSeq); yield return (object)new WaitForSeconds(0.3f); } int badSamples = 0; for (int warmSeq = 0; warmSeq < pingCount && serverPeer != null && serverPeer.m_rpc != null && !_probeAborted && !((Object)(object)ZNet.instance == (Object)null); warmSeq++) { int seq = NextSeq(); Stopwatch value = Stopwatch.StartNew(); _pingInflight[seq] = value; if (!TryInvoke(serverPeer, "FiresGhetto.AutoTune.Ping", seq)) { _pingInflight.Remove(seq); break; } float warmWait = 0f; while (_pingInflight.ContainsKey(seq) && warmWait < pingTimeoutSec) { yield return null; warmWait += Time.unscaledDeltaTime; } long num; if (_pingInflight.ContainsKey(seq)) { _pingInflight.Remove(seq); num = pingAbortMs; } else { if (!_pingResultsMs.TryGetValue(seq, out var value2)) { continue; } _pingResultsMs.Remove(seq); num = value2; } _latLastRawMs.Add(num); if (num >= pingAbortMs) { badSamples++; } if (badSamples >= 2) { _latLastAborted = true; _latLastTier = Tier.Low; _latLastMedianMs = (int)ComputeMedian(_latLastRawMs); _latLastP95Ms = pingAbortMs; _latLastJitterMs = pingAbortMs; yield break; } yield return (object)new WaitForSeconds(0.2f); } if (_latLastRawMs.Count == 0) { _latLastAborted = true; _latLastTier = Tier.Low; _latLastMedianMs = 0; _latLastP95Ms = 0; _latLastJitterMs = 0; } else { List xs = TrimWorst(_latLastRawMs, (_latLastRawMs.Count >= 5) ? 1 : 0); _latLastMedianMs = (int)ComputeMedian(xs); _latLastP95Ms = (int)ComputeP95(xs); _latLastJitterMs = ComputeIqrJitter(xs); _latLastTier = ScoreNetTier(_latLastMedianMs, _latLastP95Ms, _latLastJitterMs); } } private static IEnumerator RunRollingMonitor(ZNetPeer serverPeer) { if (!(AutoTuneConfig.EnableRollingMonitor?.Value ?? true) || _rollingMonitorActive) { yield break; } _rollingMonitorActive = true; float num = AutoTuneConfig.RollingMonitorIntervalMinutes?.Value ?? 5f; int windowSize = AutoTuneConfig.RollingMonitorWindow?.Value ?? 5; float intervalSec = num * 60f; _rollingTiers.Clear(); _rollingTiers.Enqueue(AutoTuneState.ClientTier); _pendingRollingTier = AutoTuneState.ClientTier; _pendingRollingObservations = 0; LoggerOptions.LogInfo($"[AutoTune] Rolling monitor started — re-probe every {num:0}min, window={windowSize}"); while (_probeCompletedThisSession) { if (_probeAborted || (Object)(object)ZNet.instance == (Object)null) { _rollingMonitorActive = false; yield break; } float waited = 0f; while (waited < intervalSec) { yield return (object)new WaitForSeconds(1f); waited += 1f; if (_probeAborted) { _rollingMonitorActive = false; yield break; } if ((Object)(object)ZNet.instance == (Object)null) { _rollingMonitorActive = false; yield break; } if (serverPeer == null || serverPeer.m_rpc == null) { _rollingMonitorActive = false; yield break; } if (!_probeCompletedThisSession) { _rollingMonitorActive = false; yield break; } } if (_probeAborted || (Object)(object)ZNet.instance == (Object)null) { _rollingMonitorActive = false; yield break; } yield return DoLatencyProbe(serverPeer); if (_probeAborted || (Object)(object)ZNet.instance == (Object)null) { _rollingMonitorActive = false; yield break; } if (_latLastAborted) { LoggerOptions.LogMessage(string.Format("[AutoTune] Rolling re-probe aborted (raw=[{0}]); leaving tier at {1}.", string.Join(",", _latLastRawMs), AutoTuneState.ClientTier)); continue; } Tier item = ComputeFinalTier(_sessionMachineTier, _latLastTier, -1f, bwProbeCompleted: false); _rollingTiers.Enqueue(item); while (_rollingTiers.Count > windowSize) { _rollingTiers.Dequeue(); } Tier tier = ComputeMode(_rollingTiers); Tier clientTier = AutoTuneState.ClientTier; if (tier != clientTier) { if (_pendingRollingTier == tier) { _pendingRollingObservations++; } else { _pendingRollingTier = tier; _pendingRollingObservations = 1; } int num2 = ((tier > clientTier) ? 2 : 3); if (_pendingRollingObservations >= num2) { LoggerOptions.LogMessage(string.Format("[AutoTune] Rolling tier change: {0} → {1} ({2} consecutive obs; this probe: latency={3} median={4}ms; window=[{5}])", clientTier, tier, _pendingRollingObservations, _latLastTier, _latLastMedianMs, string.Join(",", _rollingTiers))); AutoTuneState.SetClient(tier, _latLastMedianMs); ApplyClientTier(tier); try { AutoTuneCache.Save(_sessionServerKey, tier, _latLastMedianMs, _sessionHwHash); } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] Cache save skipped (" + ex.GetType().Name + ": " + ex.Message + ")."); } SendTierReport(serverPeer, tier, _latLastMedianMs); _pendingRollingObservations = 0; } else { LoggerOptions.LogInfo(string.Format("[AutoTune] Rolling tier change pending: {0} → {1} (obs {2}/{3}; this probe: latency={4} median={5}ms; window=[{6}])", clientTier, tier, _pendingRollingObservations, num2, _latLastTier, _latLastMedianMs, string.Join(",", _rollingTiers))); } } else { _pendingRollingTier = clientTier; _pendingRollingObservations = 0; LoggerOptions.LogInfo(string.Format("[AutoTune] Rolling re-probe stable at {0} (this probe: latency={1} median={2}ms iqrJitter={3}ms; window=[{4}])", tier, _latLastTier, _latLastMedianMs, _latLastJitterMs, string.Join(",", _rollingTiers))); } } _rollingMonitorActive = false; } private static Tier ComputeMode(IEnumerable tiers) { int num = 0; int num2 = 0; int num3 = 0; using (IEnumerator enumerator = tiers.GetEnumerator()) { while (enumerator.MoveNext()) { switch (enumerator.Current) { case Tier.Low: num++; break; case Tier.Medium: num2++; break; case Tier.High: num3++; break; } } } int num4 = Math.Max(num, Math.Max(num2, num3)); bool flag = num == num4; bool flag2 = num2 == num4; bool flag3 = num3 == num4; if (flag && !flag2 && !flag3) { return Tier.Low; } if (flag2 && !flag && !flag3) { return Tier.Medium; } if (flag3 && !flag && !flag2) { return Tier.High; } Tier tier = ((!AutoTuneState.HasClientResult) ? Tier.Medium : AutoTuneState.ClientTier); if (tier == Tier.Low && flag) { return Tier.Low; } if (tier == Tier.Medium && flag2) { return Tier.Medium; } if (tier == Tier.High && flag3) { return Tier.High; } if (flag) { return Tier.Low; } if (flag2) { return Tier.Medium; } return Tier.High; } private static void Finalize(string serverKey, string hwHash, Tier tier, int pingMedianMs, ZNetPeer serverPeer) { AutoTuneState.SetClient(tier, pingMedianMs); try { AutoTuneCache.Save(serverKey, tier, pingMedianMs, hwHash); } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] Cache save skipped (" + ex.GetType().Name + ": " + ex.Message + "); tier still applied in-memory."); } ApplyClientTier(tier); SendTierReport(serverPeer, tier, pingMedianMs); _probeCompletedThisSession = true; _probeRunning = false; } private static void ProbeAbort(string reason) { LoggerOptions.LogWarning("[AutoTune] Probe aborted: " + reason + " — defaulting to LOW tier."); AutoTuneState.SetClient(Tier.Low, 0); ApplyClientTier(Tier.Low); _probeCompletedThisSession = true; _probeRunning = false; } private static void ApplyClientTier(Tier tier) { try { NetworkingRatesGroup.ApplySendRates(); } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] ApplySendRates failed: " + ex.Message); } try { NetworkingRatesGroup.ApplySendBufferSize(); } catch (Exception ex2) { LoggerOptions.LogWarning("[AutoTune] ApplySendBufferSize failed: " + ex2.Message); } try { NetworkingRatesGroup.ApplyRecvBufferSize(); } catch (Exception ex3) { LoggerOptions.LogWarning("[AutoTune] ApplyRecvBufferSize failed: " + ex3.Message); } try { NetworkingRatesGroup.ApplyRecvMaxMessageSize(); } catch (Exception ex4) { LoggerOptions.LogWarning("[AutoTune] ApplyRecvMaxMessageSize failed: " + ex4.Message); } LoggerOptions.LogMessage($"[AutoTune] Applied client tier {tier}"); } private static void SendTierReport(ZNetPeer serverPeer, Tier tier, int pingMedianMs) { if (serverPeer == null || serverPeer.m_rpc == null) { return; } try { serverPeer.m_rpc.Invoke("FiresGhetto.AutoTune.TierReport", new object[2] { (int)tier, pingMedianMs }); } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] TierReport invoke failed: " + ex.Message); } } private static Tier ScoreCpuTier(int cores, int ramMb) { if (cores >= 8 && ramMb >= 16384) { return Tier.High; } if (cores >= 4 && ramMb >= 8192) { return Tier.Medium; } return Tier.Low; } private static Tier ScoreNetTier(int medianMs, int p95Ms, int jitterMs) { if (medianMs <= 60 && p95Ms <= 100 && jitterMs <= 20) { return Tier.High; } if (medianMs <= 120 && p95Ms <= 200 && jitterMs <= 50) { return Tier.Medium; } return Tier.Low; } private static Tier ScoreFpsTier(float medianMs, float p95Ms) { if (medianMs <= 16.7f && p95Ms <= 25f) { return Tier.High; } if (medianMs <= 33.4f && p95Ms <= 50f) { return Tier.Medium; } return Tier.Low; } private static Tier MinTier(Tier a, Tier b) { return (Tier)Mathf.Min((int)a, (int)b); } private static double ComputeMedian(List xs) { if (xs.Count == 0) { return 0.0; } List list = new List(xs); list.Sort(); int num = list.Count / 2; if (list.Count % 2 != 0) { return list[num]; } return (double)(list[num - 1] + list[num]) * 0.5; } private static double ComputeP95(List xs) { if (xs.Count == 0) { return 0.0; } List list = new List(xs); list.Sort(); int index = Mathf.Clamp((int)Math.Ceiling((double)list.Count * 0.95) - 1, 0, list.Count - 1); return list[index]; } private static int ComputeJitter(List xs) { if (xs.Count < 2) { return 0; } long num = xs[0]; long num2 = xs[0]; foreach (long x in xs) { if (x < num) { num = x; } if (x > num2) { num2 = x; } } return (int)(num2 - num); } private static int ComputeIqrJitter(List xs) { if (xs.Count < 4) { return ComputeJitter(xs); } List list = new List(xs); list.Sort(); int index = list.Count / 4; int num = list.Count * 3 / 4; if (num >= list.Count) { num = list.Count - 1; } return (int)(list[num] - list[index]); } private static List TrimWorst(List xs, int dropCount) { if (dropCount <= 0 || xs.Count <= dropCount) { return new List(xs); } List list = new List(xs); list.Sort(); return list.GetRange(0, list.Count - dropCount); } private static float MedianFloat(List xs) { if (xs.Count == 0) { return 0f; } List list = new List(xs); list.Sort(); int num = list.Count / 2; if (list.Count % 2 != 0) { return list[num]; } return (list[num - 1] + list[num]) * 0.5f; } private static float P95Float(List xs) { if (xs.Count == 0) { return 0f; } List list = new List(xs); list.Sort(); int index = Mathf.Clamp((int)Math.Ceiling((double)list.Count * 0.95) - 1, 0, list.Count - 1); return list[index]; } private static bool TryInvoke(ZNetPeer peer, string name, params object[] args) { try { peer.m_rpc.Invoke(name, args); return true; } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] RPC '" + name + "' invoke failed: " + ex.Message); return false; } } private static int NextSeq() { int nextSeq = _nextSeq; _nextSeq++; if (_nextSeq <= 0) { _nextSeq = 1; } return nextSeq; } private static string MakeHwHash(int cores, int ramMb, string gpu) { int num = 17; num = num * 31 + cores; num = num * 31 + ramMb; return (num * 31 + (gpu ?? "").GetHashCode()).ToString("X8"); } private static string MakeServerKey(ZNetPeer serverPeer) { try { if (serverPeer != null && serverPeer.m_socket != null) { string endPointString = serverPeer.m_socket.GetEndPointString(); if (!string.IsNullOrEmpty(endPointString)) { return endPointString; } } } catch { } return "unknown-server"; } } [HarmonyPatch] public static class AutoTuneProbeHooks { [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPostfix] public static void ZNet_OnNewConnection_Postfix(ZNetPeer peer) { AutoTuneProbe.OnPeerConnected(peer); } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPostfix] public static void ZNet_Disconnect_Postfix(ZNetPeer peer) { AutoTuneProbe.OnPeerDisconnected(); try { ServerAutoTune.ClearPeerReportedTier(peer?.m_uid ?? 0); } catch { } } [HarmonyPatch(typeof(ZNet), "Shutdown")] [HarmonyPostfix] public static void ZNet_Shutdown_Postfix() { AutoTuneProbe.OnPeerDisconnected(); } [HarmonyPatch(typeof(ZNet), "OnDestroy")] [HarmonyPostfix] public static void ZNet_OnDestroy_Postfix() { AutoTuneProbe.OnPeerDisconnected(); } } public enum Tier { Low, Medium, High } public struct TierPreset { public int SteamSendRateMinBytes; public int SteamSendRateMaxBytes; public int SteamSendBufferBytes; public int SteamRecvBufferBytes; public int SteamRecvMaxMessageBytes; public int ZoneLoadBatchSize; public int InstantiationBudgetMs; public int MaxInstancesPerFrame; public bool SafetyFallbackEnabled; public int SafetyFallbackThreshold; public UpdateRateOptions UpdateRate; public QueueSizeOptions QueueSize; public float ZDOThrottleDistance; public float AILODNearDistance; public float AILODFarDistance; public float AILODThrottleFactor; public float RpcAoIRadius; public int ExtendedZoneRadius; } public static class TierPresets { public static TierPreset For(Tier tier) { return tier switch { Tier.High => new TierPreset { SteamSendRateMinBytes = 153600, SteamSendRateMaxBytes = 1048576, SteamSendBufferBytes = 2097152, SteamRecvBufferBytes = 8388608, SteamRecvMaxMessageBytes = 4194304, ZoneLoadBatchSize = 4, InstantiationBudgetMs = 5, MaxInstancesPerFrame = 200, SafetyFallbackEnabled = true, SafetyFallbackThreshold = 5000, UpdateRate = UpdateRateOptions._150, QueueSize = QueueSizeOptions._48KB, ZDOThrottleDistance = 700f, AILODNearDistance = 150f, AILODFarDistance = 500f, AILODThrottleFactor = 0.5f, RpcAoIRadius = 384f, ExtendedZoneRadius = 2 }, Tier.Medium => new TierPreset { SteamSendRateMinBytes = 153600, SteamSendRateMaxBytes = 524288, SteamSendBufferBytes = 1048576, SteamRecvBufferBytes = 4194304, SteamRecvMaxMessageBytes = 4194304, ZoneLoadBatchSize = 2, InstantiationBudgetMs = 3, MaxInstancesPerFrame = 100, SafetyFallbackEnabled = true, SafetyFallbackThreshold = 5000, UpdateRate = UpdateRateOptions._100, QueueSize = QueueSizeOptions._32KB, ZDOThrottleDistance = 500f, AILODNearDistance = 100f, AILODFarDistance = 300f, AILODThrottleFactor = 0.5f, RpcAoIRadius = 256f, ExtendedZoneRadius = 1 }, _ => new TierPreset { SteamSendRateMinBytes = 153600, SteamSendRateMaxBytes = 393216, SteamSendBufferBytes = 524288, SteamRecvBufferBytes = 2097152, SteamRecvMaxMessageBytes = 4194304, ZoneLoadBatchSize = 1, InstantiationBudgetMs = 2, MaxInstancesPerFrame = 50, SafetyFallbackEnabled = true, SafetyFallbackThreshold = 8000, UpdateRate = UpdateRateOptions._75, QueueSize = QueueSizeOptions._vanilla, ZDOThrottleDistance = 350f, AILODNearDistance = 80f, AILODFarDistance = 200f, AILODThrottleFactor = 0.4f, RpcAoIRadius = 192f, ExtendedZoneRadius = 0 }, }; } } public static class AutoTuneState { public static bool HasClientResult { get; private set; } public static Tier ClientTier { get; private set; } = Tier.Medium; public static int ClientPingMedianMs { get; private set; } public static bool HasServerResult { get; private set; } public static Tier ServerTier { get; private set; } = Tier.Medium; public static void SetClient(Tier tier, int pingMedianMs) { ClientTier = tier; ClientPingMedianMs = pingMedianMs; HasClientResult = true; } public static void ClearClient() { HasClientResult = false; ClientTier = Tier.Medium; ClientPingMedianMs = 0; } public static void SetServer(Tier tier) { ServerTier = tier; HasServerResult = true; } } public static class EffectiveConfig { public static int SteamSendRateMin() { if (IsDedicatedServerRuntime() && UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).SteamSendRateMinBytes; } if (UseClientAutoTune()) { return TierPresets.For(AutoTuneState.ClientTier).SteamSendRateMinBytes; } return SendRateMinFromEnum(FiresGhettoNetworkMod.ConfigSendRateMin.Value); } public static int SteamSendRateMax() { if (IsDedicatedServerRuntime() && UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).SteamSendRateMaxBytes; } if (UseClientAutoTune()) { return TierPresets.For(AutoTuneState.ClientTier).SteamSendRateMaxBytes; } return SendRateMaxFromEnum(FiresGhettoNetworkMod.ConfigSendRateMax.Value); } private static bool IsDedicatedServerRuntime() { if (ServerClientUtils.IsDedicatedServerDetected) { return true; } try { return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated(); } catch { return false; } } public static int SteamRecvBufferBytes() { if (IsDedicatedServerRuntime() && UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).SteamRecvBufferBytes; } if (UseClientAutoTune()) { return TierPresets.For(AutoTuneState.ClientTier).SteamRecvBufferBytes; } return Math.Max(2097152, FiresGhettoNetworkMod.ConfigZPackageReceiveBufferSize?.Value ?? 2097152); } public static int SteamRecvMaxMessageBytes() { if (IsDedicatedServerRuntime() && UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).SteamRecvMaxMessageBytes; } if (UseClientAutoTune()) { return TierPresets.For(AutoTuneState.ClientTier).SteamRecvMaxMessageBytes; } return 4194304; } public static int SteamSendBufferBytes() { if (IsDedicatedServerRuntime() && UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).SteamSendBufferBytes; } if (UseClientAutoTune()) { return TierPresets.For(AutoTuneState.ClientTier).SteamSendBufferBytes; } return 1048576; } public static int ZoneLoadBatchSize() { if (UseClientAutoTune()) { return TierPresets.For(AutoTuneState.ClientTier).ZoneLoadBatchSize; } return Math.Max(1, FiresGhettoNetworkMod.ConfigZoneLoadBatchSize?.Value ?? 2); } public static int InstantiationBudgetMs() { if (UseClientAutoTune()) { return TierPresets.For(AutoTuneState.ClientTier).InstantiationBudgetMs; } int val = FiresGhettoNetworkMod.ConfigInstantiationBudgetMs?.Value ?? 3; return Math.Max(1, val); } public static int MaxInstancesPerFrame() { if (UseClientAutoTune()) { return TierPresets.For(AutoTuneState.ClientTier).MaxInstancesPerFrame; } int val = FiresGhettoNetworkMod.ConfigMaxInstancesPerFrame?.Value ?? 100; return Math.Max(10, val); } public static bool SafetyFallbackEnabled() { if (UseClientAutoTune()) { return TierPresets.For(AutoTuneState.ClientTier).SafetyFallbackEnabled; } return FiresGhettoNetworkMod.ConfigSafetyFallbackEnabled?.Value ?? true; } public static int SafetyFallbackThreshold() { if (UseClientAutoTune()) { return TierPresets.For(AutoTuneState.ClientTier).SafetyFallbackThreshold; } int val = FiresGhettoNetworkMod.ConfigSafetyFallbackThreshold?.Value ?? 5000; return Math.Max(100, val); } public static bool TimeSliceInstantiationEnabled() { return FiresGhettoNetworkMod.ConfigEnableTimeSliceInstantiation?.Value ?? true; } public static bool EnablePlayerPrediction() { return PlayerPositionSyncPatches.ConfigEnablePlayerPrediction?.Value ?? false; } public static float SmoothingMaxInterval() { return PlayerPositionSyncPatches.ConfigSmoothingMaxInterval?.Value ?? 0.2f; } public static float SmoothingMinInterval() { return PlayerPositionSyncPatches.ConfigSmoothingMinInterval?.Value ?? 0f; } public static UpdateRateOptions UpdateRate() { if (UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).UpdateRate; } return FiresGhettoNetworkMod.ConfigUpdateRate.Value; } public static QueueSizeOptions QueueSize() { if (UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).QueueSize; } return FiresGhettoNetworkMod.ConfigQueueSize.Value; } public static float ZDOThrottleDistance() { if (UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).ZDOThrottleDistance; } return FiresGhettoNetworkMod.ConfigZDOThrottleDistance.Value; } public static float AILODNearDistance() { if (UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).AILODNearDistance; } return FiresGhettoNetworkMod.ConfigAILODNearDistance.Value; } public static float AILODFarDistance() { if (UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).AILODFarDistance; } return FiresGhettoNetworkMod.ConfigAILODFarDistance.Value; } public static float AILODThrottleFactor() { if (UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).AILODThrottleFactor; } return FiresGhettoNetworkMod.ConfigAILODThrottleFactor.Value; } public static float RpcAoIRadius() { if (UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).RpcAoIRadius; } return FiresGhettoNetworkMod.ConfigRpcAoIRadius.Value; } public static int ExtendedZoneRadius() { if (UseServerAutoTune()) { return TierPresets.For(AutoTuneState.ServerTier).ExtendedZoneRadius; } return FiresGhettoNetworkMod.ConfigExtendedZoneRadius.Value; } private static bool UseClientAutoTune() { if (AutoTuneConfig.EnableClientAutoTune != null && AutoTuneConfig.EnableClientAutoTune.Value) { return AutoTuneState.HasClientResult; } return false; } private static bool UseServerAutoTune() { if (AutoTuneConfig.EnableServerAutoTune != null && AutoTuneConfig.EnableServerAutoTune.Value) { return AutoTuneState.HasServerResult; } return false; } private static int SendRateMinFromEnum(SendRateMinOptions opt) { return opt switch { SendRateMinOptions._1024KB => 1048576, SendRateMinOptions._768KB => 786432, SendRateMinOptions._512KB => 524288, SendRateMinOptions._256KB => 262144, _ => 153600, }; } private static int SendRateMaxFromEnum(SendRateMaxOptions opt) { return opt switch { SendRateMaxOptions._1024KB => 1048576, SendRateMaxOptions._768KB => 786432, SendRateMaxOptions._512KB => 524288, SendRateMaxOptions._256KB => 262144, _ => 153600, }; } } public static class ServerAutoTune { private struct ClientReport { public Tier Tier; public int PingMedianMs; public DateTime ReceivedUtc; } private const int ClientReportWindow = 20; private static readonly Queue _clientReports = new Queue(); private static DateTime _lastSummaryLogged = DateTime.MinValue; private static readonly TimeSpan SummaryInterval = TimeSpan.FromMinutes(15.0); private static readonly HashSet _peersWithTierReported = new HashSet(); private const int ImplausibleCoreCount = 32; private const int ImplausibleRamMb = 131072; public static bool HasPeerReportedTier(long peerUid) { if (peerUid != 0L) { return _peersWithTierReported.Contains(peerUid); } return false; } public static void ClearPeerReportedTier(long peerUid) { if (peerUid != 0L) { _peersWithTierReported.Remove(peerUid); } } public static void InitServerSide() { if (ServerClientUtils.IsDedicatedServerDetected) { int num = Mathf.Max(1, SystemInfo.processorCount); int num2 = Mathf.Max(0, SystemInfo.systemMemorySize); Tier tier = ScoreServerTier(num, num2); bool num3 = AutoTuneConfig.EnableServerAutoTune?.Value ?? false; bool flag = AutoTuneConfig.LogServerSuggestions?.Value ?? true; if (num3) { AutoTuneState.SetServer(tier); LoggerOptions.LogMessage($"[AutoTune] SERVER auto-tune APPLIED: tier={tier} (cores={num}, RAM={num2}MB)."); } else if (flag) { LogSelfTuneSuggestion(tier, num, num2); } } } private static Tier ScoreServerTier(int cores, int ramMb) { if (cores > 32 || ramMb > 131072) { LoggerOptions.LogWarning($"[AutoTune] Detected {cores} cores / {ramMb}MB RAM — implausibly large for a dedicated " + "game-server allocation, almost certainly a container reporting the HOST's specs. Capping auto-tune tier at Medium to avoid overcommitting queue/buffers beyond what the container can actually deliver. Set 'Enable Server Auto-Tune' = false and tune manually if you know your real allocation."); return Tier.Medium; } if (cores >= 8 && ramMb >= 16384) { return Tier.High; } if (cores >= 4 && ramMb >= 8192) { return Tier.Medium; } return Tier.Low; } private static void LogSelfTuneSuggestion(Tier tier, int cores, int ramMb) { TierPreset tierPreset = TierPresets.For(tier); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"[AutoTune] Server self-tune: {tier} (cores={cores}, RAM={ramMb}MB). 'Enable Server Auto-Tune' is OFF — settings unchanged. Suggested values:"); AppendDelta(stringBuilder, "Update Rate", FiresGhettoNetworkMod.ConfigUpdateRate.Value.ToString(), tierPreset.UpdateRate.ToString()); AppendDelta(stringBuilder, "Queue Size", FiresGhettoNetworkMod.ConfigQueueSize.Value.ToString(), tierPreset.QueueSize.ToString()); AppendDelta(stringBuilder, "ZDO Throttle Distance", $"{FiresGhettoNetworkMod.ConfigZDOThrottleDistance.Value:0}m", $"{tierPreset.ZDOThrottleDistance:0}m"); AppendDelta(stringBuilder, "AI LOD Near Distance", $"{FiresGhettoNetworkMod.ConfigAILODNearDistance.Value:0}m", $"{tierPreset.AILODNearDistance:0}m"); AppendDelta(stringBuilder, "AI LOD Far Distance", $"{FiresGhettoNetworkMod.ConfigAILODFarDistance.Value:0}m", $"{tierPreset.AILODFarDistance:0}m"); AppendDelta(stringBuilder, "AI LOD Throttle Factor", $"{FiresGhettoNetworkMod.ConfigAILODThrottleFactor.Value:0.00}", $"{tierPreset.AILODThrottleFactor:0.00}"); AppendDelta(stringBuilder, "RPC AoI Radius", $"{FiresGhettoNetworkMod.ConfigRpcAoIRadius.Value:0}m", $"{tierPreset.RpcAoIRadius:0}m"); AppendDelta(stringBuilder, "Extended Zone Radius", FiresGhettoNetworkMod.ConfigExtendedZoneRadius.Value.ToString(), tierPreset.ExtendedZoneRadius.ToString()); stringBuilder.Append("To apply: set 'Enable Server Auto-Tune' = true OR copy individual values into the config above."); LoggerOptions.LogMessage(stringBuilder.ToString()); } private static void AppendDelta(StringBuilder sb, string label, string current, string suggested) { if (current == suggested) { sb.AppendLine($" {label,-26} {current} (already matches)"); } else { sb.AppendLine($" {label,-26} {current} → {suggested}"); } } public static void OnTierReport(ZRpc rpc, int tierInt, int pingMedianMs) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { Tier tier = (Enum.IsDefined(typeof(Tier), tierInt) ? ((Tier)tierInt) : Tier.Low); _clientReports.Enqueue(new ClientReport { Tier = tier, PingMedianMs = pingMedianMs, ReceivedUtc = DateTime.UtcNow }); while (_clientReports.Count > 20) { _clientReports.Dequeue(); } long num = 0L; try { num = ZNet.instance.GetPeer(rpc)?.m_uid ?? 0; } catch { } if (num != 0L) { _peersWithTierReported.Add(num); } LoggerOptions.LogInfo($"[AutoTune] Client reported tier={tier} ping={pingMedianMs}ms peer={num} (rolling window: {_clientReports.Count}/{20})"); if (DateTime.UtcNow - _lastSummaryLogged >= SummaryInterval) { LogClientSummary(); _lastSummaryLogged = DateTime.UtcNow; } } } private static void LogClientSummary() { ConfigEntry logServerSuggestions = AutoTuneConfig.LogServerSuggestions; if ((logServerSuggestions != null && !logServerSuggestions.Value) || _clientReports.Count == 0) { return; } int num = 0; int num2 = 0; int num3 = 0; long num4 = 0L; foreach (ClientReport clientReport in _clientReports) { switch (clientReport.Tier) { case Tier.Low: num++; break; case Tier.Medium: num2++; break; case Tier.High: num3++; break; } num4 += clientReport.PingMedianMs; } Tier tier = MedianTier(num, num2, num3); int num5 = (int)(num4 / Math.Max(1, _clientReports.Count)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"[AutoTune] Client tier distribution (last {_clientReports.Count}): HIGH={num3} MED={num2} LOW={num}, avgPing={num5}ms"); stringBuilder.AppendLine($" Median client tier: {tier}"); ConfigEntry enableServerAutoTune = AutoTuneConfig.EnableServerAutoTune; if (enableServerAutoTune == null || !enableServerAutoTune.Value) { TierPreset tierPreset = TierPresets.For(tier); stringBuilder.AppendLine(" If you'd like the server tuned for the median client, suggested values:"); AppendDelta(stringBuilder, "Update Rate", FiresGhettoNetworkMod.ConfigUpdateRate.Value.ToString(), tierPreset.UpdateRate.ToString()); AppendDelta(stringBuilder, "Queue Size", FiresGhettoNetworkMod.ConfigQueueSize.Value.ToString(), tierPreset.QueueSize.ToString()); AppendDelta(stringBuilder, "ZDO Throttle Distance", $"{FiresGhettoNetworkMod.ConfigZDOThrottleDistance.Value:0}m", $"{tierPreset.ZDOThrottleDistance:0}m"); AppendDelta(stringBuilder, "AI LOD Far Distance", $"{FiresGhettoNetworkMod.ConfigAILODFarDistance.Value:0}m", $"{tierPreset.AILODFarDistance:0}m"); AppendDelta(stringBuilder, "RPC AoI Radius", $"{FiresGhettoNetworkMod.ConfigRpcAoIRadius.Value:0}m", $"{tierPreset.RpcAoIRadius:0}m"); AppendDelta(stringBuilder, "Extended Zone Radius", FiresGhettoNetworkMod.ConfigExtendedZoneRadius.Value.ToString(), tierPreset.ExtendedZoneRadius.ToString()); } LoggerOptions.LogMessage(stringBuilder.ToString()); } private static Tier MedianTier(int low, int med, int high) { int num = low + med + high; if (num == 0) { return Tier.Medium; } int num2 = num / 2; int num3 = 0; num3 += low; if (num3 > num2) { return Tier.Low; } num3 += med; if (num3 > num2) { return Tier.Medium; } return Tier.High; } } [HarmonyPatch] public static class ZoneLoadPatches { private const int CapMin = 5; private const int CapMax = 200; private const int ChunkSize = 1; private const int SafetyBudgetMultiplier = 3; private const int SafetyBudgetMaxMs = 16; [HarmonyReversePatch(/*Could not decode attribute arguments.*/)] [HarmonyPatch(typeof(ZNetScene), "CreateObjectsSorted")] public static void CallCreateObjectsSorted(ZNetScene self, List currentNearObjects, int maxCreatedPerFrame, ref int created) { throw new NotImplementedException("Harmony reverse patch failed for ZNetScene.CreateObjectsSorted"); } [HarmonyReversePatch(/*Could not decode attribute arguments.*/)] [HarmonyPatch(typeof(ZNetScene), "CreateDistantObjects")] public static void CallCreateDistantObjects(ZNetScene self, List objects, int maxCreatedPerFrame, ref int created) { throw new NotImplementedException("Harmony reverse patch failed for ZNetScene.CreateDistantObjects"); } [HarmonyPatch(typeof(ZNetScene), "CreateObjects")] [HarmonyPrefix] public static bool CreateObjects_TimeBudgetPrefix(ZNetScene __instance, List currentNearObjects, List currentDistantObjects) { try { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return true; } } catch { return true; } if (!EffectiveConfig.TimeSliceInstantiationEnabled()) { return true; } int num = currentNearObjects?.Count ?? 0; int num2 = currentDistantObjects?.Count ?? 0; if (num == 0 && num2 == 0) { return false; } int num3 = EffectiveConfig.InstantiationBudgetMs(); int num4 = EffectiveConfig.MaxInstancesPerFrame(); if (EffectiveConfig.SafetyFallbackEnabled() && num + num2 > EffectiveConfig.SafetyFallbackThreshold()) { int num5 = num3 * 3; if (num5 > 16) { num5 = 16; } num3 = num5; } long num6 = num3 * Stopwatch.Frequency / 1000; Stopwatch stopwatch = Stopwatch.StartNew(); int created = 0; try { while (currentNearObjects != null && currentNearObjects.Count > 0 && stopwatch.ElapsedTicks < num6 && created < num4) { int num7 = created; int num8 = Math.Min(1, num4 - created); if (num8 <= 0) { break; } CallCreateObjectsSorted(__instance, currentNearObjects, num8, ref created); if (created == num7) { break; } } while (currentDistantObjects != null && currentDistantObjects.Count > 0 && stopwatch.ElapsedTicks < num6 && created < num4) { int num9 = created; int num10 = Math.Min(1, num4 - created); if (num10 > 0) { CallCreateDistantObjects(__instance, currentDistantObjects, num10, ref created); if (created == num9) { break; } continue; } break; } } catch (Exception ex) { LoggerOptions.LogWarning("[AutoTune] Time-slice CreateObjects threw: " + ex.Message); } return false; } public static int ScaleBatchCap(int vanillaCap) { try { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return vanillaCap; } int num = EffectiveConfig.ZoneLoadBatchSize(); if (num <= 1) { return vanillaCap; } int num2 = vanillaCap * num; if (num2 > vanillaCap * 8) { num2 = vanillaCap * 8; } return num2; } catch { return vanillaCap; } } [HarmonyPatch(typeof(ZNetScene), "CreateObjects")] [HarmonyTranspiler] public static IEnumerable CreateObjects_BatchCapTranspiler(IEnumerable instructions) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown List list = new List(instructions); MethodInfo methodInfo = AccessTools.Method(typeof(ZoneLoadPatches), "ScaleBatchCap", (Type[])null, (Type[])null); if (methodInfo == null) { return list; } int num = 0; for (int i = 0; i < list.Count - 1; i++) { if (IsSmallIntLoad(list[i], out var value) && value >= 5 && value <= 200 && IsStLocZero(list[i + 1])) { list[i].opcode = OpCodes.Ldc_I4; list[i].operand = value; list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)methodInfo)); i++; num++; } } if (num == 0) { LoggerOptions.LogWarning("[AutoTune] ZoneLoadBatch: no per-frame cap constant found in ZNetScene.CreateObjects — game IL may have changed; ZoneLoadBatchSize will be a no-op until a follow-up patch addresses the new IL shape."); } else { LoggerOptions.LogInfo($"[AutoTune] ZoneLoadBatch: patched {num} per-frame cap constant(s) (in-game cap + loading-screen cap) via runtime helper."); } return list; } private static bool IsStLocZero(CodeInstruction inst) { if (inst.opcode == OpCodes.Stloc_0) { return true; } if (inst.opcode == OpCodes.Stloc_S || inst.opcode == OpCodes.Stloc) { if (inst.operand is byte b) { return b == 0; } if (inst.operand is int num) { return num == 0; } if (inst.operand is LocalBuilder localBuilder) { return localBuilder.LocalIndex == 0; } if (inst.operand is LocalVariableInfo localVariableInfo) { return localVariableInfo.LocalIndex == 0; } } return false; } private static bool IsSmallIntLoad(CodeInstruction inst, out int value) { if (inst.opcode == OpCodes.Ldc_I4_0) { value = 0; return true; } if (inst.opcode == OpCodes.Ldc_I4_1) { value = 1; return true; } if (inst.opcode == OpCodes.Ldc_I4_2) { value = 2; return true; } if (inst.opcode == OpCodes.Ldc_I4_3) { value = 3; return true; } if (inst.opcode == OpCodes.Ldc_I4_4) { value = 4; return true; } if (inst.opcode == OpCodes.Ldc_I4_5) { value = 5; return true; } if (inst.opcode == OpCodes.Ldc_I4_6) { value = 6; return true; } if (inst.opcode == OpCodes.Ldc_I4_7) { value = 7; return true; } if (inst.opcode == OpCodes.Ldc_I4_8) { value = 8; return true; } if (inst.opcode == OpCodes.Ldc_I4 || inst.opcode == OpCodes.Ldc_I4_S) { if (inst.operand is int num) { value = num; return true; } if (inst.operand is sbyte b) { value = b; return true; } if (inst.operand is byte b2) { value = b2; return true; } if (inst.operand is short num2) { value = num2; return true; } } value = 0; return false; } } }