Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SkadiNet v1.0.4
SkadiNet.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyFileVersion("1.0.4.0")] [assembly: AssemblyInformationalVersion("1.0.4")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyProduct("SkadiNet")] [assembly: AssemblyTitle("SkadiNet")] [assembly: AssemblyVersion("1.0.4.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SkadiNet { internal enum ConfigSyncScope { ServerSynced, ClientLocal } internal static class ConfigSyncManager { private static MethodInfo _addConfigEntryMethod; internal static ConfigSync Sync { get; private set; } internal static ConfigEntry<bool> LockServerConfig { get; private set; } internal static void Initialize(ConfigFile config) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown Sync = new ConfigSync("sighsorry.SkadiNet") { DisplayName = "SkadiNet", CurrentVersion = "1.0.4", MinimumRequiredVersion = "1.0.4", ModRequired = true }; LockServerConfig = config.Bind<bool>("General", "LockServerConfig", true, new ConfigDescription("Lock server-synced config for non-admin clients.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 90 } })); Sync.AddLockingConfigEntry<bool>(LockServerConfig).SynchronizedConfig = true; } internal static ConfigEntry<T> Bind<T>(ConfigFile config, string group, string name, T value, ConfigDescription description, ConfigSyncScope scope) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown string text = SuffixFor(scope); ConfigDescription val = new ConfigDescription(description.Description + text, description.AcceptableValues, description.Tags); ConfigEntry<T> obj = config.Bind<T>(group, name, value, val); Register((ConfigEntryBase)(object)obj, scope == ConfigSyncScope.ServerSynced); return obj; } private static string SuffixFor(ConfigSyncScope scope) { return scope switch { ConfigSyncScope.ServerSynced => " [Synced with Server]", ConfigSyncScope.ClientLocal => " [Client Local; Not Synced with Server]", _ => throw new ArgumentOutOfRangeException("scope", scope, null), }; } private static void Register(ConfigEntryBase entry, bool synchronized) { if (Sync != null && entry != null && (LockServerConfig == null || !object.Equals(entry.Definition, ((ConfigEntryBase)LockServerConfig).Definition)) && GetAddConfigEntryMethod().MakeGenericMethod(entry.SettingType).Invoke(Sync, new object[1] { entry }) is OwnConfigEntryBase ownConfigEntryBase) { ownConfigEntryBase.SynchronizedConfig = synchronized; } } private static MethodInfo GetAddConfigEntryMethod() { if (_addConfigEntryMethod != null) { return _addConfigEntryMethod; } _addConfigEntryMethod = typeof(ConfigSync).GetMethods(BindingFlags.Instance | BindingFlags.Public).First((MethodInfo method) => method.Name == "AddConfigEntry" && method.IsGenericMethodDefinition); return _addConfigEntryMethod; } } internal sealed class ConfigurationManagerAttributes { public int? Order; } internal static class EffectiveConfig { internal const int SteamSendRateBytes = 36000000; internal const int CompressionFailureLimitPerPeer = 1; internal const float OwnershipRelativeHysteresis = 0.15f; internal const float OwnershipAbsoluteHysteresisMs = 20f; internal const float OwnerSwitchCooldownSeconds = 3f; internal const float OwnerHintSwitchCooldownSeconds = 5f; internal const float ShipOwnerSwitchCooldownSeconds = 8f; internal const float RecoverUnownedAfterSeconds = 2f; internal static bool SchedulerEnabled { get { if (ModConfig.Enabled.Value) { return IsPositive(ModConfig.SchedulerThroughput); } return false; } } internal static bool PayloadReducerEnabled { get { if (ModConfig.Enabled.Value) { return IsPositive(ModConfig.PayloadReducerStrength); } return false; } } internal static bool CompressionEnabled { get { if (ModConfig.Enabled.Value) { return IsPositive(ModConfig.CompressionAggression); } return false; } } internal static bool RpcAoiEnabled { get { if (ModConfig.Enabled.Value) { return IsPositive(ModConfig.RpcAoiAggression); } return false; } } internal static bool ClientStutterGuardEnabled { get { if (ModConfig.Enabled.Value) { return IsPositive(ModConfig.ClientStutterGuardStrength); } return false; } } internal static bool AdaptiveOwnershipEnabled { get { if (ModConfig.Enabled.Value) { return IsPositive(ModConfig.OwnershipIntensity); } return false; } } internal static bool PeerQualityEnabled => AdaptiveOwnershipEnabled; internal static bool OwnerHintsEnabled => AdaptiveOwnershipEnabled; internal static float SendInterval => Map(ModConfig.SchedulerThroughput, 0.1f, 0.05f, 0.02f); internal static float MinSendInterval => Map(ModConfig.SchedulerThroughput, 0.06f, 0.03f, 0.015f); internal static float MaxSendInterval => Map(ModConfig.SchedulerThroughput, 0.2f, 0.1f, 0.045f); internal static int BasePeersPerTick => MapInt(ModConfig.SchedulerThroughput, 1, 4, 12); internal static int MaxPeersPerTick => MapInt(ModConfig.SchedulerThroughput, 4, 12, 32); internal static int ZdoQueueLimitBytes => MapInt(ModConfig.SchedulerThroughput, 10240, 65536, 196608); internal static int ZdoQueueMinPackageBytes => MapInt(ModConfig.SchedulerThroughput, 2048, 2048, 768); internal static int PeerQueueSoftLimitBytes => MapInt(ModConfig.SchedulerThroughput, 1572864, 524288, 196608); internal static int PeerQueueHardLimitBytes => MapInt(ModConfig.SchedulerThroughput, 6291456, 2097152, 786432); internal static float LaggingPeerMaxSkipSeconds => Map(ModConfig.SchedulerThroughput, 3f, 1f, 0.2f); internal static float PayloadVec3CullSize => Map(ModConfig.PayloadReducerStrength, 0.005f, 0.04f, 0.1f); internal static float PayloadQuaternionDotThreshold => Map(ModConfig.PayloadReducerStrength, 0.9998f, 0.995f, 0.99f); internal static float PayloadForceRefreshSeconds => Map(ModConfig.PayloadReducerStrength, 0.15f, 1f, 2f); internal static int CompressionThresholdBytes => MapInt(ModConfig.CompressionAggression, 8192, 1024, 256); internal static float CompressionMinUsefulRatio => Map(ModConfig.CompressionAggression, 0.7f, 0.9f, 0.97f); internal static float RpcAoiVisualRadius => Map(ModConfig.RpcAoiAggression, 640f, 192f, 64f); internal static float ClientStutterInitialSyncWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 2f, 10f, 24f); internal static float ClientStutterTeleportWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 1f, 5f, 14f); internal static float ClientStutterFullSnapshotWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.25f, 1.5f, 5f); internal static float ClientStutterCombatWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.35f, 2f, 6f); internal static float ClientStutterShipWindowSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.35f, 2f, 6f); internal static float ClientStutterMaxDelaySeconds => Map(ModConfig.ClientStutterGuardStrength, 6f, 30f, 60f); internal static int ClientStutterMemoryPressureThresholdPercent => MapInt(ModConfig.ClientStutterGuardStrength, 55, 75, 90); internal static int ClientStutterMinimumFreeMemoryMB => MapInt(ModConfig.ClientStutterGuardStrength, 6144, 2048, 1024); internal static float ClientStutterIdleCleanupPollSeconds => Map(ModConfig.ClientStutterGuardStrength, 0.25f, 1f, 4f); internal static float PeerPingEmaHalfLifeSeconds => Map(ModConfig.OwnershipIntensity, 6f, 2.5f, 0.75f); internal static int PeerPingSampleWindow => MapInt(ModConfig.OwnershipIntensity, 120, 60, 20); internal static float PeerQualityMeanWeight => Map(ModConfig.OwnershipIntensity, 0.35f, 0f, 0f); internal static float PeerQualityStdDevWeight => Map(ModConfig.OwnershipIntensity, 0.1f, 0.25f, 0.7f); internal static float PeerQualityJitterWeight => Map(ModConfig.OwnershipIntensity, 0.2f, 0.5f, 1.2f); internal static float PeerQualityEmaWeight => 1f; internal static float MaxCandidatePingMs => Map(ModConfig.OwnershipIntensity, 320f, 220f, 140f); internal static float MaxCandidateJitterMs => Map(ModConfig.OwnershipIntensity, 180f, 100f, 45f); internal static int OwnershipScanBudget => MapInt(ModConfig.OwnershipIntensity, 24, 96, 192); internal static int OwnershipScanStride => MapInt(ModConfig.OwnershipIntensity, 10, 4, 2); internal static float OwnershipScanIntervalSeconds => Map(ModConfig.OwnershipIntensity, 3f, 1f, 0.5f); internal static float OwnershipCandidateRadius => Map(ModConfig.OwnershipIntensity, 80f, 160f, 280f); internal static float OwnerHintCandidateRadius => Map(ModConfig.OwnershipIntensity, 128f, 256f, 512f); internal static float OwnershipDistanceScoreWeight => Map(ModConfig.OwnershipIntensity, 0.4f, 0.2f, 0.06f); internal static float ServerFallbackPenaltyMs => Map(ModConfig.OwnershipIntensity, 450f, 650f, 1000f); internal static float OwnerHintScoreBonusMs => Map(ModConfig.OwnershipIntensity, 20f, 90f, 240f); internal static float OwnerHintLifetimeSeconds => Map(ModConfig.OwnershipIntensity, 2f, 8f, 20f); private static bool IsPositive(ConfigEntry<int> entry) { return Clamp(entry?.Value ?? 0, 0, 100) > 0; } private static float Strength(ConfigEntry<int> entry) { return (float)Clamp(entry?.Value ?? 50, 0, 100) / 100f; } private static float Map(ConfigEntry<int> entry, float safe, float current, float aggressive) { float num = Strength(entry); if (!(num <= 0.5f)) { return Lerp(current, aggressive, (num - 0.5f) * 2f); } return Lerp(safe, current, num * 2f); } private static int MapInt(ConfigEntry<int> entry, int safe, int current, int aggressive) { return (int)Math.Round(Map(entry, safe, current, aggressive)); } private static float Clamp(float value, float min, float max) { if (float.IsNaN(value) || float.IsInfinity(value)) { return min; } return Math.Max(min, Math.Min(max, value)); } private static int Clamp(int value, int min, int max) { return Math.Max(min, Math.Min(max, value)); } private static float Lerp(float a, float b, float t) { return a + (b - a) * Math.Max(0f, Math.Min(1f, t)); } } internal static class ModConfig { private const string GeneralSection = "General"; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> LockServerConfig; internal static ConfigEntry<bool> DebugLogging; internal static ConfigEntry<int> SchedulerThroughput; internal static ConfigEntry<int> PayloadReducerStrength; internal static ConfigEntry<int> OwnershipIntensity; internal static ConfigEntry<int> CompressionAggression; internal static ConfigEntry<int> RpcAoiAggression; internal static ConfigEntry<int> ClientStutterGuardStrength; internal static void Bind(ConfigFile config) { Enabled = ConfigSyncManager.Bind(config, "General", "Enabled", value: true, Description("Master switch.", 100), ConfigSyncScope.ServerSynced); LockServerConfig = ConfigSyncManager.LockServerConfig; DebugLogging = ConfigSyncManager.Bind(config, "General", "DebugLogging", value: false, Description("Diagnostic log output. Enable temporarily to inspect network queues, scheduler stalls, compression cost, and ownership scan cost; keep this off during normal live play.", 80), ConfigSyncScope.ClientLocal); SchedulerThroughput = ConfigSyncManager.Bind(config, "General", "SchedulerThroughput", 35, FeatureSliderDescription("0 disables the adaptive scheduler. 1 keeps package caps close to vanilla while using the gentlest adaptive scheduler; 35 is recommended; 50 is balanced; 100 favors lower latency, higher ZDO throughput, and faster lagging-peer backfill. Steam send-rate is fixed internally at 36 MB/s while SkadiNet is enabled.", 70), ConfigSyncScope.ServerSynced); PayloadReducerStrength = ConfigSyncManager.Bind(config, "General", "PayloadReducerStrength", 30, FeatureSliderDescription("0 disables the payload reducer. 1 favors sync fidelity; 50 is balanced; 100 applies stronger Vector3/Quaternion micro-update reduction.", 60), ConfigSyncScope.ServerSynced); CompressionAggression = ConfigSyncManager.Bind(config, "General", "CompressionAggression", 50, FeatureSliderDescription("0 disables negotiated package compression. 1 compresses only large/high-value packets; 50 is balanced; 100 considers smaller packets and smaller savings.", 50), ConfigSyncScope.ServerSynced); OwnershipIntensity = ConfigSyncManager.Bind(config, "General", "OwnershipIntensity", 45, FeatureSliderDescription("0 disables Profile A adaptive ownership, peer-quality gates, and combat owner hints. 1 is very conservative with low CPU, narrow candidate reach, weak hints, and forgiving peer quality; 45 is recommended; 50 is balanced; 100 scans farther/faster within fixed ownership-switch safety guards, uses stronger hints, and rejects poor ping/jitter candidates more aggressively.", 40), ConfigSyncScope.ServerSynced); ClientStutterGuardStrength = ConfigSyncManager.Bind(config, "General", "ClientStutterGuardStrength", 50, FeatureSliderDescription("0 disables the client stutter guard. 50 is the balanced default. 1 runs cleanup sooner under pressure; 100 protects longer against cleanup stutter.", 30), ConfigSyncScope.ClientLocal); RpcAoiAggression = ConfigSyncManager.Bind(config, "General", "RpcAoiAggression", 35, FeatureSliderDescription("0 disables RPC AoI. 35 is the conservative default. 1 keeps a larger radius for safe visual RPCs; 50 is balanced; 100 routes eligible visual RPCs to smaller local areas. Unknown, unresolved, global, animation, noise, and state-critical RPCs always use vanilla routing.", 20), ConfigSyncScope.ServerSynced); } private static ConfigDescription Description(string description, int order) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown return new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = order } }); } private static ConfigDescription FeatureSliderDescription(string description, int order) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown return new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), new object[1] { new ConfigurationManagerAttributes { Order = order } }); } } internal enum ClientCriticalWindow { InitialSync, Teleport, FullSnapshotBurst, Combat, ShipTravel } internal struct MemoryPressureSnapshot { public bool Known; public ulong TotalMB; public ulong AvailableMB; public int LoadPercent; public override string ToString() { if (!Known) { return "unknown"; } return $"load={LoadPercent}% available={AvailableMB}MB total={TotalMB}MB"; } } internal static class ClientStutterGuard { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct MEMORYSTATUSEX { public uint dwLength; public uint dwMemoryLoad; public ulong ullTotalPhys; public ulong ullAvailPhys; public ulong ullTotalPageFile; public ulong ullAvailPageFile; public ulong ullTotalVirtual; public ulong ullAvailVirtual; public ulong ullAvailExtendedVirtual; } private static readonly Dictionary<ClientCriticalWindow, double> CriticalUntil = new Dictionary<ClientCriticalWindow, double>(); private static Plugin _plugin; private static Coroutine _cleanupCoroutine; private static bool _pendingGc; private static double _firstPendingSince; private static bool _runningCleanup; internal static bool IsActive { get { if (!EffectiveConfig.ClientStutterGuardEnabled) { return false; } if (IsDedicatedLike()) { return false; } return true; } } internal static void Initialize(Plugin plugin) { _plugin = plugin; CriticalUntil.Clear(); _pendingGc = false; _firstPendingSince = 0.0; _runningCleanup = false; EnsureCleanupScheduler(); } internal static void Shutdown() { try { if (_cleanupCoroutine != null && (Object)(object)_plugin != (Object)null) { ((MonoBehaviour)_plugin).StopCoroutine(_cleanupCoroutine); } } catch { } _cleanupCoroutine = null; _plugin = null; } private static bool IsDedicatedLike() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 try { if (NetReflection.IsDedicatedServer()) { return true; } } catch { } try { if (Application.isBatchMode) { return true; } if ((int)SystemInfo.graphicsDeviceType == 4) { return true; } } catch { } return false; } internal static void MarkCriticalWindow(ClientCriticalWindow window, float seconds) { if (IsActive && !(seconds <= 0f)) { double num = Time.realtimeSinceStartupAsDouble + (double)seconds; if (!CriticalUntil.TryGetValue(window, out var value) || value < num) { CriticalUntil[window] = num; } } } internal static void MarkInitialSyncWindow() { MarkCriticalWindow(ClientCriticalWindow.InitialSync, Math.Max(0.1f, EffectiveConfig.ClientStutterInitialSyncWindowSeconds)); } internal static void MarkTeleportWindow() { MarkCriticalWindow(ClientCriticalWindow.Teleport, Math.Max(0.1f, EffectiveConfig.ClientStutterTeleportWindowSeconds)); } internal static void MarkFullSnapshotBurstWindow() { MarkCriticalWindow(ClientCriticalWindow.FullSnapshotBurst, Math.Max(0.1f, EffectiveConfig.ClientStutterFullSnapshotWindowSeconds)); } internal static void MarkCombatWindow() { MarkCriticalWindow(ClientCriticalWindow.Combat, Math.Max(0.1f, EffectiveConfig.ClientStutterCombatWindowSeconds)); } internal static void MarkShipTravelWindow() { MarkCriticalWindow(ClientCriticalWindow.ShipTravel, Math.Max(0.1f, EffectiveConfig.ClientStutterShipWindowSeconds)); } internal static bool TryHandleGcCollect() { if (!IsActive || _runningCleanup) { return true; } if (ShouldDelayCleanup(out var reason)) { RequestPending(reason); return false; } _pendingGc = false; _firstPendingSince = 0.0; return true; } private static void RequestPending(string reason) { _pendingGc = true; if (_firstPendingSince <= 0.0) { _firstPendingSince = Time.realtimeSinceStartupAsDouble; } EnsureCleanupScheduler(); if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)("ClientStutterGuard: deferred GarbageCollect (" + reason + ").")); } } private static void EnsureCleanupScheduler() { if (_cleanupCoroutine == null && !((Object)(object)_plugin == (Object)null) && IsActive) { _cleanupCoroutine = ((MonoBehaviour)_plugin).StartCoroutine(CleanupScheduler()); } } private static IEnumerator CleanupScheduler() { while (true) { float num = Math.Max(0.25f, EffectiveConfig.ClientStutterIdleCleanupPollSeconds); yield return (object)new WaitForSeconds(num); if (IsActive) { TryRunPendingCleanup(); } } } private static bool TryRunPendingCleanup() { if (!_pendingGc) { return false; } if (!IsActive) { return false; } if (_runningCleanup) { return false; } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; bool flag = _firstPendingSince > 0.0 && realtimeSinceStartupAsDouble - _firstPendingSince >= (double)Math.Max(1f, EffectiveConfig.ClientStutterMaxDelaySeconds); if (!flag && TryGetCriticalReason(out var reason)) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)("ClientStutterGuard: pending cleanup waits for " + reason + ".")); } return false; } if (!flag && IsMemoryPlentiful(out var snapshot)) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"ClientStutterGuard: pending cleanup waits; memory plentiful ({snapshot})."); } return false; } _pendingGc = false; _firstPendingSince = 0.0; _runningCleanup = true; try { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)"ClientStutterGuard: running coalesced GC.Collect."); } GC.Collect(); } finally { _runningCleanup = false; } return true; } private static bool ShouldDelayCleanup(out string reason) { reason = "safe window"; if (!IsActive) { return false; } if (_firstPendingSince > 0.0 && Time.realtimeSinceStartupAsDouble - _firstPendingSince >= (double)Math.Max(1f, EffectiveConfig.ClientStutterMaxDelaySeconds)) { reason = "max delay exceeded"; return false; } if (TryGetCriticalReason(out var reason2)) { if (IsMemoryPressure(out var snapshot)) { reason = $"memory pressure overrides critical window ({snapshot})"; return false; } reason = reason2; return true; } if (IsMemoryPlentiful(out var snapshot2)) { reason = $"memory plentiful ({snapshot2})"; return true; } return false; } private static bool TryGetCriticalReason(out string reason) { reason = null; double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; double num = 0.0; ClientCriticalWindow clientCriticalWindow = ClientCriticalWindow.InitialSync; foreach (KeyValuePair<ClientCriticalWindow, double> item in CriticalUntil) { if (item.Value > realtimeSinceStartupAsDouble && item.Value > num) { clientCriticalWindow = item.Key; num = item.Value; } } if (num <= realtimeSinceStartupAsDouble) { return false; } reason = $"{clientCriticalWindow} for {num - realtimeSinceStartupAsDouble:F1}s"; return true; } private static bool IsMemoryPlentiful(out MemoryPressureSnapshot snapshot) { snapshot = GetMemorySnapshot(); if (!snapshot.Known) { return false; } if (snapshot.LoadPercent < Math.Max(1, EffectiveConfig.ClientStutterMemoryPressureThresholdPercent)) { return snapshot.AvailableMB >= (ulong)Math.Max(0, EffectiveConfig.ClientStutterMinimumFreeMemoryMB); } return false; } private static bool IsMemoryPressure(out MemoryPressureSnapshot snapshot) { snapshot = GetMemorySnapshot(); if (!snapshot.Known) { return false; } if (snapshot.LoadPercent < Math.Max(1, EffectiveConfig.ClientStutterMemoryPressureThresholdPercent)) { return snapshot.AvailableMB < (ulong)Math.Max(0, EffectiveConfig.ClientStutterMinimumFreeMemoryMB); } return true; } private static MemoryPressureSnapshot GetMemorySnapshot() { if (TryGetWindowsMemory(out var snapshot)) { return snapshot; } if (TryGetProcMemInfo(out var snapshot2)) { return snapshot2; } return new MemoryPressureSnapshot { Known = false }; } private static bool TryGetWindowsMemory(out MemoryPressureSnapshot snapshot) { snapshot = default(MemoryPressureSnapshot); try { MEMORYSTATUSEX lpBuffer = new MEMORYSTATUSEX { dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX)) }; if (!GlobalMemoryStatusEx(ref lpBuffer)) { return false; } snapshot.Known = true; snapshot.TotalMB = lpBuffer.ullTotalPhys / 1024 / 1024; snapshot.AvailableMB = lpBuffer.ullAvailPhys / 1024 / 1024; snapshot.LoadPercent = (int)lpBuffer.dwMemoryLoad; return snapshot.TotalMB != 0; } catch { return false; } } private static bool TryGetProcMemInfo(out MemoryPressureSnapshot snapshot) { snapshot = default(MemoryPressureSnapshot); try { if (!File.Exists("/proc/meminfo")) { return false; } ulong num = 0uL; ulong num2 = 0uL; string[] array = File.ReadAllLines("/proc/meminfo"); foreach (string text in array) { if (text.StartsWith("MemTotal:", StringComparison.Ordinal)) { num = ParseKb(text); } else if (text.StartsWith("MemAvailable:", StringComparison.Ordinal)) { num2 = ParseKb(text); } } if (num == 0L || num2 == 0L) { return false; } snapshot.Known = true; snapshot.TotalMB = num / 1024; snapshot.AvailableMB = num2 / 1024; snapshot.LoadPercent = (int)Math.Round(100.0 * (1.0 - (double)num2 / (double)num)); return true; } catch { return false; } } private static ulong ParseKb(string line) { string[] array = line.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 1; i < array.Length; i++) { if (ulong.TryParse(array[i], out var result)) { return result; } } return 0uL; } [DllImport("kernel32.dll", SetLastError = true)] private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer); } [HarmonyPatch] internal static class ClientStutterGuardGcCollectPatch { private static IEnumerable<MethodBase> TargetMethods() { MethodInfo[] methods = typeof(GC).GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "Collect") { yield return methodInfo; } } } private static bool Prefix() { return ClientStutterGuard.TryHandleGcCollect(); } } [HarmonyPatch] internal static class ClientStutterGuardZNetConnectionPatch { private static MethodBase TargetMethod() { Type type = ReflectionCache.ZNetType ?? AccessTools.TypeByName("ZNet"); Type type2 = ReflectionCache.ZNetPeerType ?? AccessTools.TypeByName("ZNetPeer"); return AccessTools.Method(type, "OnNewConnection", (!(type2 != null)) ? null : new Type[1] { type2 }, (Type[])null) ?? AccessTools.Method(type, "OnNewConnection", (Type[])null, (Type[])null); } private static void Postfix() { ClientStutterGuard.MarkInitialSyncWindow(); } } [HarmonyPatch] internal static class ClientStutterGuardZdoDataPatch { private static IEnumerable<MethodBase> TargetMethods() { Type type = ReflectionCache.ZDOManType ?? AccessTools.TypeByName("ZDOMan"); if (type == null) { yield break; } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "RPC_ZDOData") { yield return methodInfo; } } } private static void Prefix(object[] __args) { try { if (NetReflection.IsServer() || __args == null || ReflectionCache.ZPackageType == null) { return; } foreach (object obj in __args) { if (obj != null && ReflectionCache.ZPackageType.IsInstanceOfType(obj) && ZPackageTools.Size(obj) >= 32768) { ClientStutterGuard.MarkFullSnapshotBurstWindow(); break; } } } catch { } } } [HarmonyPatch] internal static class ClientStutterGuardLoadingScreenPatch { private static MethodBase TargetMethod() { return AccessTools.Method(AccessTools.TypeByName("ZNetScene"), "InLoadingScreen", (Type[])null, (Type[])null); } private static void Postfix(bool __result) { if (__result) { ClientStutterGuard.MarkTeleportWindow(); } } } [HarmonyPatch] internal static class ClientStutterGuardShipTravelPatch { private static FieldInfo _bodyField; private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("Ship"); _bodyField = ReflectionCache.SilentField(type, "m_body"); return AccessTools.Method(type, "CustomFixedUpdate", (Type[])null, (Type[])null) ?? AccessTools.Method(type, "FixedUpdate", (Type[])null, (Type[])null); } private static void Postfix(object __instance) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (__instance == null) { return; } try { object? obj = _bodyField?.GetValue(__instance); Rigidbody val = (Rigidbody)((obj is Rigidbody) ? obj : null); if ((Object)(object)val != (Object)null) { Vector3 linearVelocity = val.linearVelocity; if (((Vector3)(ref linearVelocity)).sqrMagnitude > 4f) { ClientStutterGuard.MarkShipTravelWindow(); } } } catch { } } } [HarmonyPatch] internal static class MonsterAISetTargetOwnershipPatch { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("MonsterAI"); Type type2 = AccessTools.TypeByName("Character"); return AccessTools.Method(type, "SetTarget", (!(type2 != null)) ? null : new Type[1] { type2 }, (Type[])null) ?? AccessTools.Method("MonsterAI:SetTarget", (Type[])null, (Type[])null); } private static void Postfix(object __instance, object[] __args) { if (__args != null && __args.Length != 0) { ClientStutterGuard.MarkCombatWindow(); OwnershipManager.TryTransferCombatOwnership(__instance, __args[0]); } } } internal enum OwnershipCandidateReason { Generic, CombatTarget, DisconnectedOwner, LongUnownedPersistent } internal sealed class OwnerState { public double LastOwnerChangeTime; public double LastCombatOwnerChangeTime; public double LastSeenUnownedTime; public double LastTouchedTime; public long CombatTargetUid; public double CombatTargetHintTime; } internal sealed class OwnerCandidate { public long Uid; public float Distance; public float Quality; public float Score; } internal static class OwnershipManager { private const int MaxOwnerStates = 50000; private const double OwnerStateTtlSeconds = 600.0; private const double OwnerStatePruneIntervalSeconds = 30.0; private const bool AllowShipOwnership = false; private const bool AllowHealthyOwnerChallenge = false; private const bool AllowServerFallbackForPersistentRecovery = true; private static readonly Dictionary<ZdoIdKey, OwnerState> ByZdoId = new Dictionary<ZdoIdKey, OwnerState>(); private static double _nextProfileAScan; private static double _nextOwnerStatePrune; private static int _sectorCursor; private static int _bucketCursor; private static int _scanPhase; private static bool _loggedUnsupportedSectorShape; private static int _lastScanPeerCount; internal static bool ProfileAEnabled { get { if (!EffectiveConfig.AdaptiveOwnershipEnabled || !ZdoKeyPolicy.Ready || !NetReflection.IsServer()) { return false; } return true; } } internal static void Initialize() { ByZdoId.Clear(); _nextProfileAScan = 0.0; _nextOwnerStatePrune = 0.0; _sectorCursor = 0; _bucketCursor = 0; _scanPhase = 0; _loggedUnsupportedSectorShape = false; _lastScanPeerCount = 0; } internal static bool TryTransferCombatOwnership(object monsterAI, object target) { if (!ProfileAEnabled || !EffectiveConfig.OwnerHintsEnabled) { return false; } if (!GameplayReflection.LooksLikePlayer(target)) { return false; } if (!GameplayReflection.TryGetPlayerId(target, out var id) || id == 0L) { return false; } object zdoFromCharacterLike = GameplayReflection.GetZdoFromCharacterLike(monsterAI); if (zdoFromCharacterLike == null) { return false; } OwnerState ownerState = GetOwnerState(zdoFromCharacterLike); ownerState.CombatTargetUid = id; ownerState.CombatTargetHintTime = Time.realtimeSinceStartupAsDouble; return TryMaybeImproveOwner(zdoFromCharacterLike, OwnershipCandidateReason.CombatTarget); } internal static void ClearPeer(long uid) { if (uid == 0L) { return; } foreach (OwnerState value in ByZdoId.Values) { if (value.CombatTargetUid == uid) { value.CombatTargetUid = 0L; value.CombatTargetHintTime = 0.0; } } } internal static void TickLightweight(object zdoMan) { if (!ProfileAEnabled) { return; } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (!(realtimeSinceStartupAsDouble < _nextProfileAScan)) { _nextProfileAScan = realtimeSinceStartupAsDouble + (double)Math.Max(0.1f, EffectiveConfig.OwnershipScanIntervalSeconds); if (!HasAnyZdoPeer(zdoMan, out var peerCount)) { _lastScanPeerCount = 0; return; } PruneOwnerStatesIfDue(realtimeSinceStartupAsDouble); _lastScanPeerCount = peerCount; TryProfileAScan(zdoMan); } } private static bool HasAnyZdoPeer(object zdoMan, out int peerCount) { peerCount = 0; try { if (zdoMan == null || ReflectionCache.ZDOManPeersField == null) { return false; } object value = ReflectionCache.ZDOManPeersField.GetValue(zdoMan); if (value is ICollection collection) { peerCount = collection.Count; return peerCount > 0; } if (value is IEnumerable enumerable) { { IEnumerator enumerator = enumerable.GetEnumerator(); try { if (enumerator.MoveNext()) { _ = enumerator.Current; peerCount++; return true; } } finally { IDisposable disposable = enumerator as IDisposable; if (disposable != null) { disposable.Dispose(); } } } } } catch { } return false; } private static void TryProfileAScan(object zdoMan) { try { double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; object obj = ReflectionCache.ZDOObjectsBySectorField?.GetValue(zdoMan); if (obj == null) { return; } int visited = 0; int acted = 0; int num = Math.Max(1, EffectiveConfig.OwnershipScanBudget); ScanSectorBuckets(obj, num, ref visited, ref acted); double num2 = Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble; if (ModConfig.DebugLogging.Value && (acted > 0 || visited > 0)) { bool flag = num2 >= 0.008; if (acted > 0 || flag) { Plugin.Log.LogInfo((object)$"Adaptive ownership scan: elapsed={num2 * 1000.0:F2}ms slow={flag} visited={visited}/{num}, ownerChanges={acted}, zdoPeers={_lastScanPeerCount}, sectorCursor={_sectorCursor}, stride={EffectiveConfig.OwnershipScanStride}"); } } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("Adaptive ownership scan failed: " + ex.Message)); } } } private static void ScanSectorBuckets(object sectors, int budget, ref int visited, ref int acted) { if (!(sectors is IList list)) { if (!_loggedUnsupportedSectorShape) { _loggedUnsupportedSectorShape = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Adaptive ownership scan disabled: unsupported m_objectsBySector type " + (sectors?.GetType().FullName ?? "null") + ".")); } } } else { if (list == null || list.Count <= 0) { return; } if (_sectorCursor < 0 || _sectorCursor >= list.Count) { _sectorCursor = 0; } int num = Math.Max(1, EffectiveConfig.OwnershipScanStride); _scanPhase = Math.Max(0, _scanPhase % num); int num2 = 0; while (num2 < list.Count && visited < budget) { if (list[_sectorCursor] is IList { Count: not 0 } list2) { if (_bucketCursor < 0 || _bucketCursor >= list2.Count) { _bucketCursor = 0; } while (_bucketCursor < list2.Count && visited < budget) { int num3 = _bucketCursor++; object obj = list2[num3]; if (obj != null && (num3 + _scanPhase) % num == 0) { visited++; if (TryMaybeImproveOwner(obj, OwnershipCandidateReason.Generic)) { acted++; } } } if (_bucketCursor >= list2.Count) { _bucketCursor = 0; AdvanceSector(list.Count, num); num2++; } } else { _bucketCursor = 0; AdvanceSector(list.Count, num); num2++; } } } } private static void AdvanceSector(int sectorCount, int stride) { _sectorCursor++; if (_sectorCursor >= sectorCount) { _sectorCursor = 0; _scanPhase = (_scanPhase + 1) % Math.Max(1, stride); } } private static bool TryMaybeImproveOwner(object zdo, OwnershipCandidateReason reason) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) if (zdo == null || ReflectionCache.ZDOGetPositionMethod == null) { return false; } if (!ZdoReflection.TryGetOwner(zdo, out var owner)) { owner = 0L; } bool persistent; bool flag = ZdoReflection.TryGetPersistent(zdo, out persistent) && persistent; OwnerState ownerState = GetOwnerState(zdo); double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; bool flag2 = owner != 0L && IsUidCurrentlyConnected(owner); long uid; bool flag3 = owner != 0L && ZdoReflection.TryGetServerSessionId(out uid) && owner == uid; if (owner == 0L) { if (ownerState.LastSeenUnownedTime <= 0.0) { ownerState.LastSeenUnownedTime = realtimeSinceStartupAsDouble; } if (flag && realtimeSinceStartupAsDouble - ownerState.LastSeenUnownedTime >= 2.0) { reason = OwnershipCandidateReason.LongUnownedPersistent; } } else { ownerState.LastSeenUnownedTime = 0.0; } if (!flag2 && owner != 0L && !flag3) { reason = OwnershipCandidateReason.DisconnectedOwner; } if (flag && reason != OwnershipCandidateReason.LongUnownedPersistent && reason != OwnershipCandidateReason.DisconnectedOwner) { return false; } if (reason == OwnershipCandidateReason.Generic && flag2) { return false; } ZdoKeyPolicy.ClassifyOwnership(zdo, out var playerLike, out var shipLike); if (playerLike) { return false; } if (shipLike && reason != OwnershipCandidateReason.DisconnectedOwner) { return false; } Vector3 position = ZdoReflection.GetPosition(zdo, Vector3.zero); OwnerCandidate ownerCandidate = FindBestCandidate(zdo, position, ownerState, reason); if (ownerCandidate == null) { if (reason == OwnershipCandidateReason.LongUnownedPersistent) { return RecoverToServerOwner(zdo, ownerState, owner, reason); } return false; } if (ownerCandidate.Uid == owner) { return false; } float num = ComputeCurrentOwnerScore(owner, position, ownerState, reason, flag2, flag3); if (!IsCandidateBetter(ownerCandidate.Score, num, reason)) { return false; } float num2 = ((reason == OwnershipCandidateReason.CombatTarget) ? 5f : 3f); if (shipLike) { num2 = Math.Max(num2, 8f); } if (realtimeSinceStartupAsDouble - ownerState.LastOwnerChangeTime < (double)num2) { return false; } if (reason == OwnershipCandidateReason.CombatTarget && realtimeSinceStartupAsDouble - ownerState.LastCombatOwnerChangeTime < 5.0) { return false; } if (!ZdoReflection.TrySetOwner(zdo, ownerCandidate.Uid)) { return false; } ownerState.LastOwnerChangeTime = realtimeSinceStartupAsDouble; if (reason == OwnershipCandidateReason.CombatTarget) { ownerState.LastCombatOwnerChangeTime = realtimeSinceStartupAsDouble; } ZdoReflection.ForceSend(zdo); if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"ProfileA owner {reason}: {owner}->{ownerCandidate.Uid}, best={ownerCandidate.Score:F1}, current={num:F1}, q={ownerCandidate.Quality:F1}, dist={ownerCandidate.Distance:F1}"); } return true; } private static OwnerCandidate FindBestCandidate(object zdo, Vector3 zdoPosition, OwnerState state, OwnershipCandidateReason reason) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) OwnerCandidate ownerCandidate = null; float num = ((reason == OwnershipCandidateReason.CombatTarget) ? Math.Max(EffectiveConfig.OwnershipCandidateRadius, EffectiveConfig.OwnerHintCandidateRadius) : EffectiveConfig.OwnershipCandidateRadius); foreach (object item in ZdoReflection.EnumeratePeers(ZdoReflection.ZDOManInstance)) { if (!NetReflection.TryGetPeerUid(item, out var uid) || uid == 0L) { continue; } Vector3 peerRefPos = NetReflection.GetPeerRefPos(item); float num2 = Vector3.Distance(zdoPosition, peerRefPos); if (num2 > num) { continue; } PeerQualityState peerQualityState = PeerQualityMeter.GetByUid(uid) ?? PeerQualityMeter.GetByPeer(item); if (CandidateConnectionAllowed(peerQualityState)) { float num3 = ComputeCandidateScore(uid, num2, peerQualityState, state); if (ownerCandidate == null || num3 < ownerCandidate.Score) { ownerCandidate = new OwnerCandidate { Uid = uid, Distance = num2, Quality = (peerQualityState?.ConnectionQualityMs ?? 999f), Score = num3 }; } } } return ownerCandidate; } private static float ComputeCandidateScore(long uid, float distance, PeerQualityState quality, OwnerState state) { float num = quality?.ConnectionQualityMs ?? 999f; num += distance * Math.Max(0f, EffectiveConfig.OwnershipDistanceScoreWeight); if (uid == state.CombatTargetUid && IsCombatHintFresh(state)) { num -= Math.Max(0f, EffectiveConfig.OwnerHintScoreBonusMs); } return num; } private static float ComputeCurrentOwnerScore(long currentOwner, Vector3 zdoPosition, OwnerState state, OwnershipCandidateReason reason, bool currentConnected, bool currentIsServer) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (currentOwner == 0L) { return 999f; } if (!currentConnected && !currentIsServer) { return 999f; } if (currentIsServer) { return EffectiveConfig.ServerFallbackPenaltyMs; } PeerQualityState byUid = PeerQualityMeter.GetByUid(currentOwner); object obj = FindZdoPeerByUid(currentOwner); float distance = ((obj != null) ? Vector3.Distance(zdoPosition, NetReflection.GetPeerRefPos(obj)) : 0f); return ComputeCandidateScore(currentOwner, distance, byUid, state); } private static bool RecoverToServerOwner(object zdo, OwnerState state, long currentOwner, OwnershipCandidateReason reason) { if (!ZdoReflection.TryGetServerSessionId(out var uid) || uid == 0L) { return false; } if (!ZdoReflection.TrySetOwner(zdo, uid)) { return false; } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; state.LastOwnerChangeTime = realtimeSinceStartupAsDouble; ZdoReflection.ForceSend(zdo); if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"ProfileA recovery to server owner {reason}: {currentOwner}->{uid}"); } return true; } private static bool CandidateConnectionAllowed(PeerQualityState candidate) { if (!EffectiveConfig.PeerQualityEnabled) { return true; } if (candidate == null || !candidate.HasAnySample) { return false; } if (candidate.PingEmaMs > EffectiveConfig.MaxCandidatePingMs) { return false; } if (candidate.PingJitterMs > EffectiveConfig.MaxCandidateJitterMs) { return false; } return true; } private static bool IsCandidateBetter(float candidateScore, float currentScore, OwnershipCandidateReason reason) { if (currentScore <= 0f || currentScore >= 900f) { return true; } float num = Math.Max(0f, 0.15f); float val = Math.Max(0f, 20f); if (reason == OwnershipCandidateReason.DisconnectedOwner || reason == OwnershipCandidateReason.LongUnownedPersistent) { val = Math.Min(val, 5f); } float num2 = Math.Max(val, currentScore * num); return currentScore - candidateScore >= num2; } private static OwnerState GetOwnerState(object zdo) { if (!ZdoReflection.TryGetIdKey(zdo, out var key)) { key = ZdoIdKey.FromRuntimeObject(zdo); } if (!ByZdoId.TryGetValue(key, out var value)) { value = new OwnerState(); ByZdoId[key] = value; } value.LastTouchedTime = Time.realtimeSinceStartupAsDouble; return value; } private static void PruneOwnerStatesIfDue(double now) { if (now < _nextOwnerStatePrune) { return; } _nextOwnerStatePrune = now + 30.0; List<ZdoIdKey> list = new List<ZdoIdKey>(); foreach (KeyValuePair<ZdoIdKey, OwnerState> item in ByZdoId) { OwnerState value = item.Value; if (value == null || (value.LastTouchedTime > 0.0 && now - value.LastTouchedTime >= 600.0)) { list.Add(item.Key); } } foreach (ZdoIdKey item2 in list) { ByZdoId.Remove(item2); } TrimOwnerStatesToLimit(); } private static void TrimOwnerStatesToLimit() { int num = ByZdoId.Count - 50000; if (num > 0) { List<KeyValuePair<ZdoIdKey, OwnerState>> list = new List<KeyValuePair<ZdoIdKey, OwnerState>>(ByZdoId); list.Sort((KeyValuePair<ZdoIdKey, OwnerState> left, KeyValuePair<ZdoIdKey, OwnerState> right) => (left.Value?.LastTouchedTime ?? 0.0).CompareTo(right.Value?.LastTouchedTime ?? 0.0)); for (int num2 = 0; num2 < num; num2++) { ByZdoId.Remove(list[num2].Key); } } } private static bool IsCombatHintFresh(OwnerState state) { if (state == null || state.CombatTargetUid == 0L) { return false; } return Time.realtimeSinceStartupAsDouble - state.CombatTargetHintTime <= (double)Math.Max(0.1f, EffectiveConfig.OwnerHintLifetimeSeconds); } private static bool IsUidCurrentlyConnected(long uid) { return FindZdoPeerByUid(uid) != null; } private static object FindZdoPeerByUid(long uid) { if (uid == 0L) { return null; } foreach (object item in ZdoReflection.EnumeratePeers(ZdoReflection.ZDOManInstance)) { if (NetReflection.TryGetPeerUid(item, out var uid2) && uid2 == uid) { return item; } } return null; } } internal static class CompressionDiagnostics { private const double SummaryIntervalSeconds = 10.0; private static int _encoded; private static int _decoded; private static long _rawEncodeBytes; private static long _compressedEncodeBytes; private static long _compressedDecodeBytes; private static long _rawDecodeBytes; private static double _encodeSeconds; private static double _decodeSeconds; private static double _maxEncodeSeconds; private static double _maxDecodeSeconds; private static double _nextSummaryTime; internal static void RecordEncode(int rawBytes, int compressedBytes, double seconds) { if (ModConfig.DebugLogging.Value) { _encoded++; _rawEncodeBytes += Math.Max(0, rawBytes); _compressedEncodeBytes += Math.Max(0, compressedBytes); _encodeSeconds += Math.Max(0.0, seconds); if (seconds > _maxEncodeSeconds) { _maxEncodeSeconds = seconds; } LogIfDue(); } } internal static void RecordDecode(int compressedBytes, int rawBytes, double seconds) { if (ModConfig.DebugLogging.Value) { _decoded++; _compressedDecodeBytes += Math.Max(0, compressedBytes); _rawDecodeBytes += Math.Max(0, rawBytes); _decodeSeconds += Math.Max(0.0, seconds); if (seconds > _maxDecodeSeconds) { _maxDecodeSeconds = seconds; } LogIfDue(); } } private static void LogIfDue() { double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (!(realtimeSinceStartupAsDouble < _nextSummaryTime)) { _nextSummaryTime = realtimeSinceStartupAsDouble + 10.0; if (_encoded > 0 || _decoded > 0) { float num = ((_rawEncodeBytes > 0) ? ((float)_compressedEncodeBytes / (float)_rawEncodeBytes) : 0f); Plugin.Log.LogDebug((object)($"Compression summary {10.0:F0}s: encoded={_encoded} raw={FormatBytes(_rawEncodeBytes)} compressed={FormatBytes(_compressedEncodeBytes)} ratio={num:F2} " + $"encodeAvgMs={_encodeSeconds / (double)Math.Max(1, _encoded) * 1000.0:F2} encodeMaxMs={_maxEncodeSeconds * 1000.0:F2} " + $"decoded={_decoded} compressedIn={FormatBytes(_compressedDecodeBytes)} rawOut={FormatBytes(_rawDecodeBytes)} " + $"decodeAvgMs={_decodeSeconds / (double)Math.Max(1, _decoded) * 1000.0:F2} decodeMaxMs={_maxDecodeSeconds * 1000.0:F2}")); } _encoded = 0; _decoded = 0; _rawEncodeBytes = 0L; _compressedEncodeBytes = 0L; _compressedDecodeBytes = 0L; _rawDecodeBytes = 0L; _encodeSeconds = 0.0; _decodeSeconds = 0.0; _maxEncodeSeconds = 0.0; _maxDecodeSeconds = 0.0; } } private static string FormatBytes(long bytes) { if (bytes >= 1048576) { return $"{(float)bytes / 1048576f:F1}MB"; } if (bytes >= 1024) { return $"{(float)bytes / 1024f:F1}KB"; } return $"{bytes}B"; } } [HarmonyPatch] internal static class ZSteamSocketSendCompressionPatch { private static MethodBase TargetMethod() { return ReflectionCache.ZSteamSocketSendMethod ?? AccessTools.Method("ZSteamSocket:Send", (Type[])null, (Type[])null); } private static void Prefix(object __instance, ref object __0) { if (!EffectiveConfig.CompressionEnabled || __instance == null || __0 == null || !FeatureNegotiation.IsCompressionActiveForSocket(__instance)) { return; } try { int rawBytes = ZPackageTools.Size(__0); double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (ZPackageTools.TryBuildCompressedPackage(__0, out var compressedPackage)) { __0 = compressedPackage; CompressionDiagnostics.RecordEncode(rawBytes, ZPackageTools.Size(compressedPackage), Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble); } } catch (Exception ex) { FeatureNegotiation.RecordCompressionFailure(__instance); if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("ZSteamSocket.Send compression failed: " + ex.Message)); } } } } [HarmonyPatch] internal static class ZSteamSocketRecvCompressionPatch { private static MethodBase TargetMethod() { return ReflectionCache.ZSteamSocketRecvMethod ?? AccessTools.Method("ZSteamSocket:Recv", (Type[])null, (Type[])null); } private static void Postfix(object __instance, ref object __result) { if (__result == null) { return; } try { int compressedBytes = ZPackageTools.Size(__result); double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (ZPackageTools.TryDecompressPackage(__result, out var rawPackage)) { __result = rawPackage; CompressionDiagnostics.RecordDecode(compressedBytes, ZPackageTools.Size(rawPackage), Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble); } } catch (Exception ex) { FeatureNegotiation.RecordCompressionFailure(__instance); __result = null; if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("ZSteamSocket.Recv decompression failed: " + ex.Message)); } } } } [Flags] internal enum PeerFeatureFlags { None = 0, Compression = 1, RpcAoi = 4 } internal sealed class PeerFeatureState { public object Rpc; public object Socket; public long Uid; public bool RegisteredRpc; public bool HandshakeSent; public bool HandshakeReceived; public PeerFeatureFlags RemoteCapabilities; public int CompressionFailures; } internal static class FeatureNegotiation { internal const int ProtocolVersion = 2; internal const int FeatureMagic = 1179536211; internal const string RpcName = "SkadiNet_Features"; private static readonly object Lock = new object(); private static readonly Dictionary<object, PeerFeatureState> ByRpc = new Dictionary<object, PeerFeatureState>(); private static readonly Dictionary<object, PeerFeatureState> BySocket = new Dictionary<object, PeerFeatureState>(); private static readonly Dictionary<long, PeerFeatureState> ByUid = new Dictionary<long, PeerFeatureState>(); internal static PeerFeatureFlags LocalCapabilities => PeerFeatureFlags.Compression | PeerFeatureFlags.RpcAoi; internal static void Initialize() { lock (Lock) { ByRpc.Clear(); BySocket.Clear(); ByUid.Clear(); } } internal static PeerFeatureState GetOrCreateByRpc(object rpc, long uid = 0L) { if (rpc == null) { return null; } lock (Lock) { if (!ByRpc.TryGetValue(rpc, out var value)) { value = new PeerFeatureState { Rpc = rpc, Uid = uid }; ByRpc[rpc] = value; } if (uid != 0L) { value.Uid = uid; ByUid[uid] = value; } object socketFromRpc = NetReflection.GetSocketFromRpc(rpc); if (socketFromRpc != null) { value.Socket = socketFromRpc; BySocket[socketFromRpc] = value; } return value; } } internal static PeerFeatureState GetBySocket(object socket) { if (socket == null) { return null; } lock (Lock) { BySocket.TryGetValue(socket, out var value); return value; } } internal static PeerFeatureState GetByUid(long uid) { lock (Lock) { ByUid.TryGetValue(uid, out var value); return value; } } internal static void ClearPeer(object peerOrRpc, long uid) { object obj = NetReflection.GetPeerRpc(peerOrRpc); if (obj == null && peerOrRpc != null && ReflectionCache.ZRpcType != null && ReflectionCache.ZRpcType.IsInstanceOfType(peerOrRpc)) { obj = peerOrRpc; } object socketFromRpc = NetReflection.GetSocketFromRpc(obj); lock (Lock) { PeerFeatureState value = null; if (uid != 0L) { ByUid.TryGetValue(uid, out value); } if (value == null && obj != null) { ByRpc.TryGetValue(obj, out value); } if (value == null && socketFromRpc != null) { BySocket.TryGetValue(socketFromRpc, out value); } if (uid != 0L) { ByUid.Remove(uid); } if (obj != null) { ByRpc.Remove(obj); } if (socketFromRpc != null) { BySocket.Remove(socketFromRpc); } if (value != null) { if (value.Uid != 0L) { ByUid.Remove(value.Uid); } if (value.Rpc != null) { ByRpc.Remove(value.Rpc); } if (value.Socket != null) { BySocket.Remove(value.Socket); } RemoveState(ByRpc, value); RemoveState(BySocket, value); RemoveState(ByUid, value); } } } private static void RemoveState<TKey>(Dictionary<TKey, PeerFeatureState> map, PeerFeatureState state) { if (state == null || map.Count == 0) { return; } List<TKey> list = new List<TKey>(); foreach (KeyValuePair<TKey, PeerFeatureState> item in map) { if (item.Value == state) { list.Add(item.Key); } } foreach (TKey item2 in list) { map.Remove(item2); } } internal static bool IsCompressionActiveForSocket(object socket) { if (!EffectiveConfig.CompressionEnabled) { return false; } PeerFeatureState bySocket = GetBySocket(socket); if (bySocket == null) { return false; } if (bySocket.CompressionFailures >= 1) { return false; } if (bySocket.HandshakeReceived) { return Supports(bySocket, PeerFeatureFlags.Compression); } return false; } internal static bool IsRpcAoiActiveForUid(long uid) { if (!EffectiveConfig.RpcAoiEnabled) { return false; } PeerFeatureState byUid = GetByUid(uid); if (byUid == null) { return true; } if (byUid.HandshakeReceived) { return Supports(byUid, PeerFeatureFlags.RpcAoi); } return true; } internal static void RecordCompressionFailure(object socket) { PeerFeatureState bySocket = GetBySocket(socket); if (bySocket != null) { bySocket.CompressionFailures++; } } internal static void OnNewConnection(object znetPeer) { if (ModConfig.Enabled.Value) { object peerRpc = NetReflection.GetPeerRpc(znetPeer); if (peerRpc != null) { NetReflection.TryGetPeerUid(znetPeer, out var uid); PeerFeatureState orCreateByRpc = GetOrCreateByRpc(peerRpc, uid); RegisterRpc(peerRpc, orCreateByRpc); SendHello(peerRpc, orCreateByRpc); } } } private static void RegisterRpc(object rpc, PeerFeatureState state) { if (rpc == null || state == null || state.RegisteredRpc) { return; } try { if (!(ReflectionCache.ZRpcRegisterGenericPackageMethod == null) && !(ReflectionCache.ZRpcType == null) && !(ReflectionCache.ZPackageType == null)) { MethodInfo method = typeof(FeatureNegotiation).GetMethod("RPC_Features_Generic", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(ReflectionCache.ZRpcType, ReflectionCache.ZPackageType); Delegate obj = Delegate.CreateDelegate(typeof(Action<, >).MakeGenericType(ReflectionCache.ZRpcType, ReflectionCache.ZPackageType), method); ReflectionCache.ZRpcRegisterGenericPackageMethod.Invoke(rpc, new object[2] { "SkadiNet_Features", obj }); state.RegisteredRpc = true; } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("Could not register SkadiNet_Features: " + ex.Message)); } } } private static void SendHello(object rpc, PeerFeatureState state) { if (rpc == null || state == null || state.HandshakeSent) { return; } try { object obj = ZPackageTools.NewPackage(); ZPackageTools.WriteInt(obj, 1179536211); ZPackageTools.WriteInt(obj, 2); ZPackageTools.WriteInt(obj, (int)LocalCapabilities); ZPackageTools.WriteString(obj, "1.0.4"); ReflectionCache.ZRpcInvokeMethod?.Invoke(rpc, new object[2] { "SkadiNet_Features", new object[1] { obj } }); state.HandshakeSent = true; } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("Could not send SkadiNet feature handshake: " + ex.Message)); } } } private static void RPC_Features_Generic<TRpc, TPkg>(TRpc rpc, TPkg pkg) { RPC_Features(rpc, pkg); } private static void RPC_Features(object rpc, object pkg) { try { PeerFeatureState orCreateByRpc = GetOrCreateByRpc(rpc, 0L); if (orCreateByRpc == null || pkg == null) { return; } int pos = ZPackageTools.GetPos(pkg); ZPackageTools.SetPos(pkg, 0); if (ZPackageTools.ReadInt(pkg) != 1179536211) { ZPackageTools.SetPos(pkg, pos); return; } int num = ZPackageTools.ReadInt(pkg); int remoteCapabilities = ZPackageTools.ReadInt(pkg); string text = ZPackageTools.ReadString(pkg); orCreateByRpc.RemoteCapabilities = (PeerFeatureFlags)remoteCapabilities; orCreateByRpc.HandshakeReceived = num >= 1; if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"SkadiNet feature handshake: protocol={num}, capabilities={orCreateByRpc.RemoteCapabilities}, version={text}, compression={EffectiveConfig.CompressionEnabled && Supports(orCreateByRpc, PeerFeatureFlags.Compression)}, rpcAoi={EffectiveConfig.RpcAoiEnabled && Supports(orCreateByRpc, PeerFeatureFlags.RpcAoi)}"); } if (!orCreateByRpc.HandshakeSent) { SendHello(rpc, orCreateByRpc); } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("SkadiNet feature handshake receive failed: " + ex.Message)); } } } private static bool Supports(PeerFeatureState state, PeerFeatureFlags flag) { if (state != null) { return (state.RemoteCapabilities & flag) != 0; } return false; } } [HarmonyPatch] internal static class ZNetOnNewConnectionFeatureHandshakePatch { private static MethodBase TargetMethod() { Type type = ReflectionCache.ZNetType ?? AccessTools.TypeByName("ZNet"); Type type2 = ReflectionCache.ZNetPeerType ?? AccessTools.TypeByName("ZNetPeer"); return AccessTools.Method(type, "OnNewConnection", (!(type2 != null)) ? null : new Type[1] { type2 }, (Type[])null) ?? AccessTools.Method(type, "OnNewConnection", (Type[])null, (Type[])null); } private static void Postfix(object __0) { FeatureNegotiation.OnNewConnection(__0); } } internal static class PeerLifecycle { internal static void ClearDisconnectedPeer(object zdoPeer) { long uid = 0L; NetReflection.TryGetPeerUid(zdoPeer, out uid); FeatureNegotiation.ClearPeer(zdoPeer, uid); PeerQualityMeter.ClearPeer(zdoPeer, uid); OwnershipManager.ClearPeer(uid); ZDOManSendSchedulerPatch.ClearPeer(uid); } } [HarmonyPatch] internal static class PeerLifecycleDisconnectPatch { private static IEnumerable<MethodBase> TargetMethods() { HashSet<MethodBase> hashSet = new HashSet<MethodBase>(); AddNamedMethods(hashSet, ReflectionCache.ZNetType ?? AccessTools.TypeByName("ZNet"), "Disconnect", "OnDisconnected", "OnPeerDisconnected", "RemovePeer", "RPC_Disconnect"); AddNamedMethods(hashSet, ReflectionCache.ZDOManType ?? AccessTools.TypeByName("ZDOMan"), "RemovePeer", "RemoveZdoPeer", "DisconnectPeer"); foreach (MethodBase item in hashSet) { yield return item; } } private static void AddNamedMethods(HashSet<MethodBase> targets, Type type, params string[] names) { if (targets == null || type == null || names == null) { return; } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { for (int j = 0; j < names.Length; j++) { if (methodInfo.Name == names[j]) { targets.Add(methodInfo); break; } } } } private static void Postfix(object[] __args) { if (__args != null) { for (int i = 0; i < __args.Length; i++) { TryClear(__args[i]); } } } private static void TryClear(object candidate) { if (candidate != null) { long uid; bool num = NetReflection.TryGetPeerUid(candidate, out uid) && uid != 0; bool flag = NetReflection.GetPeerRpc(candidate) != null; bool flag2 = ReflectionCache.ZRpcType != null && ReflectionCache.ZRpcType.IsInstanceOfType(candidate); if (num || flag || flag2) { PeerLifecycle.ClearDisconnectedPeer(candidate); } } } } internal sealed class PeerQualityState { public object Rpc; public long Uid; public readonly Queue<float> Samples = new Queue<float>(); public float LastPingMs; public float PingEmaMs; public float PingMeanMs; public float PingStdDevMs; public float PingJitterMs; public float ConnectionQualityMs; public double LastUpdateTime; public bool HasAnySample; } internal static class PeerQualityMeter { private static readonly Dictionary<object, PeerQualityState> ByRpc = new Dictionary<object, PeerQualityState>(); private static readonly Dictionary<long, PeerQualityState> ByUid = new Dictionary<long, PeerQualityState>(); private static readonly Dictionary<Type, Func<object, int>> SocketPingReaders = new Dictionary<Type, Func<object, int>>(); private static readonly object Lock = new object(); private static Func<object, float> _zrpcPingGetter; internal static void Initialize() { lock (Lock) { ByRpc.Clear(); ByUid.Clear(); SocketPingReaders.Clear(); } if (ReflectionCache.ZRpcType != null) { _zrpcPingGetter = ReflectionDelegateFactory.SingleFieldGetter(ReflectionCache.SilentField(ReflectionCache.ZRpcType, "m_ping")); } } internal static PeerQualityState GetOrCreateByRpc(object rpc, long uid = 0L) { if (rpc == null) { return null; } lock (Lock) { if (!ByRpc.TryGetValue(rpc, out var value)) { value = new PeerQualityState { Rpc = rpc, Uid = uid, PingEmaMs = 999f, ConnectionQualityMs = 999f }; ByRpc[rpc] = value; } else if (value.Rpc == null) { value.Rpc = rpc; } if (uid != 0L) { value.Uid = uid; ByUid[uid] = value; } return value; } } internal static void ClearPeer(object peerOrRpc, long uid) { object obj = NetReflection.GetPeerRpc(peerOrRpc); if (obj == null && peerOrRpc != null && ReflectionCache.ZRpcType != null && ReflectionCache.ZRpcType.IsInstanceOfType(peerOrRpc)) { obj = peerOrRpc; } lock (Lock) { PeerQualityState value = null; if (uid != 0L) { ByUid.TryGetValue(uid, out value); } if (value == null && obj != null) { ByRpc.TryGetValue(obj, out value); } if (uid != 0L) { ByUid.Remove(uid); } if (obj != null) { ByRpc.Remove(obj); } if (value != null) { if (value.Uid != 0L) { ByUid.Remove(value.Uid); } if (value.Rpc != null) { ByRpc.Remove(value.Rpc); } RemoveState(ByRpc, value); RemoveState(ByUid, value); } } } private static void RemoveState<TKey>(Dictionary<TKey, PeerQualityState> map, PeerQualityState state) { if (state == null || map.Count == 0) { return; } List<TKey> list = new List<TKey>(); foreach (KeyValuePair<TKey, PeerQualityState> item in map) { if (item.Value == state) { list.Add(item.Key); } } foreach (TKey item2 in list) { map.Remove(item2); } } internal static PeerQualityState GetByUid(long uid) { lock (Lock) { ByUid.TryGetValue(uid, out var value); return value; } } internal static PeerQualityState GetByPeer(object zdoPeer) { object peerRpc = NetReflection.GetPeerRpc(zdoPeer); NetReflection.TryGetPeerUid(zdoPeer, out var uid); return GetOrCreateByRpc(peerRpc, uid); } internal static void UpdateFromRpc(object rpc) { UpdateFromRpcCore(rpc, 0L); } private static PeerQualityState UpdateFromRpcCore(object rpc, long uid) { if (!EffectiveConfig.PeerQualityEnabled || rpc == null) { return null; } float num = TryReadPingMs(rpc); if (num <= 0f || float.IsNaN(num) || float.IsInfinity(num)) { return GetOrCreateByRpc(rpc, uid); } if (uid == 0L) { NetReflection.TryGetUidFromPeerObject(rpc, out uid); } PeerQualityState orCreateByRpc = GetOrCreateByRpc(rpc, uid); if (orCreateByRpc == null) { return null; } double num2 = Time.realtimeSinceStartup; lock (Lock) { double num3 = ((orCreateByRpc.LastUpdateTime > 0.0) ? Math.Max(0.001, num2 - orCreateByRpc.LastUpdateTime) : 0.05); orCreateByRpc.LastUpdateTime = num2; float lastPingMs = orCreateByRpc.LastPingMs; orCreateByRpc.LastPingMs = num; double num4 = (double)Math.Max(0.1f, EffectiveConfig.PeerPingEmaHalfLifeSeconds) / Math.Log(2.0); float num5 = (float)(1.0 - Math.Exp((0.0 - num3) / num4)); orCreateByRpc.PingEmaMs = (orCreateByRpc.HasAnySample ? (num5 * num + (1f - num5) * orCreateByRpc.PingEmaMs) : num); orCreateByRpc.HasAnySample = true; orCreateByRpc.Samples.Enqueue(num); while (orCreateByRpc.Samples.Count > Math.Max(4, EffectiveConfig.PeerPingSampleWindow)) { orCreateByRpc.Samples.Dequeue(); } RecalculateWindowStats(orCreateByRpc, lastPingMs); orCreateByRpc.ConnectionQualityMs = orCreateByRpc.PingMeanMs * EffectiveConfig.PeerQualityMeanWeight + orCreateByRpc.PingStdDevMs * EffectiveConfig.PeerQualityStdDevWeight + orCreateByRpc.PingJitterMs * EffectiveConfig.PeerQualityJitterWeight + orCreateByRpc.PingEmaMs * EffectiveConfig.PeerQualityEmaWeight; return orCreateByRpc; } } private static float TryReadPingMs(object rpc) { if (TryReadSocketPingMs(NetReflection.GetSocketFromRpc(rpc), out var pingMs)) { return pingMs; } try { if (_zrpcPingGetter == null) { return -1f; } float num = _zrpcPingGetter(rpc); return (num < 10f) ? (num * 1000f) : num; } catch { } return -1f; } private static bool TryReadSocketPingMs(object socket, out float pingMs) { pingMs = 0f; if (socket == null) { return false; } try { Func<object, int> socketPingReader = GetSocketPingReader(socket.GetType()); if (socketPingReader == null) { return false; } int num = socketPingReader(socket); if (num > 0) { pingMs = num; return true; } } catch { } return false; } private static Func<object, int> GetSocketPingReader(Type socketType) { if (socketType == null) { return null; } lock (Lock) { if (SocketPingReaders.TryGetValue(socketType, out var value)) { return value; } Func<object, int> func = null; MethodInfo[] methods = socketType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "GetConnectionQuality")) { func = CreateSocketPingReader(methodInfo); if (func != null) { break; } } } SocketPingReaders[socketType] = func; return func; } } private static Func<object, int> CreateSocketPingReader(MethodInfo method) { try { if (method == null || method.IsStatic || method.ContainsGenericParameters || method.ReturnType != typeof(void)) { return null; } ParameterInfo[] parameters = method.GetParameters(); Type type = typeof(float).MakeByRefType(); Type type2 = typeof(int).MakeByRefType(); if (parameters.Length != 5 || parameters[0].ParameterType != type || parameters[1].ParameterType != type || parameters[2].ParameterType != type2 || parameters[3].ParameterType != type || parameters[4].ParameterType != type) { return null; } ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "socket"); ParameterExpression parameterExpression2 = Expression.Variable(typeof(float), "localQuality"); ParameterExpression parameterExpression3 = Expression.Variable(typeof(float), "remoteQuality"); ParameterExpression parameterExpression4 = Expression.Variable(typeof(int), "ping"); ParameterExpression parameterExpression5 = Expression.Variable(typeof(float), "outByteSec"); ParameterExpression parameterExpression6 = Expression.Variable(typeof(float), "inByteSec"); MethodCallExpression methodCallExpression = Expression.Call(Expression.Convert(parameterExpression, method.DeclaringType), method, parameterExpression2, parameterExpression3, parameterExpression4, parameterExpression5, parameterExpression6); return Expression.Lambda<Func<object, int>>(Expression.Block(new ParameterExpression[5] { parameterExpression2, parameterExpression3, parameterExpression4, parameterExpression5, parameterExpression6 }, methodCallExpression, parameterExpression4), new ParameterExpression[1] { parameterExpression }).Compile(); } catch { return null; } } private static void RecalculateWindowStats(PeerQualityState state, float previousLast) { int count = state.Samples.Count; if (count == 0) { return; } float num = 0f; foreach (float sample in state.Samples) { num += sample; } float num2 = num / (float)count; float num3 = 0f; foreach (float sample2 in state.Samples) { float num4 = sample2 - num2; num3 += num4 * num4; } state.PingMeanMs = num2; state.PingStdDevMs = (float)Math.Sqrt(num3 / (float)Math.Max(1, count)); state.PingJitterMs = ((previousLast > 0f) ? Math.Abs(state.LastPingMs - previousLast) : 0f); } } [HarmonyPatch] internal static class ZRpcReceivePingPatch { private static MethodBase TargetMethod() { return AccessTools.Method(ReflectionCache.ZRpcType ?? AccessTools.TypeByName("ZRpc"), "ReceivePing", (Type[])null, (Type[])null); } private static void Postfix(object __instance) { PeerQualityMeter.UpdateFromRpc(__instance); } } internal enum RpcAoiKind { Visual, StateCritical, Unknown } internal static class RpcAoiRouter { private static readonly Dictionary<int, RpcAoiKind> Known = new Dictionary<int, RpcAoiKind>(); private static int _damageTextHash; private static int _talkerSayHash; private static bool _initialized; internal static void Initialize() { Known.Clear(); _damageTextHash = StableHash("DamageText"); _talkerSayHash = StableHash("TalkerSay"); Add(_damageTextHash, RpcAoiKind.Visual); Add(_talkerSayHash, RpcAoiKind.Visual); Add("HealthChanged", RpcAoiKind.StateCritical); Add("WNTHealthChanged", RpcAoiKind.StateCritical); Add("SetTarget", RpcAoiKind.StateCritical); Add("TriggerOnDeath", RpcAoiKind.StateCritical); Add("SpawnedZone", RpcAoiKind.StateCritical); _initialized = true; } private static void Add(string name, RpcAoiKind kind) { Add(StableHash(name), kind); } private static void Add(int methodHash, RpcAoiKind kind) { Known[methodHash] = kind; } internal static bool TryRoute(object routedRpc, object package) { //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) if (!EffectiveConfig.RpcAoiEnabled || !NetReflection.IsServer()) { return false; } if (routedRpc == null || package == null || RpcReflection.RoutedRPCDataType == null) { return false; } if (!_initialized) { Initialize(); } try { int pos = ZPackageTools.GetPos(package); ZPackageTools.SetPos(package, 0); object routedData = RpcReflection.CreateRoutedRpcData(); bool num = RpcReflection.DeserializeRoutedRpcData(routedData, package); ZPackageTools.SetPos(package, pos); if (!num) { return false; } int methodHash = RpcReflection.GetMethodHash(routedData); RpcAoiKind value; RpcAoiKind rpcAoiKind = (Known.TryGetValue(methodHash, out value) ? value : RpcAoiKind.Unknown); switch (rpcAoiKind) { case RpcAoiKind.Unknown: return false; case RpcAoiKind.StateCritical: return false; default: { if (!IsKindEnabled(rpcAoiKind, methodHash)) { return false; } if (RpcReflection.GetTargetPeerId(routedData) != 0L) { return false; } float num2 = RadiusFor(rpcAoiKind); if (num2 <= 0f) { return false; } if (!TryResolveZdoOrigin(routedData, out var origin)) { return false; } List<long> list = new List<long>(); int num3 = 0; foreach (object item in ZdoReflection.EnumeratePeers(ZdoReflection.ZDOManInstance)) { if (!NetReflection.TryGetPeerUid(item, out var uid) || uid == 0L) { continue; } num3++; Vector3 val = NetReflection.GetPeerRefPos(item) - origin; if (!(((Vector3)(ref val)).sqrMagnitude > num2 * num2)) { if (!FeatureNegotiation.IsRpcAoiActiveForUid(uid)) { return false; } list.Add(uid); } } if (list.Count <= 0) { return false; } if (list.Count >= num3) { return false; } int num4 = 0; foreach (long item2 in list) { RpcReflection.SetTargetPeerId(routedData, item2); if (RpcReflection.RouteRpc(routedRpc, routedData)) { num4++; } } if (num4 <= 0) { return false; } if (num4 < list.Count && ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"RPC AoI routed partial hash={methodHash} kind={rpcAoiKind} recipients={num4}/{list.Count} radius={num2}; vanilla fallback is suppressed to avoid duplicates for already-routed visual RPCs."); } if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"RPC AoI routed hash={methodHash} kind={rpcAoiKind} recipients={num4} radius={num2}"); } return true; } } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("RPC AoI failed; vanilla route used: " + ex.Message)); } return false; } } private static bool IsKindEnabled(RpcAoiKind kind, int methodHash) { if (kind != RpcAoiKind.Visual) { return false; } if (methodHash != _damageTextHash) { return methodHash == _talkerSayHash; } return true; } private static float RadiusFor(RpcAoiKind kind) { return Math.Max(1f, EffectiveConfig.RpcAoiVisualRadius); } private static bool TryResolveZdoOrigin(object routedData, out Vector3 origin) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) origin = Vector3.zero; try { object targetZdo = RpcReflection.GetTargetZdo(routedData); if (targetZdo != null && !ZdoReflection.IsIdNone(targetZdo)) { object byId = ZdoReflection.GetById(targetZdo); if (byId != null) { origin = ZdoReflection.GetPosition(byId, Vector3.zero); return true; } } } catch { } return false; } internal static int StableHash(string text) { int num = 5381; int num2 = num; for (int i = 0; i < text.Length; i += 2) { num = ((num << 5) + num) ^ text[i]; if (i == text.Length - 1) { break; } num2 = ((num2 << 5) + num2) ^ text[i + 1]; } return num + num2 * 1566083941; } } [HarmonyPatch] internal static class ZRoutedRpcAoiPatch { private static MethodBase TargetMethod() { Type type = ReflectionCache.ZRoutedRpcType ?? AccessTools.TypeByName("ZRoutedRpc"); Type type2 = ReflectionCache.ZRpcType ?? AccessTools.TypeByName("ZRpc"); Type type3 = ReflectionCache.ZPackageType ?? AccessTools.TypeByName("ZPackage"); return AccessTools.Method(type, "RPC_RoutedRPC", (!(type2 != null) || !(type3 != null)) ? null : new Type[2] { type2, type3 }, (Type[])null) ?? AccessTools.Method(type, "RPC_RoutedRPC", (Type[])null, (Type[])null); } private static bool Prefix(object __instance, object __0, object __1) { return !RpcAoiRouter.TryRoute(__instance, __1); } } [HarmonyPatch] internal static class ZSteamSocketRegisterGlobalCallbacksRatePatch { private const int VanillaSteamSendRateBytes = 153600; public static int SteamSendRateBytes { get { if (!ModConfig.Enabled.Value) { return 153600; } return 36000000; } } private static MethodBase TargetMethod() { return AccessTools.Method("ZSteamSocket:RegisterGlobalCallbacks", (Type[])null, (Type[])null); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); int num = 0; foreach (CodeInstruction item in list) { if (item.opcode == OpCodes.Ldc_I4 && item.operand is int num2 && num2 == 153600) { num++; } } if (num != 1) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"Skipped Steam send-rate transpiler: expected one {153600} constant, found {num}."); } return list; } for (int i = 0; i < list.Count; i++) { CodeInstruction val = list[i]; if (val.opcode == OpCodes.Ldc_I4 && val.operand is int num3 && num3 == 153600) { list[i] = Replacement(val); } } return list; } private static CodeInstruction Replacement(CodeInstruction original) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown CodeInstruction val = new CodeInstruction(OpCodes.Call, (object)AccessTools.PropertyGetter(typeof(ZSteamSocketRegisterGlobalCallbacksRatePatch), "SteamSendRateBytes")); val.labels.AddRange(original.labels); val.blocks.AddRange(original.blocks); return val; } } internal static class ZPackageTools { internal const int CompressionMagic = 1129204563; internal const int CompressionProtocol = 1; internal const int CompressionAlgoDeflate = 1; private const int MaxDecompressedPackageBytes = 67108864; private const int InflateBufferBytes = 81920; private static ConstructorInfo _ctorEmpty; private static ConstructorInfo _ctorBytes; private static MethodInfo _writeInt; private static MethodInfo _writeString; private static MethodInfo _writeByteArray; private static MethodInfo _readInt; private static MethodInfo _readByteArray; private static MethodInfo _readString; private static MethodInfo _getArray; private static MethodInfo _size; private static MethodInfo _setPos; private static MethodInfo _getPos; private static MethodInfo _loadBytes; internal static void Initialize() { Type zPackageType = ReflectionCache.ZPackageType; if (!(zPackageType == null)) { _ctorEmpty = AccessTools.Constructor(zPackageType, Type.EmptyTypes, false); _ctorBytes = AccessTools.Constructor(zPackageType, new Type[1] { typeof(byte[]) }, false); _writeInt = AccessTools.Method(zPackageType, "Write", new Type[1] { typeof(int) }, (Type[])null); _writeString = AccessTools.Method(zPackageType, "Write", new Type[1] { typeof(string) }, (Type[])null); _writeByteArray = AccessTools.Method(zPackageType, "Write", new Type[1] { typeof(byte[]) }, (Type[])null); _readInt = AccessTools.Method(zPackageType, "ReadInt", (Type[])null, (Type[])null); _readByteArray = AccessTools.Method(zPackageType, "ReadByteArray", Type.EmptyTypes, (Type[])null); _readString = AccessTools.Method(zPackageType, "ReadString", (Type[])null, (Type[])null); _getArray = AccessTools.Method(zPackageType, "GetArray", (Type[])null, (Type[])null); _size = AccessTools.Method(zPackageType, "Size", (Type[])null, (Type[])null); _setPos = AccessTools.Method(zPackageType, "SetPos", new Type[1] { typeof(int) }, (Type[])null); _getPos = AccessTools.Method(zPackageType, "GetPos", (Type[])null, (Type[])null); _loadBytes = AccessTools.Method(zPackageType, "Load", new Type[1] { typeof(byte[]) }, (Type[])null); } } internal static object NewPackage() { return _ctorEmpty?.Invoke(null) ?? Activator.CreateInstance(ReflectionCache.ZPackageType); } internal static object NewPackage(byte[] bytes) { if (_ctorBytes != null) { return _ctorBytes.Invoke(new object[1] { bytes }); } object obj = NewPackage(); _loadBytes?.Invoke(obj, new object[1] { bytes }); return obj; } internal static int Size(object pkg) { try { return (_size != null && pkg != null) ? ((int)_size.Invoke(pkg, null)) : 0; } catch { return 0; } } internal static byte[] GetArray(object pkg) { try { return (_getArray?.Invoke(pkg, null) as byte[]) ?? Array.Empty<byte>(); } catch { return Array.Empty<byte>(); } } internal static int GetPos(object pkg) { try { return (_getPos != null) ? ((int)_getPos.Invoke(pkg, null)) : 0; } catch { return 0; } } internal static void SetPos(object pkg, int pos) { try { _setPos?.Invoke(pkg, new object[1] { pos }); } catch { } } internal static void WriteInt(object pkg, int v) { _writeInt.Invoke(pkg, new object[1] { v }); } internal static void WriteString(object pkg, string v) { _writeString.Invoke(pkg, new object[1] { v ?? string.Empty }); } internal static void WriteByteArray(object pkg, byte[] v) { _writeByteArray.Invoke(pkg, new object[1] { v ?? Array.Empty<byte>() }); } internal static int ReadInt(object pkg) { return (int)_readInt.Invoke(pkg, null); } internal static string ReadString(object pkg) { return (string)_readString.Invoke(pkg, null); } internal static byte[] ReadByteArray(object pkg) { return (_readByteArray.Invoke(pkg, null) as byte[]) ?? Array.Empty<byte>(); } internal static bool TryBuildCompressedPackage(object original, out object compressedPackage) { compressedPackage = null; byte[] array = GetArray(original); if (array == null || array.Length < Math.Max(64, EffectiveConfig.CompressionThresholdBytes) || array.Length > 67108864) { return false; } if (LooksCompressed(original)) { return false; } byte[] array2 = Deflate(array); if (array2 == null || array2.Length == 0) { return false; } if ((float)array2.Length / (float)Math.Max(1, array.Length) >= EffectiveConfig.CompressionMinUsefulRatio) { return false; } object obj = NewPackage(); WriteInt(obj, 1129204563); WriteInt(obj, 1); WriteInt(obj, 1); WriteInt(obj, array.Length); WriteByteArray(obj, array2); compressedPackage = obj; return true; } internal static bool TryDecompressPackage(object maybeCompressed, out object rawPackage) { rawPackage = maybeCompressed; if (maybeCompressed == null) { return false; } if (Size(maybeCompressed) < 4) { return false; } int pos = GetPos(maybeCompressed); try { SetPos(maybeCompressed, 0); if (ReadInt(maybeCompressed) != 1129204563) { return false; } int num = ReadInt(maybeCompressed); int num2 = ReadInt(maybeCompressed); int num3 = ReadInt(maybeCompressed); byte[] array = ReadByteArray(maybeCompressed); if (num != 1 || num2 != 1 || num3 <= 0 || num3 > 67108864) { throw new InvalidDataException($"Unsupported SkadiNet compression header protocol={num}, algo={num2}, size={num3}"); } if (array == null || array.Length == 0) { throw new InvalidDataException("SkadiNet compression payload is empty."); } byte[] bytes = Inflate(array, num3); rawPackage = NewPackage(bytes); return true; } finally { SetPos(maybeCompressed, pos); } } internal static bool LooksCompressed(object pkg) { if (pkg == null) { return false; } int pos = GetPos(pkg); try { if (Size(pkg) < 16) { return false; } SetPos(pkg, 0); return ReadInt(pkg) == 1129204563; } catch { return false; } finally { SetPos(pkg, pos); } } private static byte[] Deflate(byte[] raw) { using MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Fastest, leaveOpen: true)) { deflateStream.Write(raw, 0, raw.Length); } return memoryStream.ToArray(); } private static byte[] Inflate(byte[] compressed, int expectedSize) { using MemoryStream stream = new MemoryStream(compressed); using DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(Math.Min(expectedSize, 1048576)); byte[] array = new byte[81920]; int num = 0; int num2; while ((num2 = deflateStream.Read(array, 0, array.Length)) > 0) { num += num2; if (num > expectedSize || num > 67108864) { throw new InvalidDataException($"Inflated payload exceeds declared size {expectedSize}."); } memoryStream.Write(array, 0, num2); } byte[] array2 = memoryStream.ToArray(); if (array2.Length != expectedSize) { throw new InvalidDataException($"Inflated size mismatch expected={expectedSize}, actual={array2.Length}"); } return array2; } } [BepInPlugin("sighsorry.SkadiNet", "SkadiNet", "1.0.4")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "sighsorry.SkadiNet"; public const string PluginName = "SkadiNet"; public const string ModVersion = "1.0.4"; internal static ManualLogSource Log; private Harmony _harmony; internal static Plugin Instance; private void Awake() { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ConfigSyncManager.Initialize(((BaseUnityPlugin)this).Config); ModConfig.Bind(((BaseUnityPlugin)this).Config); ReflectionCache.Initialize(); FeatureNegotiation.Initialize(); PeerQualityMeter.Initialize(); ZdoKeyPolicy.Initialize(); OwnershipManager.Initialize(); RpcAoiRouter.Initialize(); ClientStutterGuard.Initialize(this); if (!ModConfig.Enabled.Value) { Log.LogInfo((object)"SkadiNet is disabled by configuration."); return; } _harmony = new Harmony("sighsorry.SkadiNet"); _harmony.PatchAll(typeof(Plugin).Assembly); Log.LogInfo((object)("SkadiNet 1.0.4 loaded. Stable core: adaptive scheduler, queue patch, micro-update reducer, peer quality, adaptive client ownership, optional client stutter guard. ServerSync config lock=" + (ModConfig.LockServerConfig.Value ? "on" : "off") + ". Slider-gated features: scheduler=" + (EffectiveConfig.SchedulerEnabled ? "on" : "off") + ", payload reducer=" + (EffectiveConfig.PayloadReducerEnabled ? "on" : "off") + ", compression=" + (EffectiveConfig.CompressionEnabled ? "on" : "off") + ", RPC AoI=" + (EffectiveConfig.RpcAoiEnabled ? "on" : "off") + ", ClientStutterGuard=" + (EffectiveConfig.ClientStutterGuardEnabled ? "on" : "off") + ".")); LogDebugConfigSnapshot(); } private static void LogDebugConfigSnapshot() { if (ModConfig.DebugLogging.Value) { Log.LogDebug((object)($"SkadiNet sliders: SchedulerThroughput={ModConfig.SchedulerThroughput.Value}, PayloadReducerStrength={ModConfig.PayloadReducerStrength.Value}, " + $"CompressionAggression={ModConfig.CompressionAggression.Value}, OwnershipIntensity={ModConfig.OwnershipIntensity.Value}, " + $"RpcAoiAggression={ModConfig.RpcAoiAggression.Value}, ClientStutterGuardStrength={ModConfig.ClientStutterGuardStrength.Value}")); Log.LogDebug((object)($"SkadiNet scheduler effective: interval={EffectiveConfig.SendInterval:F3}s min={EffectiveConfig.MinSendInterval:F3}s max={EffectiveConfig.MaxSendInterval:F3}s " + $"peersPerTick={EffectiveConfig.BasePeersPerTick}-{EffectiveConfig.MaxPeersPerTick} " + "zdoQueueLimit=" + FormatBytes(EffectiveConfig.ZdoQueueLimitBytes) + " minPackage=" + FormatBytes(EffectiveConfig.ZdoQueueMinPackageBytes) + " peerQueueSoft=" + FormatBytes(EffectiveConfig.PeerQueueSoftLimitBytes) + " peerQueueHard=" + FormatBytes(EffectiveConfig.PeerQueueHardLimitBytes) + " " + $"lagBackfill={EffectiveConfig.LaggingPeerMaxSkipSeconds:F2}s fixedSteamRate={FormatBytes(36000000)}/s")); Log.LogDebug((object)($"SkadiNet other effective: payloadVec3={EffectiveConfig.PayloadVec3CullSize:F4} payloadQuatDot={EffectiveConfig.PayloadQuaternionDotThreshold:F4} payloadRefresh={EffectiveConfig.PayloadForceRefreshSeconds:F2}s " + $"compressionThreshold={FormatBytes(EffectiveConfig.CompressionThresholdBytes)} compressionRatio={EffectiveConfig.CompressionMinUsefulRatio:F2} " + $"ownershipScanBudget={EffectiveConfig.OwnershipScanBudget} ownershipScanInterval={EffectiveConfig.OwnershipScanIntervalSeconds:F2}s ownershipCandidateRadius={EffectiveConfig.OwnershipCandidateRadius:F0} " + $"rpcAoiRadius={EffectiveConfig.RpcAoiVisualRadius:F0}")); } } private static string FormatBytes(int bytes) { if (bytes >= 1048576) { return $"{(float)bytes / 1048576f:F1}MB"; } if (bytes >= 1024) { return $"{(float)bytes / 1024f:F1}KB"; } return $"{bytes}B"; } private void Update() { OwnershipManager.TickLightweight(ZdoReflection.ZDOManInstance); } private void OnDestroy() { try { ClientStutterGuard.Shutdown(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception arg) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)$"Failed to unpatch cleanly: {arg}"); } } finally { if (Instance == this) { Instance = null; } } } } internal static class GameplayReflection { internal static object GetZdoFromNView(object nview) { if (nview == null) { return null; } try { return ReflectionCache.ZNetViewGetZDOMethod?.Invoke(nview, null) ?? ReflectionCache.ZNetViewZdoField?.GetValue(nview); } catch { return null; } } internal static object GetNViewFromCharacterLike(object instance) { if (instance == null) { return null; } try { return ReflectionCache.CharacterNViewField?.GetValue(instance); } catch { } try { return ReflectionCache.MonsterAINViewField?.GetValue(instance); } catch { } try { return ReflectionCache.CachedField(instance.GetType(), "m_nview")?.GetValue(instance); } catch { } return null; } internal static object GetZdoFromCharacterLike(object instance) { return GetZdoFromNView(GetNViewFromCharacterLike(instance)); } internal static bool TryGetPlayerId(object player, out long id) { id = 0L; if (player == null || ReflectionCache.PlayerGetPlayerIDMethod == null) { return false; } try { return ReflectionCache.TryConvertToLong(ReflectionCache.PlayerGetPlayerIDMethod.Invoke(player, null), out id); } catch { } return false; } internal static bool LooksLikePlayer(object obj) { if (obj == null) { return false; } Type type = obj.GetType(); if (!(type.Name == "Player")) { if (ReflectionCache.PlayerType != null) { return ReflectionCache.PlayerType.IsAssignableFrom(type); } return false; } return true; } } internal static class NetReflection { private static Func<object, object> _peerRpcGetter; private static Func<object, object> _peerUidGetter; private static Func<object, Vector3> _peerRefPosGetter; private static Func<object, object> _rpcSocketGetter; private static Func<object, object> _socketSendQueueSizeGetter; internal static Type ZNetType => ReflectionCache.ZNetType; internal static Type ZNetPeerType => ReflectionCache.ZNetPeerType; internal static Type ZRpcType => ReflectionCache.ZRpcType; internal static Type ZPackageType => ReflectionCache.ZPackageType; internal static object ZNetInstance => ReflectionCache.ZNetInstanceField?.GetValue(null); internal static void Initialize() { _peerRpcGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.PeerRpcField); _peerUidGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.PeerUidField); _peerRefPosGetter = ReflectionDelegateFactory.Vector3FieldGetter(ReflectionCache.PeerRefPosField); _rpcSocketGetter = ReflectionDelegateFactory.BoxedInstanceMethod(ReflectionCache.ZRpcGetSocketMethod); _socketSendQueueSizeGetter = ReflectionDelegateFactory.BoxedInstanceMethod(ReflectionCache.ZSteamSocketGetSendQueueSizeMethod); } internal static bool IsServer() { try { object zNetInstance = ZNetInstance; if (zNetInstance == null || ReflectionCache.ZNetIsServerMethod == null) { return false; } return (bool)ReflectionCache.ZNetIsServerMethod.Invoke(zNetInstance, null); } catch { return false; } } internal static bool IsDedicatedServer() { try { object zNetInstance = ZNetInstance; if (zNetInstance == null || ReflectionCache.ZNetIsDedicatedMethod == null) { return false; } return (bool)ReflectionCache.ZNetIsDedicatedMethod.Invoke(zNetInstance, null); } catch { return false; } } internal static bool TryGetPeerUid(object peerOrRpc, out long uid) { uid = 0L; if (peerOrRpc == null) { return false; } try { if (ReflectionCache.TryConvertToLong(TryGet(_peerUidGetter, peerOrRpc), out uid)) { return true; } if (ReflectionCache.TryConvertToLong(ReflectionCache.CachedField(peerOrRpc.GetType(), "m_uid")?.GetValue(peerOrRpc), out uid)) { return true; } return TryGetUidFromPeerObject(GetPeerRpc(peerOrRpc), out uid); } catch { return false; } } internal static bool TryGetUidFromPeerObject(object peerOrRpc, out long uid) { uid = 0L; if (peerOrRpc == null) { return false; } try { return ReflectionCache.TryConvertToLong(ReflectionCache.CachedField(peerOrRpc.GetType(), "m_uid")?.GetValue(peerOrRpc), out uid); } catch { return false; } } internal static object GetPeerRpc(object peer) { if (peer == null) { return null; } try { object obj = ReflectionCache.CachedField(peer.GetType(), "m_rpc")?.GetValue(peer); if (obj != null) { return obj; } object obj2 = TryGet(_peerRpcGetter, peer); if (obj2 == null) { return null; } return ReflectionCache.CachedField(obj2.GetType(), "m_rpc")?.GetValue(obj2) ?? obj2; } catch { return null; } } internal static object GetSocketFromRpc(object rpc) { if (rpc == null) { return null; } try { return TryGet(_rpcSocketGetter, rpc) ?? ReflectionCache.ZRpcGetSocketMethod?.Invoke(rpc, null); } catch { return null; } } internal static Vector3 GetPeerRefPos(object peer) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) if (peer == null) { return Vector3.zero; } try { if (TryGetVector3(_peerRefPosGetter, peer, out var value)) { return value; } if (ReflectionCache.CachedField(peer.GetType(), "m_refPos")?.GetValue(peer) is Vector3 result) { return result; } object obj = ReflectionCache.CachedField(peer.GetType(), "m_peer")?.GetValue(peer); if (obj != null && obj != peer && ReflectionCache.CachedField(obj.GetType(), "m_refPos")?.GetValue(obj) is Vector3 result2) { return result2; } } catch { } return Vector3.zero; } internal static int GetSendQueueSizeForPeer(object zdoPeer) { try { object peerRpc = GetPeerRpc(zdoPeer); if (peerRpc == null) { return 0; } object socketFromRpc = GetSocketFromRpc(peerRpc); if (socketFromRpc == null) { return 0; } object obj = TryGet(_socketSendQueueSizeGetter, socketFromRpc) ?? ReflectionCache.ZSteamSocketGetSendQueueSizeMethod?.Invoke(socketFromRpc, null