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: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("SkadiNet")] [assembly: AssemblyTitle("SkadiNet")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 ClientCleanupKind { GarbageCollect, UnloadUnusedAssets } 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; } [CompilerGenerated] private sealed class d__42 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__42(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; if (IsActive && RunCleanupWhenIdle) { TryRunPendingCleanup(forced: false); } } else { <>1__state = -1; } float num2 = Math.Max(0.25f, EffectiveConfig.ClientStutterIdleCleanupPollSeconds); <>2__current = (object)new WaitForSeconds(num2); <>1__state = 1; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly Dictionary CriticalUntil = new Dictionary(); private static Plugin _plugin; private static Coroutine _cleanupCoroutine; private static bool _pendingGc; private static bool _pendingUnload; private static double _firstPendingSince; private static bool _runningCleanup; private static AsyncOperation _lastUnloadOperation; private static bool DelayGcCollect => true; private static bool DelayUnusedAssetCleanup => false; private static bool DelayDuringInitialSync => true; private static bool DelayDuringTeleport => true; private static bool DelayDuringFullSnapshotBurst => true; private static bool DelayDuringCombat => true; private static bool DelayDuringShipTravel => true; private static bool RunCleanupWhenIdle => true; private static bool UseMemoryPressureGate => true; 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; _pendingUnload = false; _firstPendingSince = 0.0; _runningCleanup = false; _lastUnloadOperation = null; 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) && IsWindowEnabled(window)) { 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 || !DelayGcCollect || _runningCleanup) { return true; } if (!RunCleanupWhenIdle) { return true; } if (ShouldDelayCleanup(ClientCleanupKind.GarbageCollect, out var reason)) { RequestPending(ClientCleanupKind.GarbageCollect, reason); return false; } return true; } internal static bool TryHandleUnloadUnusedAssetsPrefix(ref AsyncOperation result) { if (!IsActive || !DelayUnusedAssetCleanup || _runningCleanup) { return true; } if (!RunCleanupWhenIdle) { return true; } if (ShouldDelayCleanup(ClientCleanupKind.UnloadUnusedAssets, out var reason)) { RequestPending(ClientCleanupKind.UnloadUnusedAssets, reason); if (_lastUnloadOperation != null) { result = _lastUnloadOperation; return false; } return true; } return true; } internal static void OnUnloadUnusedAssetsPostfix(AsyncOperation result) { if (result != null) { _lastUnloadOperation = result; } } private static void RequestPending(ClientCleanupKind kind, string reason) { if (kind == ClientCleanupKind.GarbageCollect) { _pendingGc = true; } if (kind == ClientCleanupKind.UnloadUnusedAssets) { _pendingUnload = true; } if (_firstPendingSince <= 0.0) { _firstPendingSince = Time.realtimeSinceStartupAsDouble; } EnsureCleanupScheduler(); if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"ClientStutterGuard: deferred {kind} ({reason})."); } } private static void EnsureCleanupScheduler() { if (_cleanupCoroutine == null && !((Object)(object)_plugin == (Object)null) && IsActive && RunCleanupWhenIdle) { _cleanupCoroutine = ((MonoBehaviour)_plugin).StartCoroutine(CleanupScheduler()); } } [IteratorStateMachine(typeof(d__42))] private static IEnumerator CleanupScheduler() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__42(0); } private static bool TryRunPendingCleanup(bool forced) { if (!_pendingGc && !_pendingUnload) { return false; } if (!IsActive && !forced) { return false; } if (_runningCleanup) { return false; } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; bool flag = _firstPendingSince > 0.0 && realtimeSinceStartupAsDouble - _firstPendingSince >= (double)Math.Max(1f, EffectiveConfig.ClientStutterMaxDelaySeconds); if (!forced && !flag && TryGetCriticalReason(out var reason)) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)("ClientStutterGuard: pending cleanup waits for " + reason + ".")); } return false; } if (!forced && !flag && UseMemoryPressureGate && IsMemoryPlentiful(out var snapshot)) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"ClientStutterGuard: pending cleanup waits; memory plentiful ({snapshot})."); } return false; } bool flag2 = _pendingUnload && DelayUnusedAssetCleanup; bool flag3 = _pendingGc && DelayGcCollect; _pendingUnload = false; _pendingGc = false; _firstPendingSince = 0.0; _runningCleanup = true; try { if (flag2) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)"ClientStutterGuard: running coalesced Resources.UnloadUnusedAssets."); } AsyncOperation val = Resources.UnloadUnusedAssets(); if (val != null) { _lastUnloadOperation = val; } } if (flag3) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)"ClientStutterGuard: running coalesced GC.Collect."); } GC.Collect(); } } finally { _runningCleanup = false; } return flag2 || flag3; } private static bool ShouldDelayCleanup(ClientCleanupKind kind, 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 (UseMemoryPressureGate && IsMemoryPressure(out var snapshot)) { reason = $"memory pressure overrides critical window ({snapshot})"; return false; } reason = reason2; return true; } if (UseMemoryPressureGate && 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 item in CriticalUntil) { if (item.Value > realtimeSinceStartupAsDouble && item.Value > num && IsWindowEnabled(item.Key)) { clientCriticalWindow = item.Key; num = item.Value; } } if (num <= realtimeSinceStartupAsDouble) { return false; } reason = $"{clientCriticalWindow} for {num - realtimeSinceStartupAsDouble:F1}s"; return true; } private static bool IsWindowEnabled(ClientCriticalWindow window) { return window switch { ClientCriticalWindow.InitialSync => DelayDuringInitialSync, ClientCriticalWindow.Teleport => DelayDuringTeleport, ClientCriticalWindow.FullSnapshotBurst => DelayDuringFullSnapshotBurst, ClientCriticalWindow.Combat => DelayDuringCombat, ClientCriticalWindow.ShipTravel => DelayDuringShipTravel, _ => 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; } try { if (SystemInfo.systemMemorySize > 0) { ulong num = (ulong)SystemInfo.systemMemorySize; MemoryPressureSnapshot result = default(MemoryPressureSnapshot); result.Known = true; result.TotalMB = num; result.AvailableMB = num; result.LoadPercent = 0; return result; } } catch { } MemoryPressureSnapshot result2 = default(MemoryPressureSnapshot); result2.Known = false; return result2; } private static bool TryGetWindowsMemory(out MemoryPressureSnapshot snapshot) { snapshot = default(MemoryPressureSnapshot); try { MEMORYSTATUSEX lpBuffer = default(MEMORYSTATUSEX); lpBuffer.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 { [CompilerGenerated] private sealed class d__0 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private MethodBase <>2__current; private int <>l__initialThreadId; private MethodInfo[] <>7__wrap1; private int <>7__wrap2; MethodBase IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__0(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_006e; } <>1__state = -1; <>7__wrap1 = typeof(GC).GetMethods(BindingFlags.Static | BindingFlags.Public); <>7__wrap2 = 0; goto IL_007c; IL_006e: <>7__wrap2++; goto IL_007c; IL_007c: if (<>7__wrap2 < <>7__wrap1.Length) { MethodInfo methodInfo = <>7__wrap1[<>7__wrap2]; if (methodInfo.Name == "Collect") { <>2__current = methodInfo; <>1__state = 1; return true; } goto IL_006e; } <>7__wrap1 = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__0(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [IteratorStateMachine(typeof(d__0))] private static IEnumerable TargetMethods() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__0(-2); } private static bool Prefix() { return ClientStutterGuard.TryHandleGcCollect(); } } [HarmonyPatch] internal static class ClientStutterGuardUnloadUnusedAssetsPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Resources), "UnloadUnusedAssets", (Type[])null, (Type[])null); } private static bool Prefix(ref AsyncOperation __result) { return ClientStutterGuard.TryHandleUnloadUnusedAssetsPrefix(ref __result); } private static void Postfix(AsyncOperation __result) { ClientStutterGuard.OnUnloadUnusedAssetsPostfix(__result); } } [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 { [CompilerGenerated] private sealed class d__0 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private MethodBase <>2__current; private int <>l__initialThreadId; private MethodInfo[] <>7__wrap1; private int <>7__wrap2; MethodBase IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__0(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_0084; } <>1__state = -1; Type type = ReflectionCache.ZDOManType ?? AccessTools.TypeByName("ZDOMan"); if (type == null) { return false; } <>7__wrap1 = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); <>7__wrap2 = 0; goto IL_0092; IL_0092: if (<>7__wrap2 < <>7__wrap1.Length) { MethodInfo methodInfo = <>7__wrap1[<>7__wrap2]; if (methodInfo.Name == "RPC_ZDOData") { <>2__current = methodInfo; <>1__state = 1; return true; } goto IL_0084; } <>7__wrap1 = null; return false; IL_0084: <>7__wrap2++; goto IL_0092; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__0(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [IteratorStateMachine(typeof(d__0))] private static IEnumerable TargetMethods() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__0(-2); } 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 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); if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("ZSteamSocket.Recv decompression failed: " + ex.Message)); } } } } internal enum ConfigSyncScope { ServerSynced, ClientLocal, MigrationOnly } internal static class ConfigSyncManager { private static MethodInfo _addConfigEntryMethod; internal static ConfigSync Sync { get; private set; } internal static ConfigEntry 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.0", MinimumRequiredVersion = "1.0.0", ModRequired = true }; LockServerConfig = config.Bind("General", "LockServerConfig", true, new ConfigDescription("Lock server-synced config for non-admin clients.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 90 } })); Sync.AddLockingConfigEntry(LockServerConfig).SynchronizedConfig = true; } internal static ConfigEntry Bind(ConfigFile config, string group, string name, T value, string description, ConfigSyncScope scope) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown return Bind(config, group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty()), scope); } internal static ConfigEntry Bind(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 val2 = config.Bind(group, name, value, val); if (scope != ConfigSyncScope.MigrationOnly) { Register((ConfigEntryBase)(object)val2, scope == ConfigSyncScope.ServerSynced); } return val2; } private static string SuffixFor(ConfigSyncScope scope) { return scope switch { ConfigSyncScope.ServerSynced => " [Synced with Server]", ConfigSyncScope.ClientLocal => " [Client Local; Not Synced with Server]", ConfigSyncScope.MigrationOnly => " [Migration Only; 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 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.12f); internal static float PayloadQuaternionDotThreshold => Map(ModConfig.PayloadReducerStrength, 0.9998f, 0.995f, 0.985f); internal static float PayloadForceRefreshSeconds => Map(ModConfig.PayloadReducerStrength, 0.15f, 1f, 3f); internal static int CompressionThresholdBytes => MapInt(ModConfig.CompressionAggression, 8192, 1024, 128); internal static float CompressionMinUsefulRatio => Map(ModConfig.CompressionAggression, 0.7f, 0.9f, 0.99f); internal static int CompressionDisableAfterFailures => 1; 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, 90f); internal static int ClientStutterMemoryPressureThresholdPercent => MapInt(ModConfig.ClientStutterGuardStrength, 55, 75, 95); internal static int ClientStutterMinimumFreeMemoryMB => MapInt(ModConfig.ClientStutterGuardStrength, 6144, 2048, 512); 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, 256); internal static int OwnershipScanStride => MapInt(ModConfig.OwnershipIntensity, 10, 4, 1); internal static float OwnershipScanIntervalSeconds => Map(ModConfig.OwnershipIntensity, 3f, 1f, 0.25f); internal static float OwnershipRelativeHysteresis => Map(ModConfig.OwnershipIntensity, 0.03f, 0.15f, 0.4f); internal static float OwnershipAbsoluteHysteresisMs => Map(ModConfig.OwnershipIntensity, 5f, 20f, 90f); internal static float OwnerSwitchCooldownSeconds => Map(ModConfig.OwnershipIntensity, 0.75f, 3f, 12f); internal static float OwnerHintSwitchCooldownSeconds => Map(ModConfig.OwnershipIntensity, 1f, 5f, 16f); internal static float ShipOwnerSwitchCooldownSeconds => Map(ModConfig.OwnershipIntensity, 3f, 8f, 24f); internal static float RecoverUnownedAfterSeconds => Map(ModConfig.OwnershipIntensity, 0.75f, 2f, 6f); internal static float OwnershipCandidateRadius => Map(ModConfig.OwnershipIntensity, 80f, 160f, 360f); internal static float OwnerHintCandidateRadius => Map(ModConfig.OwnershipIntensity, 128f, 256f, 640f); internal static float OwnershipDistanceScoreWeight => Map(ModConfig.OwnershipIntensity, 0.4f, 0.2f, 0.06f); internal static float OwnershipLoadPenaltyPerZdo => Map(ModConfig.OwnershipIntensity, 0.5f, 0.35f, 0.15f); 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 entry) { return Clamp(entry?.Value ?? 0, 0, 100) > 0; } private static float Strength(ConfigEntry entry) { return (float)Clamp(entry?.Value ?? 50, 0, 100) / 100f; } private static float Map(ConfigEntry 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 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)); } } [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 int RemoteProtocol; public PeerFeatureFlags RemoteCapabilities; public bool CompressionActive; public bool RpcAoiActive; public int CompressionFailures; public double LastHandshakeTime; } 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 ByRpc = new Dictionary(); private static readonly Dictionary BySocket = new Dictionary(); private static readonly Dictionary ByUid = new Dictionary(); 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(Dictionary map, PeerFeatureState state) { if (state == null || map.Count == 0) { return; } List list = new List(); foreach (KeyValuePair 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 >= EffectiveConfig.CompressionDisableAfterFailures) { 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++; bySocket.CompressionActive = false; } } 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 @delegate = Delegate.CreateDelegate(typeof(Action<, >).MakeGenericType(ReflectionCache.ZRpcType, ReflectionCache.ZPackageType), method); ReflectionCache.ZRpcRegisterGenericPackageMethod.Invoke(rpc, new object[2] { "SkadiNet_Features", @delegate }); 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.0"); ReflectionCache.ZRpcInvokeMethod?.Invoke(rpc, new object[2] { "SkadiNet_Features", new object[1] { obj } }); state.HandshakeSent = true; state.LastHandshakeTime = Time.realtimeSinceStartupAsDouble; } 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 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.RemoteProtocol = num; orCreateByRpc.RemoteCapabilities = (PeerFeatureFlags)remoteCapabilities; orCreateByRpc.HandshakeReceived = num >= 1; RefreshActiveFlags(orCreateByRpc); if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"SkadiNet feature handshake: protocol={num}, capabilities={orCreateByRpc.RemoteCapabilities}, version={text}, compression={orCreateByRpc.CompressionActive}, rpcAoi={orCreateByRpc.RpcAoiActive}"); } 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; } private static void RefreshActiveFlags(PeerFeatureState state) { if (state != null) { state.CompressionActive = EffectiveConfig.CompressionEnabled && Supports(state, PeerFeatureFlags.Compression); state.RpcAoiActive = EffectiveConfig.RpcAoiEnabled && Supports(state, PeerFeatureFlags.RpcAoi); } } } [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 FrameHitchDiagnostics { private struct NetworkSnapshot { public bool IsServer; public bool IsDedicated; public int ZNetPeers; public int ZdoPeers; public int QueueTotalBytes; public int QueueMaxBytes; public int QueueSoftPeers; public int QueueHardPeers; public int ZdoSectors; public int ZdoObjectsApprox; public bool ZdoObjectCountCapped; public long ManagedMemoryBytes; public override string ToString() { string text = (ZdoObjectCountCapped ? "+" : ""); return $"network[server={IsServer} dedicated={IsDedicated} znetPeers={ZNetPeers} zdoPeers={ZdoPeers} " + $"queueTotal={FormatBytes(QueueTotalBytes)} queueMax={FormatBytes(QueueMaxBytes)} queueSoftPeers={QueueSoftPeers} queueHardPeers={QueueHardPeers} " + $"zdoSectors={ZdoSectors} zdoObjects~={ZdoObjectsApprox}{text} managedMem={FormatBytes(ManagedMemoryBytes)}]"; } } private const double HitchThresholdSeconds = 0.12; private const double SevereHitchThresholdSeconds = 0.25; private const double SummaryIntervalSeconds = 10.0; private const double HitchLogCooldownSeconds = 0.75; private const int MaxZdoObjectsSnapshotCount = 50000; private static double _lastRealtime; private static double _lastHitchLogTime; private static double _nextSummaryTime; private static double _summarySeconds; private static int _summaryFrames; private static int _summaryHitches; private static int _summarySevereHitches; private static double _summaryMaxFrameSeconds; internal static void Update() { if (!DiagnosticsEnabled()) { return; } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (_lastRealtime <= 0.0) { _lastRealtime = realtimeSinceStartupAsDouble; _nextSummaryTime = realtimeSinceStartupAsDouble + 10.0; return; } double num = Math.Max(0.0, realtimeSinceStartupAsDouble - _lastRealtime); _lastRealtime = realtimeSinceStartupAsDouble; double num2 = Math.Max(Time.unscaledDeltaTime, num); _summaryFrames++; _summarySeconds += num2; if (num2 > _summaryMaxFrameSeconds) { _summaryMaxFrameSeconds = num2; } if (num2 >= 0.12) { _summaryHitches++; } if (num2 >= 0.25) { _summarySevereHitches++; } if (num2 >= 0.12 && realtimeSinceStartupAsDouble - _lastHitchLogTime >= 0.75) { _lastHitchLogTime = realtimeSinceStartupAsDouble; LogFrameSnapshot(num2, num, (num2 >= 0.25) ? "severe" : "mild"); } if (realtimeSinceStartupAsDouble >= _nextSummaryTime) { LogSummary(); _nextSummaryTime = realtimeSinceStartupAsDouble + 10.0; } } private static bool DiagnosticsEnabled() { try { return Plugin.Log != null && ModConfig.Enabled != null && ModConfig.Enabled.Value && ModConfig.DebugLogging != null && ModConfig.DebugLogging.Value; } catch { return false; } } private static void LogFrameSnapshot(double frameDelta, double realtimeDelta, string severity) { NetworkSnapshot networkSnapshot = CaptureNetworkSnapshot(); string text = ZDOManSendSchedulerPatch.DescribeRecentState(); string text2 = OwnershipManager.DescribeRecentState(); Plugin.Log.LogInfo((object)($"Frame hitch {severity}: frameMs={frameDelta * 1000.0:F1} realtimeGapMs={realtimeDelta * 1000.0:F1} " + $"instantFps={SafeFps(frameDelta):F1} avgFps10s={AverageFps():F1} " + $"{networkSnapshot} {text} {text2} sliders[scheduler={Value(ModConfig.SchedulerThroughput)} payload={Value(ModConfig.PayloadReducerStrength)} " + $"compression={Value(ModConfig.CompressionAggression)} ownership={Value(ModConfig.OwnershipIntensity)} rpcAoi={Value(ModConfig.RpcAoiAggression)} " + $"stutterGuard={Value(ModConfig.ClientStutterGuardStrength)}]")); } private static void LogSummary() { if (_summaryFrames > 0) { NetworkSnapshot networkSnapshot = CaptureNetworkSnapshot(); Plugin.Log.LogInfo((object)($"Frame diagnostics {10.0:F0}s: frames={_summaryFrames} avgFps={AverageFps():F1} " + $"maxFrameMs={_summaryMaxFrameSeconds * 1000.0:F1} hitches>={120.0:F0}ms={_summaryHitches} " + $"severe>={250.0:F0}ms={_summarySevereHitches} {networkSnapshot}")); _summaryFrames = 0; _summarySeconds = 0.0; _summaryHitches = 0; _summarySevereHitches = 0; _summaryMaxFrameSeconds = 0.0; } } private static double AverageFps() { if (!(_summarySeconds > 0.0)) { return 0.0; } return (double)_summaryFrames / _summarySeconds; } private static double SafeFps(double frameDelta) { if (!(frameDelta > 0.0)) { return 0.0; } return 1.0 / frameDelta; } private static int Value(ConfigEntry entry) { try { return entry?.Value ?? 0; } catch { return 0; } } private static NetworkSnapshot CaptureNetworkSnapshot() { NetworkSnapshot networkSnapshot = default(NetworkSnapshot); networkSnapshot.IsServer = NetReflection.IsServer(); networkSnapshot.IsDedicated = NetReflection.IsDedicatedServer(); NetworkSnapshot snapshot = networkSnapshot; object zDOManInstance = ZdoReflection.ZDOManInstance; foreach (object item in ZdoReflection.EnumeratePeers(zDOManInstance)) { snapshot.ZdoPeers++; int sendQueueSizeForPeer = NetReflection.GetSendQueueSizeForPeer(item); snapshot.QueueTotalBytes += sendQueueSizeForPeer; if (sendQueueSizeForPeer > snapshot.QueueMaxBytes) { snapshot.QueueMaxBytes = sendQueueSizeForPeer; } if (sendQueueSizeForPeer > EffectiveConfig.PeerQueueSoftLimitBytes) { snapshot.QueueSoftPeers++; } if (sendQueueSizeForPeer > EffectiveConfig.PeerQueueHardLimitBytes) { snapshot.QueueHardPeers++; } } foreach (object item2 in NetReflection.EnumerateZNetPeers()) { _ = item2; snapshot.ZNetPeers++; } CountZdoSectors(zDOManInstance, ref snapshot); snapshot.ManagedMemoryBytes = GC.GetTotalMemory(forceFullCollection: false); return snapshot; } private static void CountZdoSectors(object zdoMan, ref NetworkSnapshot snapshot) { try { if (zdoMan == null || ReflectionCache.ZDOObjectsBySectorField == null) { return; } object value = ReflectionCache.ZDOObjectsBySectorField.GetValue(zdoMan); if (value == null) { return; } if (value is IDictionary dictionary) { snapshot.ZdoSectors = dictionary.Count; { foreach (object value2 in dictionary.Values) { int num = 50000 - snapshot.ZdoObjectsApprox; if (num <= 0) { snapshot.ZdoObjectCountCapped = true; break; } snapshot.ZdoObjectsApprox += CountEnumerable(value2, num, ref snapshot.ZdoObjectCountCapped); } return; } } if (!(value is IEnumerable enumerable)) { return; } foreach (object item in enumerable) { snapshot.ZdoSectors++; int num2 = 50000 - snapshot.ZdoObjectsApprox; if (num2 <= 0) { snapshot.ZdoObjectCountCapped = true; break; } snapshot.ZdoObjectsApprox += CountSectorEntry(item, num2, ref snapshot.ZdoObjectCountCapped); } } catch { } } private static int CountSectorEntry(object entry, int cap, ref bool capped) { if (entry == null) { return 0; } try { object value = entry; Type type = entry.GetType(); if (type.IsGenericType && type.FullName != null && type.FullName.StartsWith("System.Collections.Generic.KeyValuePair", StringComparison.Ordinal)) { value = type.GetProperty("Value")?.GetValue(entry, null) ?? entry; } return CountEnumerable(value, cap, ref capped); } catch { return 0; } } private static int CountEnumerable(object value, int cap, ref bool capped) { if (value == null) { return 0; } if (value is ICollection collection) { if (collection.Count > cap) { capped = true; } return Math.Min(collection.Count, cap); } if (!(value is IEnumerable enumerable)) { return 0; } int num = 0; foreach (object item in enumerable) { _ = item; num++; if (num >= cap) { capped = true; break; } } return num; } private static string FormatBytes(long bytes) { if (bytes >= 1073741824) { return $"{(float)bytes / 1.0737418E+09f:F2}GB"; } if (bytes >= 1048576) { return $"{(float)bytes / 1048576f:F1}MB"; } if (bytes >= 1024) { return $"{(float)bytes / 1024f:F1}KB"; } return $"{bytes}B"; } } internal static class GameplayReflection { internal static void Initialize() { } 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 ModConfig { private const string GeneralSection = "General"; internal static ConfigEntry Enabled; internal static ConfigEntry LockServerConfig; internal static ConfigEntry DebugLogging; internal static ConfigEntry SchedulerThroughput; internal static ConfigEntry PayloadReducerStrength; internal static ConfigEntry OwnershipIntensity; internal static ConfigEntry CompressionAggression; internal static ConfigEntry RpcAoiAggression; internal static ConfigEntry 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 frame hitches, FPS, 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, 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(0, 100), new object[1] { new ConfigurationManagerAttributes { Order = order } }); } } internal static class NetReflection { [CompilerGenerated] private sealed class d__24 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; private int <>l__initialThreadId; private IEnumerator <>7__wrap1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__24(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; object zNetInstance = ZNetInstance; if (zNetInstance == null) { return false; } IEnumerable enumerable = null; try { enumerable = ReflectionCache.ZNetGetPeersMethod?.Invoke(zNetInstance, null) as IEnumerable; } catch { } if (enumerable == null) { try { enumerable = ReflectionCache.ZNetPeersField?.GetValue(zNetInstance) as IEnumerable; } catch { } } if (enumerable == null) { return false; } <>7__wrap1 = enumerable.GetEnumerator(); <>1__state = -3; break; } case 1: <>1__state = -3; break; } if (<>7__wrap1.MoveNext()) { object current = <>7__wrap1.Current; <>2__current = current; <>1__state = 1; return true; } <>m__Finally1(); <>7__wrap1 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap1 is IDisposable disposable) { disposable.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__24(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } private static Func _peerRpcGetter; private static Func _peerUidGetter; private static Func _peerRefPosGetter; private static Func _rpcSocketGetter; private static Func _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) { object obj2 = ReflectionCache.CachedField(obj.GetType(), "m_refPos")?.GetValue(obj); if (obj2 is Vector3) { return (Vector3)obj2; } } } 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) ?? AccessTools.Method(socketFromRpc.GetType(), "GetSendQueueSize", (Type[])null, (Type[])null)?.Invoke(socketFromRpc, null); if (obj is int result) { return result; } if (obj is long) { long val = (long)obj; return (int)Math.Min(2147483647L, val); } } catch { } return 0; } [IteratorStateMachine(typeof(d__24))] internal static IEnumerable EnumerateZNetPeers() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__24(-2); } internal static Vector3 GetReferencePosition(Vector3 fallback) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) try { object zNetInstance = ZNetInstance; if (zNetInstance != null && ReflectionCache.ZNetGetReferencePositionMethod != null) { object obj = ReflectionCache.ZNetGetReferencePositionMethod.Invoke(zNetInstance, null); if (obj is Vector3) { return (Vector3)obj; } } } catch { } return fallback; } private static object TryGet(Func getter, object instance) { try { return (getter != null && instance != null) ? getter(instance) : null; } catch { return null; } } private static bool TryGetVector3(Func getter, object instance, out Vector3 value) { //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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) value = Vector3.zero; try { if (getter == null || instance == null) { return false; } value = getter(instance); return true; } catch { return false; } } } internal enum OwnershipCandidateReason { Generic, CombatTarget, DisconnectedOwner, LongUnownedPersistent } internal sealed class OwnerState { public long CurrentOwner; public long PreviousOwner; public double LastOwnerChangeTime; public double LastCombatOwnerChangeTime; public double LastSeenUnownedTime; public double LastTouchedTime; public int ChangeCount; public long CombatTargetUid; public double CombatTargetHintTime; } internal sealed class OwnerCandidate { public long Uid; public object Peer; public float Distance; public float Quality; public float Score; public int EstimatedLoad; public bool IsCombatTarget; } 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 ByZdoId = new Dictionary(); private static readonly Dictionary OwnerLoadEstimate = new Dictionary(); private static double _nextProfileAScan; private static double _nextOwnerStatePrune; private static int _scanCursor; private static int _sectorCursor; private static double _lastScanTime; private static double _lastScanSeconds; private static int _lastScanVisited; private static int _lastScanBudget; private static int _lastScanOwnerChanges; private static int _lastScanPeerCount; private static int _lastScanSectorCursor; private static bool _lastScanSkippedNoPeers; internal static bool ProfileAEnabled { get { if (!EffectiveConfig.AdaptiveOwnershipEnabled || !NetReflection.IsServer()) { return false; } return true; } } internal static void Initialize() { ByZdoId.Clear(); OwnerLoadEstimate.Clear(); _nextProfileAScan = 0.0; _nextOwnerStatePrune = 0.0; _scanCursor = 0; _sectorCursor = 0; _lastScanTime = 0.0; _lastScanSeconds = 0.0; _lastScanVisited = 0; _lastScanBudget = 0; _lastScanOwnerChanges = 0; _lastScanPeerCount = 0; _lastScanSectorCursor = 0; _lastScanSkippedNoPeers = false; } 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; } OwnerLoadEstimate.Remove(uid); 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; } if (!HasAnyZdoPeer(zdoMan, out var peerCount)) { _lastScanSkippedNoPeers = true; _lastScanPeerCount = 0; return; } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; PruneOwnerStatesIfDue(realtimeSinceStartupAsDouble); if (!(realtimeSinceStartupAsDouble < _nextProfileAScan)) { _nextProfileAScan = realtimeSinceStartupAsDouble + (double)Math.Max(0.1f, EffectiveConfig.OwnershipScanIntervalSeconds); _lastScanPeerCount = peerCount; TryProfileAScan(zdoMan); } } internal static string DescribeRecentState() { double num = ((_lastScanTime > 0.0) ? Math.Max(0.0, Time.realtimeSinceStartupAsDouble - _lastScanTime) : (-1.0)); string text = ((num >= 0.0) ? $"{num:F2}s" : "n/a"); return $"ownershipRecent age={text} scanMs={_lastScanSeconds * 1000.0:F2} visited={_lastScanVisited}/{_lastScanBudget} " + $"ownerChanges={_lastScanOwnerChanges} zdoPeers={_lastScanPeerCount} sectorCursor={_lastScanSectorCursor} " + $"skippedNoPeers={_lastScanSkippedNoPeers}"; } 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; OwnerLoadEstimate.Clear(); object obj = ReflectionCache.ZDOObjectsBySectorField?.GetValue(zdoMan); if (!(obj is IEnumerable enumerable)) { return; } int visited = 0; int acted = 0; int num = Math.Max(1, EffectiveConfig.OwnershipScanBudget); if (obj is IList list && list.Count > 0) { if (_sectorCursor < 0 || _sectorCursor >= list.Count) { _sectorCursor = 0; } int sectorCursor = _sectorCursor; int num2 = 0; while (true) { if (num2 < list.Count) { int num3 = (sectorCursor + num2) % list.Count; if (ScanBucket(list[num3], num, ref visited, ref acted)) { _sectorCursor = (num3 + 1) % list.Count; break; } num2++; continue; } _sectorCursor = 0; break; } } else { { IEnumerator enumerator = enumerable.GetEnumerator(); try { while (enumerator.MoveNext() && !ScanBucket(enumerator.Current, num, ref visited, ref acted)) { } } finally { IDisposable disposable = enumerator as IDisposable; if (disposable != null) { disposable.Dispose(); } } } } double num4 = Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble; _lastScanTime = Time.realtimeSinceStartupAsDouble; _lastScanSeconds = num4; _lastScanVisited = visited; _lastScanBudget = num; _lastScanOwnerChanges = acted; _lastScanSectorCursor = _sectorCursor; _lastScanSkippedNoPeers = false; if (ModConfig.DebugLogging.Value && (acted > 0 || visited > 0)) { bool flag = num4 >= 0.008; if (acted > 0 || flag) { Plugin.Log.LogInfo((object)$"Adaptive ownership scan: elapsed={num4 * 1000.0:F2}ms slow={flag} visited={visited}/{num}, ownerChanges={acted}, zdoPeers={_lastScanPeerCount}, loadOwners={OwnerLoadEstimate.Count}, sectorCursor={_sectorCursor}, stride={EffectiveConfig.OwnershipScanStride}"); } } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("Adaptive ownership scan failed: " + ex.Message)); } } } private static bool ScanBucket(object bucket, int budget, ref int visited, ref int acted) { if (!(bucket is IEnumerable enumerable)) { return false; } foreach (object item in enumerable) { if (item == null) { continue; } _scanCursor++; int num = Math.Max(1, EffectiveConfig.OwnershipScanStride); if (_scanCursor % num == 0) { TrackCurrentOwnerLoad(item); visited++; if (TryMaybeImproveOwner(item, OwnershipCandidateReason.Generic)) { acted++; } if (visited >= budget) { return true; } } } return false; } private static void TrackCurrentOwnerLoad(object zdo) { if (ZdoReflection.TryGetOwner(zdo, out var owner) && owner != 0L) { OwnerLoadEstimate.TryGetValue(owner, out var value); OwnerLoadEstimate[owner] = value + 1; } } private static bool TryMaybeImproveOwner(object zdo, OwnershipCandidateReason reason) { //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) 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; bool num = ZdoKeyPolicy.LooksPlayerLike(zdo); bool flag2 = ZdoKeyPolicy.LooksShipLike(zdo); if (num) { return false; } if (flag2 && reason != OwnershipCandidateReason.DisconnectedOwner) { return false; } OwnerState ownerState = GetOwnerState(zdo); double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; bool flag3 = owner != 0L && IsUidCurrentlyConnected(owner); long uid; bool flag4 = owner != 0L && ZdoReflection.TryGetServerSessionId(out uid) && owner == uid; if (owner == 0L) { if (ownerState.LastSeenUnownedTime <= 0.0) { ownerState.LastSeenUnownedTime = realtimeSinceStartupAsDouble; } if (flag && realtimeSinceStartupAsDouble - ownerState.LastSeenUnownedTime >= (double)EffectiveConfig.RecoverUnownedAfterSeconds) { reason = OwnershipCandidateReason.LongUnownedPersistent; } } else { ownerState.LastSeenUnownedTime = 0.0; } if (flag && reason != OwnershipCandidateReason.LongUnownedPersistent && reason != OwnershipCandidateReason.DisconnectedOwner) { return false; } if (!flag3 && owner != 0L && !flag4) { reason = OwnershipCandidateReason.DisconnectedOwner; } Vector3 position = ZdoReflection.GetPosition(zdo, Vector3.zero); if (reason == OwnershipCandidateReason.Generic && flag3) { return false; } 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 num2 = ComputeCurrentOwnerScore(owner, position, ownerState, reason, flag3, flag4); if (!IsCandidateBetter(ownerCandidate.Score, num2, reason)) { return false; } float num3 = ((reason == OwnershipCandidateReason.CombatTarget) ? EffectiveConfig.OwnerHintSwitchCooldownSeconds : EffectiveConfig.OwnerSwitchCooldownSeconds); if (flag2) { num3 = Math.Max(num3, EffectiveConfig.ShipOwnerSwitchCooldownSeconds); } if (realtimeSinceStartupAsDouble - ownerState.LastOwnerChangeTime < (double)num3) { return false; } if (reason == OwnershipCandidateReason.CombatTarget && realtimeSinceStartupAsDouble - ownerState.LastCombatOwnerChangeTime < (double)EffectiveConfig.OwnerHintSwitchCooldownSeconds) { return false; } if (!ZdoReflection.TrySetOwner(zdo, ownerCandidate.Uid)) { return false; } ownerState.PreviousOwner = owner; ownerState.CurrentOwner = ownerCandidate.Uid; ownerState.LastOwnerChangeTime = realtimeSinceStartupAsDouble; if (reason == OwnershipCandidateReason.CombatTarget) { ownerState.LastCombatOwnerChangeTime = realtimeSinceStartupAsDouble; } ownerState.ChangeCount++; ZdoReflection.ForceSend(zdo); if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"ProfileA owner {reason}: {owner}->{ownerCandidate.Uid}, best={ownerCandidate.Score:F1}, current={num2:F1}, q={ownerCandidate.Quality:F1}, dist={ownerCandidate.Distance:F1}, load={ownerCandidate.EstimatedLoad}"); } 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.UpdateFromPeer(item) ?? PeerQualityMeter.GetByUid(uid) ?? PeerQualityMeter.GetByPeer(item); if (CandidateConnectionAllowed(peerQualityState, reason)) { int num3 = EstimateOwnerLoad(uid); float num4 = ComputeCandidateScore(uid, num2, peerQualityState, num3, state, reason); if (ownerCandidate == null || num4 < ownerCandidate.Score) { ownerCandidate = new OwnerCandidate { Uid = uid, Peer = item, Distance = num2, Quality = (peerQualityState?.ConnectionQualityMs ?? 999f), Score = num4, EstimatedLoad = num3, IsCombatTarget = (uid == state.CombatTargetUid && IsCombatHintFresh(state)) }; } } } return ownerCandidate; } private static float ComputeCandidateScore(long uid, float distance, PeerQualityState quality, int load, OwnerState state, OwnershipCandidateReason reason) { float num = quality?.ConnectionQualityMs ?? 999f; num += distance * Math.Max(0f, EffectiveConfig.OwnershipDistanceScoreWeight); num += (float)load * Math.Max(0f, EffectiveConfig.OwnershipLoadPenaltyPerZdo); 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, EstimateOwnerLoad(currentOwner), state, reason); } 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.PreviousOwner = currentOwner; state.CurrentOwner = uid; state.LastOwnerChangeTime = realtimeSinceStartupAsDouble; state.ChangeCount++; ZdoReflection.ForceSend(zdo); if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"ProfileA recovery to server owner {reason}: {currentOwner}->{uid}"); } return true; } private static int EstimateOwnerLoad(long uid) { if (uid == 0L) { return 0; } if (OwnerLoadEstimate.TryGetValue(uid, out var value)) { return value; } return PeerQualityMeter.GetByUid(uid)?.OwnedDynamicEstimate ?? 0; } private static bool CandidateConnectionAllowed(PeerQualityState candidate, OwnershipCandidateReason reason) { 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, EffectiveConfig.OwnershipRelativeHysteresis); float val = Math.Max(0f, EffectiveConfig.OwnershipAbsoluteHysteresisMs); 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 list = new List(); foreach (KeyValuePair 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); } while (ByZdoId.Count > 50000 && RemoveOldestOwnerState()) { } } private static bool RemoveOldestOwnerState() { ZdoIdKey key = default(ZdoIdKey); double num = double.MaxValue; bool flag = false; foreach (KeyValuePair item in ByZdoId) { double num2 = item.Value?.LastTouchedTime ?? 0.0; if (num2 < num) { num = num2; key = item.Key; flag = true; } } if (!flag) { return false; } ByZdoId.Remove(key); return true; } 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 enum PayloadCullValueKind : byte { Vector3, Quaternion } internal readonly struct PayloadCullKey : IEquatable { private readonly ZdoIdKey _zdoId; private readonly int _hash; private readonly PayloadCullValueKind _kind; internal PayloadCullKey(ZdoIdKey zdoId, int hash, PayloadCullValueKind kind) { _zdoId = zdoId; _hash = hash; _kind = kind; } public bool Equals(PayloadCullKey other) { if (_zdoId.Equals(other._zdoId) && _hash == other._hash) { return _kind == other._kind; } return false; } public override bool Equals(object obj) { if (obj is PayloadCullKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (_zdoId.GetHashCode() * 31 + _hash) * 31 + (int)_kind; } } internal static class PayloadCullState { private const int MaxEntries = 65536; private const double MinEntryTtlSeconds = 60.0; private const double PruneIntervalSeconds = 30.0; private static readonly Dictionary LastAllowedByZdoKey = new Dictionary(); private static readonly object Lock = new object(); private static double _nextPruneTime; internal static bool ShouldAllowByTime(object zdo, int hash, PayloadCullValueKind kind) { float num = Math.Max(0.05f, EffectiveConfig.PayloadForceRefreshSeconds); double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; PayloadCullKey key = BuildKey(zdo, hash, kind); lock (Lock) { PruneIfDue(realtimeSinceStartupAsDouble); if (!LastAllowedByZdoKey.TryGetValue(key, out var value) || realtimeSinceStartupAsDouble - value >= (double)num) { LastAllowedByZdoKey[key] = realtimeSinceStartupAsDouble; return true; } } return false; } internal static void MarkAllowed(object zdo, int hash, PayloadCullValueKind kind) { PayloadCullKey key = BuildKey(zdo, hash, kind); double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; lock (Lock) { PruneIfDue(realtimeSinceStartupAsDouble); LastAllowedByZdoKey[key] = realtimeSinceStartupAsDouble; } } private static void PruneIfDue(double now) { if (now < _nextPruneTime) { return; } _nextPruneTime = now + 30.0; double num = Math.Max(60.0, (double)Math.Max(0.05f, EffectiveConfig.PayloadForceRefreshSeconds) * 20.0); List list = new List(); foreach (KeyValuePair item in LastAllowedByZdoKey) { if (now - item.Value >= num) { list.Add(item.Key); } } foreach (PayloadCullKey item2 in list) { LastAllowedByZdoKey.Remove(item2); } while (LastAllowedByZdoKey.Count > 65536 && RemoveOldest()) { } } private static bool RemoveOldest() { PayloadCullKey key = default(PayloadCullKey); double num = double.MaxValue; bool flag = false; foreach (KeyValuePair item in LastAllowedByZdoKey) { if (item.Value < num) { num = item.Value; key = item.Key; flag = true; } } if (!flag) { return false; } LastAllowedByZdoKey.Remove(key); return true; } private static PayloadCullKey BuildKey(object zdo, int hash, PayloadCullValueKind kind) { if (!ZdoReflection.TryGetIdKey(zdo, out var key)) { key = ZdoIdKey.FromRuntimeObject(zdo); } return new PayloadCullKey(key, hash, kind); } } [HarmonyPatch] internal static class ZDOSetVector3ReducerPatch { private static MethodBase TargetMethod() { return AccessTools.Method(ReflectionCache.ZDOType ?? AccessTools.TypeByName("ZDO"), "Set", new Type[2] { typeof(int), typeof(Vector3) }, (Type[])null); } private static bool Prefix(object __instance, int __0, Vector3 __1) { //IL_001d: 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_0041: 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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) if (!EffectiveConfig.PayloadReducerEnabled) { return true; } if (__instance == null || ReflectionCache.ZDOGetVec3Method == null) { return true; } if (ZdoKeyPolicy.ShouldNeverCull(__0)) { return true; } try { Vector3 val = (Vector3)ReflectionCache.ZDOGetVec3Method.Invoke(__instance, new object[2] { __0, Vector3.zero }); float num = Math.Max(0f, EffectiveConfig.PayloadVec3CullSize); Vector3 val2 = val - __1; if (!(((Vector3)(ref val2)).sqrMagnitude < num * num)) { PayloadCullState.MarkAllowed(__instance, __0, PayloadCullValueKind.Vector3); return true; } if (PayloadCullState.ShouldAllowByTime(__instance, __0, PayloadCullValueKind.Vector3)) { return true; } return false; } catch { return true; } } } [HarmonyPatch] internal static class ZDOSetQuaternionReducerPatch { private static MethodBase TargetMethod() { return AccessTools.Method(ReflectionCache.ZDOType ?? AccessTools.TypeByName("ZDO"), "Set", new Type[2] { typeof(int), typeof(Quaternion) }, (Type[])null); } private static bool Prefix(object __instance, int __0, Quaternion __1) { //IL_001d: 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_0041: 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_0056: Unknown result type (might be due to invalid IL or missing references) if (!EffectiveConfig.PayloadReducerEnabled) { return true; } if (__instance == null || ReflectionCache.ZDOGetQuaternionMethod == null) { return true; } if (ZdoKeyPolicy.ShouldNeverCull(__0)) { return true; } try { if (!(Math.Abs(Quaternion.Dot((Quaternion)ReflectionCache.ZDOGetQuaternionMethod.Invoke(__instance, new object[2] { __0, Quaternion.identity }), __1)) > EffectiveConfig.PayloadQuaternionDotThreshold)) { PayloadCullState.MarkAllowed(__instance, __0, PayloadCullValueKind.Quaternion); return true; } if (PayloadCullState.ShouldAllowByTime(__instance, __0, PayloadCullValueKind.Quaternion)) { return true; } return false; } catch { return true; } } } 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); } } internal sealed class PeerQualityState { public object Rpc; public long Uid; public readonly Queue Samples = new Queue(); public float LastPingMs; public float PingEmaMs; public float PingMeanMs; public float PingStdDevMs; public float PingJitterMs; public float ConnectionQualityMs; public double LastUpdateTime; public int OwnedDynamicEstimate; public bool HasAnySample; } internal static class PeerQualityMeter { private static readonly Dictionary ByRpc = new Dictionary(); private static readonly Dictionary ByUid = new Dictionary(); private static readonly Dictionary SocketQualityMethods = new Dictionary(); private static readonly object Lock = new object(); private static FieldInfo _zrpcPingField; internal static void Initialize() { lock (Lock) { ByRpc.Clear(); ByUid.Clear(); SocketQualityMethods.Clear(); } if (ReflectionCache.ZRpcType != null) { _zrpcPingField = 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(Dictionary map, PeerQualityState state) { if (state == null || map.Count == 0) { return; } List list = new List(); foreach (KeyValuePair 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 PeerQualityState UpdateFromPeer(object zdoPeer) { if (zdoPeer == null) { return null; } object peerRpc = NetReflection.GetPeerRpc(zdoPeer); NetReflection.TryGetPeerUid(zdoPeer, out var uid); return UpdateFromRpcCore(peerRpc, uid); } internal static float GetQualityForUid(long uid, float fallback = 999f) { PeerQualityState byUid = GetByUid(uid); if (byUid == null || !byUid.HasAnySample) { return fallback; } return byUid.ConnectionQualityMs; } 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 { object obj = _zrpcPingField?.GetValue(rpc); if (obj is float num) { return (num < 10f) ? (num * 1000f) : num; } if (obj is double) { double num2 = (double)obj; return (num2 < 10.0) ? ((float)(num2 * 1000.0)) : ((float)num2); } } catch { } return -1f; } private static bool TryReadSocketPingMs(object socket, out float pingMs) { pingMs = 0f; if (socket == null) { return false; } try { MethodInfo socketQualityMethod = GetSocketQualityMethod(socket.GetType()); if (socketQualityMethod == null) { return false; } object[] array = new object[5] { 0f, 0f, 0, 0f, 0f }; socketQualityMethod.Invoke(socket, array); float num2 = ((array[0] is float num) ? num : 0f); float num4 = ((array[1] is float num3) ? num3 : 0f); int num6 = ((array[2] is int num5) ? num5 : Convert.ToInt32(array[2])); if (num6 > 0) { pingMs = num6; return true; } if (num2 > 0f || num4 > 0f) { pingMs = 1f; return true; } } catch { } return false; } private static MethodInfo GetSocketQualityMethod(Type socketType) { if (socketType == null) { return null; } lock (Lock) { if (SocketQualityMethods.TryGetValue(socketType, out var value)) { return value; } MethodInfo methodInfo = null; MethodInfo[] methods = socketType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != "GetConnectionQuality") && methodInfo2.GetParameters().Length == 5) { methodInfo = methodInfo2; break; } } SocketQualityMethods[socketType] = methodInfo; return methodInfo; } } 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); } } [BepInPlugin("sighsorry.SkadiNet", "SkadiNet", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "sighsorry.SkadiNet"; public const string PluginName = "SkadiNet"; public const string PluginVersion = "1.0.0"; 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.0 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() { FrameHitchDiagnostics.Update(); } 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 ReflectionCache { private sealed class KeyValueEntryAccessors { internal readonly PropertyInfo Key; internal readonly PropertyInfo Value; internal KeyValueEntryAccessors(PropertyInfo key, PropertyInfo value) { Key = key; Value = value; } } [CompilerGenerated] private sealed class d__111 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; private int <>l__initialThreadId; private IEnumerator <>7__wrap1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__111(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; object zNetInstance = ZNetInstance; if (zNetInstance == null) { return false; } IEnumerable enumerable = null; try { enumerable = ZNetGetPeersMethod?.Invoke(zNetInstance, null) as IEnumerable; } catch { } if (enumerable == null) { try { enumerable = ZNetPeersField?.GetValue(zNetInstance) as IEnumerable; } catch { } } if (enumerable == null) { return false; } <>7__wrap1 = enumerable.GetEnumerator(); <>1__state = -3; break; } case 1: <>1__state = -3; break; } if (<>7__wrap1.MoveNext()) { object current = <>7__wrap1.Current; <>2__current = current; <>1__state = 1; return true; } <>m__Finally1(); <>7__wrap1 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap1 is IDisposable disposable) { disposable.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__111(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [CompilerGenerated] private sealed class d__131 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; private int <>l__initialThreadId; private object zdoMan; public object <>3__zdoMan; private IEnumerator <>7__wrap1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__131(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; if (zdoMan == null || ZDOManPeersField == null) { return false; } IEnumerable enumerable = null; try { enumerable = ZDOManPeersField.GetValue(zdoMan) as IEnumerable; } catch { } if (enumerable == null) { return false; } <>7__wrap1 = enumerable.GetEnumerator(); <>1__state = -3; break; } case 1: <>1__state = -3; break; } if (<>7__wrap1.MoveNext()) { object current = <>7__wrap1.Current; <>2__current = current; <>1__state = 1; return true; } <>m__Finally1(); <>7__wrap1 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap1 is IDisposable disposable) { disposable.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__131 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__131(0); } d__.zdoMan = <>3__zdoMan; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } internal static Type ZNetType; internal static Type ZNetPeerType; internal static Type ZDOManType; internal static Type ZDOType; internal static Type ZDOIDType; internal static Type ZDOExtraDataType; internal static Type ZRpcType; internal static Type ZPackageType; internal static Type ZSteamSocketType; internal static Type ZNetViewType; internal static Type MonsterAIType; internal static Type PlayerType; internal static Type ZDOVarsType; internal static Type ZRoutedRpcType; internal static Type RoutedRPCDataType; internal static FieldInfo ZDOManPeersField; internal static FieldInfo ZDOManNextSendPeerField; internal static FieldInfo ZDOManSendTimerField; internal static FieldInfo ZDOManSessionIdField; internal static FieldInfo ZNetInstanceField; internal static FieldInfo ZNetPeersField; internal static FieldInfo ZDOManInstanceField; internal static FieldInfo PeerRpcField; internal static FieldInfo PeerUidField; internal static FieldInfo PeerRefPosField; internal static FieldInfo ZNetPeerRpcField; internal static FieldInfo ZNetPeerUidField; internal static FieldInfo ZNetPeerRefPosField; internal static FieldInfo ZNetViewZdoField; internal static FieldInfo CharacterNViewField; internal static FieldInfo MonsterAINViewField; internal static FieldInfo ZDOUidField; internal static FieldInfo ZDOPrefabField; internal static FieldInfo ZDORotationField; internal static FieldInfo ZDODataRevisionField; internal static FieldInfo ZDOOwnerRevisionField; internal static FieldInfo ZDOObjectsBySectorField; internal static FieldInfo ZSteamSocketSendQueueField; internal static FieldInfo RoutedRpcDataSenderPeerIdField; internal static FieldInfo RoutedRpcDataTargetPeerIdField; internal static FieldInfo RoutedRpcDataTargetZdoField; internal static FieldInfo RoutedRpcDataMethodHashField; internal static FieldInfo RoutedRpcDataParametersField; internal static MethodInfo ZNetIsServerMethod; internal static MethodInfo ZNetIsDedicatedMethod; internal static MethodInfo ZNetGetPeersMethod; internal static MethodInfo ZNetGetReferencePositionMethod; internal static MethodInfo SendZDOsMethod; internal static MethodInfo ZDOGetVec3Method; internal static MethodInfo ZDOGetQuaternionMethod; internal static MethodInfo ZDOGetPositionMethod; internal static MethodInfo ZDOGetRotationMethod; internal static MethodInfo ZDOGetOwnerMethod; internal static MethodInfo ZDOSetOwnerMethod; internal static MethodInfo ZDOSetOwnerInternalMethod; internal static MethodInfo ZDOGetPrefabMethod; internal static MethodInfo ZDOPersistentGetter; internal static MethodInfo ZDODistantGetter; internal static MethodInfo ZDOTypeGetter; internal static MethodInfo ZDOIDIsNoneMethod; internal static MethodInfo ZDOIDUserIDGetter; internal static MethodInfo ZDOIDIDGetter; internal static MethodInfo PlayerGetPlayerIDMethod; internal static MethodInfo ZNetViewGetZDOMethod; internal static MethodInfo ZNetViewClaimOwnershipMethod; internal static MethodInfo ZDOManForceSendZDOMethod; internal static MethodInfo ZDOManGetZDOMethod; internal static MethodInfo ZDOManGetSessionIdMethod; internal static MethodInfo ZRpcGetSocketMethod; internal static MethodInfo ZRpcInvokeMethod; internal static MethodInfo ZRpcRegisterGenericPackageMethod; internal static MethodInfo ZRpcUnregisterMethod; internal static MethodInfo ZSteamSocketSendMethod; internal static MethodInfo ZSteamSocketRecvMethod; internal static MethodInfo ZSteamSocketGetSendQueueSizeMethod; internal static MethodInfo RoutedRpcDataSerializeMethod; internal static MethodInfo RoutedRpcDataDeserializeMethod; internal static MethodInfo ZRoutedRpcRouteRPCMethod; internal static MethodInfo ZDOExtraDataGetFloatsMethod; internal static MethodInfo ZDOExtraDataGetVec3sMethod; internal static MethodInfo ZDOExtraDataGetQuaternionsMethod; internal static MethodInfo ZDOExtraDataGetIntsMethod; internal static MethodInfo ZDOExtraDataGetLongsMethod; internal static MethodInfo ZDOExtraDataGetStringsMethod; internal static MethodInfo ZDOExtraDataGetByteArraysMethod; internal static MethodInfo ZDOExtraDataGetConnectionMethod; private static readonly Dictionary> FieldCache = new Dictionary>(); private static readonly Dictionary KeyValueAccessorsByType = new Dictionary(); internal static object ZNetInstance => ZNetInstanceField?.GetValue(null); internal static object ZDOManInstance => ZDOManInstanceField?.GetValue(null); private static void LogCapabilitySummary() { try { if (ModConfig.DebugLogging != null && ModConfig.DebugLogging.Value && Plugin.Log != null) { Plugin.Log.LogDebug((object)("Reflection capabilities: Net[" + Status(ZNetType) + " type, " + Status(ZRpcGetSocketMethod) + " rpcSocket, " + Status(ZSteamSocketSendMethod) + " socketSend] ZDO[" + Status(ZDOType) + " type, " + Status(SendZDOsMethod) + " send, " + Status(ZDOExtraDataGetFloatsMethod) + " extraData] RPC[" + Status(ZRoutedRpcType) + " type, " + Status(RoutedRpcDataDeserializeMethod) + " data, " + Status(ZRoutedRpcRouteRPCMethod) + " route] Gameplay[" + Status(ZNetViewType) + " nview, " + Status(PlayerGetPlayerIDMethod) + " playerId, " + Status(ZNetViewGetZDOMethod) + " zdo]")); } } catch (Exception ex) { try { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("Reflection capability summary skipped: " + ex.Message)); } } catch { } } } private static string Status(object value) { if (value == null) { return "missing"; } return "ok"; } internal static void Initialize() { FieldCache.Clear(); KeyValueAccessorsByType.Clear(); InitializeNetReflection(); InitializeZdoReflection(); InitializeRpcReflection(); InitializeGameplayReflection(); NetReflection.Initialize(); ZdoReflection.Initialize(); RpcReflection.Initialize(); GameplayReflection.Initialize(); ZPackageTools.Initialize(); LogCapabilitySummary(); } private static FieldInfo FieldByQualifiedName(string typeAndField) { if (string.IsNullOrEmpty(typeAndField)) { return null; } int num = typeAndField.LastIndexOf(':'); if (num <= 0 || num >= typeAndField.Length - 1) { return null; } return SilentField(AccessTools.TypeByName(typeAndField.Substring(0, num)), typeAndField.Substring(num + 1)); } internal static FieldInfo SilentField(Type type, string name) { if (type == null || string.IsNullOrEmpty(name)) { return null; } Type type2 = type; while (type2 != null) { FieldInfo field = type2.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field; } type2 = type2.BaseType; } return null; } private static void InitializeGameplayReflection() { ZNetViewType = AccessTools.TypeByName("ZNetView"); MonsterAIType = AccessTools.TypeByName("MonsterAI"); PlayerType = AccessTools.TypeByName("Player"); CharacterNViewField = FieldByQualifiedName("Character:m_nview"); MonsterAINViewField = FieldByQualifiedName("MonsterAI:m_nview"); ZNetViewZdoField = SilentField(ZNetViewType, "m_zdo"); PlayerGetPlayerIDMethod = AccessTools.Method(PlayerType, "GetPlayerID", (Type[])null, (Type[])null); ZNetViewGetZDOMethod = AccessTools.Method(ZNetViewType, "GetZDO", (Type[])null, (Type[])null); ZNetViewClaimOwnershipMethod = AccessTools.Method(ZNetViewType, "ClaimOwnership", (Type[])null, (Type[])null); } internal static object GetZdoFromNView(object nview) { if (nview == null) { return null; } try { return ZNetViewGetZDOMethod?.Invoke(nview, null) ?? ZNetViewZdoField?.GetValue(nview); } catch { return null; } } internal static object GetNViewFromCharacterLike(object instance) { if (instance == null) { return null; } try { return CharacterNViewField?.GetValue(instance); } catch { } try { return MonsterAINViewField?.GetValue(instance); } catch { } try { return 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 || PlayerGetPlayerIDMethod == null) { return false; } try { return TryConvertToLong(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 (PlayerType != null) { return PlayerType.IsAssignableFrom(type); } return false; } return true; } private static void InitializeNetReflection() { ZNetType = AccessTools.TypeByName("ZNet"); ZNetPeerType = AccessTools.TypeByName("ZNetPeer"); ZRpcType = AccessTools.TypeByName("ZRpc"); ZPackageType = AccessTools.TypeByName("ZPackage"); ZSteamSocketType = AccessTools.TypeByName("ZSteamSocket"); ZNetInstanceField = SilentField(ZNetType, "instance") ?? SilentField(ZNetType, "m_instance") ?? SilentField(ZNetType, "s_instance"); ZNetPeersField = SilentField(ZNetType, "m_peers"); PeerRpcField = FieldByQualifiedName("ZDOMan+ZDOPeer:m_peer") ?? FieldByQualifiedName("ZDOPeer:m_peer") ?? FieldByQualifiedName("ZDOMan+ZDOPeer:m_rpc") ?? FieldByQualifiedName("ZDOPeer:m_rpc"); PeerUidField = FieldByQualifiedName("ZDOMan+ZDOPeer:m_uid") ?? FieldByQualifiedName("ZDOPeer:m_uid"); PeerRefPosField = FieldByQualifiedName("ZDOMan+ZDOPeer:m_refPos") ?? FieldByQualifiedName("ZDOPeer:m_refPos"); ZNetPeerRpcField = SilentField(ZNetPeerType, "m_rpc"); ZNetPeerUidField = SilentField(ZNetPeerType, "m_uid"); ZNetPeerRefPosField = SilentField(ZNetPeerType, "m_refPos"); ZSteamSocketSendQueueField = SilentField(ZSteamSocketType, "m_sendQueue"); ZNetIsServerMethod = AccessTools.Method(ZNetType, "IsServer", (Type[])null, (Type[])null); ZNetIsDedicatedMethod = AccessTools.Method(ZNetType, "IsDedicated", (Type[])null, (Type[])null); ZNetGetPeersMethod = AccessTools.Method(ZNetType, "GetPeers", (Type[])null, (Type[])null); ZNetGetReferencePositionMethod = AccessTools.Method(ZNetType, "GetReferencePosition", (Type[])null, (Type[])null); ZRpcGetSocketMethod = AccessTools.Method(ZRpcType, "GetSocket", (Type[])null, (Type[])null); ZRpcInvokeMethod = AccessTools.Method(ZRpcType, "Invoke", new Type[2] { typeof(string), typeof(object[]) }, (Type[])null); ZRpcUnregisterMethod = AccessTools.Method(ZRpcType, "Unregister", new Type[1] { typeof(string) }, (Type[])null); ZRpcRegisterGenericPackageMethod = FindZRpcPackageRegisterMethod(); ZSteamSocketSendMethod = AccessTools.Method(ZSteamSocketType, "Send", new Type[1] { ZPackageType }, (Type[])null) ?? AccessTools.Method(ZSteamSocketType, "Send", (Type[])null, (Type[])null); ZSteamSocketRecvMethod = AccessTools.Method(ZSteamSocketType, "Recv", (Type[])null, (Type[])null); ZSteamSocketGetSendQueueSizeMethod = AccessTools.Method(ZSteamSocketType, "GetSendQueueSize", (Type[])null, (Type[])null); } private static MethodInfo FindZRpcPackageRegisterMethod() { if (ZRpcType == null || ZPackageType == null) { return null; } MethodInfo[] methods = ZRpcType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "Register") && methodInfo.IsGenericMethodDefinition && methodInfo.GetGenericArguments().Length == 1) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 2 && parameters[0].ParameterType == typeof(string)) { return methodInfo.MakeGenericMethod(ZPackageType); } } } return null; } internal static bool IsServer() { try { object zNetInstance = ZNetInstance; if (zNetInstance == null || ZNetIsServerMethod == null) { return false; } return (bool)ZNetIsServerMethod.Invoke(zNetInstance, null); } catch { return false; } } internal static bool IsDedicatedServer() { try { object zNetInstance = ZNetInstance; if (zNetInstance == null || ZNetIsDedicatedMethod == null) { return false; } return (bool)ZNetIsDedicatedMethod.Invoke(zNetInstance, null); } catch { return false; } } internal static bool TryGetPeerUid(object zdoPeer, out long uid) { uid = 0L; try { if (zdoPeer == null) { return false; } FieldInfo fieldInfo = CachedField(zdoPeer.GetType(), "m_uid"); if (fieldInfo != null && TryConvertToLong(fieldInfo.GetValue(zdoPeer), out uid)) { return true; } if (PeerUidField != null && TryConvertToLong(PeerUidField.GetValue(zdoPeer), out uid)) { return true; } return TryGetUidFromPeerObject(GetPeerRpc(zdoPeer), out uid); } catch { return false; } } internal static bool TryGetUidFromPeerObject(object peerOrRpc, out long uid) { uid = 0L; if (peerOrRpc == null) { return false; } try { FieldInfo fieldInfo = CachedField(peerOrRpc.GetType(), "m_uid"); if (fieldInfo == null) { return false; } return TryConvertToLong(fieldInfo.GetValue(peerOrRpc), out uid); } catch { } return false; } internal static object GetPeerRpc(object peer) { try { if (peer == null) { return null; } object obj = CachedField(peer.GetType(), "m_rpc")?.GetValue(peer); if (obj != null) { return obj; } object obj2 = PeerRpcField?.GetValue(peer); if (obj2 == null) { return null; } return CachedField(obj2.GetType(), "m_rpc")?.GetValue(obj2) ?? obj2; } catch { return null; } } internal static object GetSocketFromRpc(object rpc) { try { return (rpc == null) ? null : 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_0008: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) try { if (peer == null) { return Vector3.zero; } if (CachedField(peer.GetType(), "m_refPos")?.GetValue(peer) is Vector3 result) { return result; } if (PeerRefPosField?.GetValue(peer) is Vector3 result2) { return result2; } object obj = CachedField(peer.GetType(), "m_peer")?.GetValue(peer); if (obj != null && obj != peer) { object obj2 = CachedField(obj.GetType(), "m_refPos")?.GetValue(obj); if (obj2 is Vector3) { return (Vector3)obj2; } } } catch { } return Vector3.zero; } internal static int GetSendQueueSizeForPeer(object zdoPeer) { try { object peerRpc = GetPeerRpc(zdoPeer); if (peerRpc == null) { return 0; } object obj = ZRpcGetSocketMethod?.Invoke(peerRpc, null); if (obj == null) { return 0; } object obj2 = ZSteamSocketGetSendQueueSizeMethod?.Invoke(obj, null) ?? AccessTools.Method(obj.GetType(), "GetSendQueueSize", (Type[])null, (Type[])null)?.Invoke(obj, null); if (obj2 is int result) { return result; } if (obj2 is long) { long val = (long)obj2; return (int)Math.Min(2147483647L, val); } } catch { } return 0; } [IteratorStateMachine(typeof(d__111))] internal static IEnumerable EnumerateZNetPeers() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__111(-2); } internal static Vector3 GetReferencePosition(Vector3 fallback) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) try { object zNetInstance = ZNetInstance; if (zNetInstance != null && ZNetGetReferencePositionMethod != null) { object obj = ZNetGetReferencePositionMethod.Invoke(zNetInstance, null); if (obj is Vector3) { return (Vector3)obj; } } } catch { } return fallback; } internal static bool TryConvertToLong(object value, out long result) { result = 0L; if (value is long num) { result = num; return true; } if (value is ulong num2) { result = (long)num2; return true; } if (value is int num3) { result = num3; return true; } if (value is uint num4) { result = num4; return true; } if (value is short num5) { result = num5; return true; } if (value is ushort num6) { result = num6; return true; } return false; } internal static bool TryConvertToUInt(object value, out uint result) { result = 0u; if (value is uint num) { result = num; return true; } if (value is int num2 && num2 >= 0) { result = (uint)num2; return true; } if (value is ulong num3 && num3 <= uint.MaxValue) { result = (uint)num3; return true; } if (value is long num4 && num4 >= 0 && num4 <= uint.MaxValue) { result = (uint)num4; return true; } if (value is ushort num5) { result = num5; return true; } if (value is short num6 && num6 >= 0) { result = (uint)num6; return true; } if (value is byte b) { result = b; return true; } return false; } internal static FieldInfo CachedField(Type type, string name) { if (type == null || string.IsNullOrEmpty(name)) { return null; } lock (FieldCache) { if (!FieldCache.TryGetValue(type, out var value)) { value = new Dictionary(); FieldCache[type] = value; } if (!value.TryGetValue(name, out var value2)) { value2 = (value[name] = SilentField(type, name)); } return value2; } } internal static bool TryReadKeyValueEntry(object entry, out int key, out object value) { key = 0; value = null; if (entry == null) { return false; } try { KeyValueEntryAccessors keyValueAccessors = GetKeyValueAccessors(entry.GetType()); if (keyValueAccessors?.Key == null || keyValueAccessors.Value == null) { return false; } object value2 = keyValueAccessors.Key.GetValue(entry, null); key = Convert.ToInt32(value2); value = keyValueAccessors.Value.GetValue(entry, null); return true; } catch { return false; } } internal static bool TryReadKeyValueKey(object entry, out int key) { key = 0; if (entry == null) { return false; } try { KeyValueEntryAccessors keyValueAccessors = GetKeyValueAccessors(entry.GetType()); if (keyValueAccessors?.Key == null) { return false; } object value = keyValueAccessors.Key.GetValue(entry, null); key = Convert.ToInt32(value); return true; } catch { return false; } } private static KeyValueEntryAccessors GetKeyValueAccessors(Type type) { if (type == null) { return null; } lock (KeyValueAccessorsByType) { if (!KeyValueAccessorsByType.TryGetValue(type, out var value)) { value = new KeyValueEntryAccessors(type.GetProperty("Key"), type.GetProperty("Value")); KeyValueAccessorsByType[type] = value; } return value; } } private static void InitializeRpcReflection() { ZRoutedRpcType = AccessTools.TypeByName("ZRoutedRpc"); RoutedRPCDataType = AccessTools.TypeByName("ZRoutedRpc+RoutedRPCData") ?? AccessTools.TypeByName("RoutedRPCData"); RoutedRpcDataSenderPeerIdField = SilentField(RoutedRPCDataType, "m_senderPeerID"); RoutedRpcDataTargetPeerIdField = SilentField(RoutedRPCDataType, "m_targetPeerID"); RoutedRpcDataTargetZdoField = SilentField(RoutedRPCDataType, "m_targetZDO"); RoutedRpcDataMethodHashField = SilentField(RoutedRPCDataType, "m_methodHash"); RoutedRpcDataParametersField = SilentField(RoutedRPCDataType, "m_parameters"); RoutedRpcDataSerializeMethod = AccessTools.Method(RoutedRPCDataType, "Serialize", (Type[])null, (Type[])null); RoutedRpcDataDeserializeMethod = AccessTools.Method(RoutedRPCDataType, "Deserialize", (Type[])null, (Type[])null); ZRoutedRpcRouteRPCMethod = AccessTools.Method(ZRoutedRpcType, "RouteRPC", new Type[1] { RoutedRPCDataType }, (Type[])null) ?? AccessTools.Method(ZRoutedRpcType, "RouteRPC", (Type[])null, (Type[])null); } private static void InitializeZdoReflection() { ZDOManType = AccessTools.TypeByName("ZDOMan"); ZDOType = AccessTools.TypeByName("ZDO"); ZDOIDType = AccessTools.TypeByName("ZDOID"); ZDOExtraDataType = AccessTools.TypeByName("ZDOExtraData"); ZDOVarsType = AccessTools.TypeByName("ZDOVars"); ZDOManInstanceField = SilentField(ZDOManType, "instance") ?? SilentField(ZDOManType, "s_instance"); ZDOManPeersField = SilentField(ZDOManType, "m_peers"); ZDOManNextSendPeerField = SilentField(ZDOManType, "m_nextSendPeer"); ZDOManSendTimerField = SilentField(ZDOManType, "m_sendTimer"); ZDOManSessionIdField = SilentField(ZDOManType, "m_sessionID"); ZDOObjectsBySectorField = SilentField(ZDOManType, "m_objectsBySector"); ZDOUidField = SilentField(ZDOType, "m_uid"); ZDOPrefabField = SilentField(ZDOType, "m_prefab"); ZDORotationField = SilentField(ZDOType, "m_rotation"); ZDODataRevisionField = SilentField(ZDOType, "k__BackingField"); ZDOOwnerRevisionField = SilentField(ZDOType, "k__BackingField"); SendZDOsMethod = AccessTools.Method(ZDOManType, "SendZDOs", (Type[])null, (Type[])null); ZDOGetVec3Method = AccessTools.Method(ZDOType, "GetVec3", new Type[2] { typeof(int), typeof(Vector3) }, (Type[])null); ZDOGetQuaternionMethod = AccessTools.Method(ZDOType, "GetQuaternion", new Type[2] { typeof(int), typeof(Quaternion) }, (Type[])null); ZDOGetPositionMethod = AccessTools.Method(ZDOType, "GetPosition", (Type[])null, (Type[])null); ZDOGetRotationMethod = AccessTools.Method(ZDOType, "GetRotation", (Type[])null, (Type[])null); ZDOGetOwnerMethod = AccessTools.Method(ZDOType, "GetOwner", (Type[])null, (Type[])null); ZDOSetOwnerMethod = AccessTools.Method(ZDOType, "SetOwner", new Type[1] { typeof(long) }, (Type[])null); ZDOSetOwnerInternalMethod = AccessTools.Method(ZDOType, "SetOwnerInternal", new Type[1] { typeof(long) }, (Type[])null); ZDOGetPrefabMethod = AccessTools.Method(ZDOType, "GetPrefab", (Type[])null, (Type[])null); ZDOPersistentGetter = AccessTools.PropertyGetter(ZDOType, "Persistent") ?? AccessTools.Method(ZDOType, "get_Persistent", (Type[])null, (Type[])null); ZDODistantGetter = AccessTools.PropertyGetter(ZDOType, "Distant") ?? AccessTools.Method(ZDOType, "get_Distant", (Type[])null, (Type[])null); ZDOTypeGetter = AccessTools.PropertyGetter(ZDOType, "Type") ?? AccessTools.Method(ZDOType, "get_Type", (Type[])null, (Type[])null); ZDOIDIsNoneMethod = AccessTools.Method(ZDOIDType, "IsNone", (Type[])null, (Type[])null); ZDOIDUserIDGetter = AccessTools.PropertyGetter(ZDOIDType, "UserID") ?? AccessTools.Method(ZDOIDType, "get_UserID", (Type[])null, (Type[])null); ZDOIDIDGetter = AccessTools.PropertyGetter(ZDOIDType, "ID") ?? AccessTools.Method(ZDOIDType, "get_ID", (Type[])null, (Type[])null); ZDOManForceSendZDOMethod = AccessTools.Method(ZDOManType, "ForceSendZDO", new Type[1] { ZDOIDType }, (Type[])null) ?? AccessTools.Method(ZDOManType, "ForceSendZDO", (Type[])null, (Type[])null); ZDOManGetZDOMethod = AccessTools.Method(ZDOManType, "GetZDO", new Type[1] { ZDOIDType }, (Type[])null) ?? AccessTools.Method(ZDOManType, "GetZDO", (Type[])null, (Type[])null); ZDOManGetSessionIdMethod = AccessTools.Method(ZDOManType, "GetSessionID", (Type[])null, (Type[])null); InitializeZdoExtraDataReflection(); } private static void InitializeZdoExtraDataReflection() { if (!(ZDOExtraDataType == null) && !(ZDOIDType == null)) { ZDOExtraDataGetFloatsMethod = AccessTools.Method(ZDOExtraDataType, "GetFloats", new Type[1] { ZDOIDType }, (Type[])null); ZDOExtraDataGetVec3sMethod = AccessTools.Method(ZDOExtraDataType, "GetVec3s", new Type[1] { ZDOIDType }, (Type[])null); ZDOExtraDataGetQuaternionsMethod = AccessTools.Method(ZDOExtraDataType, "GetQuaternions", new Type[1] { ZDOIDType }, (Type[])null); ZDOExtraDataGetIntsMethod = AccessTools.Method(ZDOExtraDataType, "GetInts", new Type[1] { ZDOIDType }, (Type[])null); ZDOExtraDataGetLongsMethod = AccessTools.Method(ZDOExtraDataType, "GetLongs", new Type[1] { ZDOIDType }, (Type[])null); ZDOExtraDataGetStringsMethod = AccessTools.Method(ZDOExtraDataType, "GetStrings", new Type[1] { ZDOIDType }, (Type[])null); ZDOExtraDataGetByteArraysMethod = AccessTools.Method(ZDOExtraDataType, "GetByteArrays", new Type[1] { ZDOIDType }, (Type[])null); ZDOExtraDataGetConnectionMethod = AccessTools.Method(ZDOExtraDataType, "GetConnection", new Type[1] { ZDOIDType }, (Type[])null); } } internal static bool TryGetZdoOwner(object zdo, out long owner) { owner = 0L; try { return TryConvertToLong(ZDOGetOwnerMethod?.Invoke(zdo, null), out owner); } catch { } return false; } internal static bool TrySetZdoOwner(object zdo, long uid) { try { if (zdo == null) { return false; } if (ZDOSetOwnerMethod != null) { ZDOSetOwnerMethod.Invoke(zdo, new object[1] { uid }); return true; } if (ZDOSetOwnerInternalMethod != null) { ZDOSetOwnerInternalMethod.Invoke(zdo, new object[1] { uid }); return true; } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("SetOwner failed: " + ex.Message)); } } return false; } internal static object GetZdoIdObject(object zdo) { try { return ZDOUidField?.GetValue(zdo); } catch { return null; } } internal static bool TryGetZdoIdKey(object zdo, out ZdoIdKey key) { key = default(ZdoIdKey); if (zdo == null) { return false; } if (TryGetZdoIdKeyFromId(GetZdoIdObject(zdo), out key)) { return true; } key = ZdoIdKey.FromRuntimeObject(zdo); return true; } internal static bool TryGetZdoIdKeyFromId(object zdoId, out ZdoIdKey key) { key = default(ZdoIdKey); if (zdoId == null) { return false; } try { object? value = ZDOIDUserIDGetter?.Invoke(zdoId, null); object value2 = ZDOIDIDGetter?.Invoke(zdoId, null); if (TryConvertToLong(value, out var result) && TryConvertToUInt(value2, out var result2)) { key = new ZdoIdKey(result, result2); return true; } } catch { } try { string text = zdoId.ToString(); int num = text.IndexOf(':'); if (num > 0 && long.TryParse(text.Substring(0, num), out var result3) && uint.TryParse(text.Substring(num + 1), out var result4)) { key = new ZdoIdKey(result3, result4); return true; } } catch { } return false; } internal static void ForceSendZdo(object zdo) { try { object zDOManInstance = ZDOManInstance; object zdoIdObject = GetZdoIdObject(zdo); if (zDOManInstance != null && zdoIdObject != null && !(ZDOManForceSendZDOMethod == null)) { ZDOManForceSendZDOMethod.Invoke(zDOManInstance, new object[1] { zdoIdObject }); } } catch { } } [IteratorStateMachine(typeof(d__131))] internal static IEnumerable EnumerateZdoPeers(object zdoMan) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__131(-2) { <>3__zdoMan = zdoMan }; } internal static object GetZdoById(object zdoId) { try { object zDOManInstance = ZDOManInstance; if (zDOManInstance == null || zdoId == null || ZDOManGetZDOMethod == null) { return null; } return ZDOManGetZDOMethod.Invoke(zDOManInstance, new object[1] { zdoId }); } catch { return null; } } internal static bool IsZdoIdNone(object zdoId) { try { if (zdoId == null) { return true; } if (ZDOIDIsNoneMethod != null) { object obj = ZDOIDIsNoneMethod.Invoke(zdoId, null); if (obj is bool) { return (bool)obj; } } } catch { } return false; } internal static bool TryGetZdoPersistent(object zdo, out bool persistent) { persistent = false; if (zdo == null) { return false; } try { object obj = ZDOPersistentGetter?.Invoke(zdo, null); if (obj is bool) { bool flag = (bool)obj; persistent = flag; return true; } } catch { } return false; } internal static bool TryGetZdoDistant(object zdo, out bool distant) { distant = false; if (zdo == null) { return false; } try { object obj = ZDODistantGetter?.Invoke(zdo, null); if (obj is bool) { bool flag = (bool)obj; distant = flag; return true; } } catch { } return false; } internal static bool TryGetServerSessionId(out long uid) { uid = 0L; try { object zDOManInstance = ZDOManInstance; if (zDOManInstance != null && ZDOManSessionIdField != null && TryConvertToLong(ZDOManSessionIdField.GetValue(zDOManInstance), out uid) && uid != 0L) { return true; } if (ZDOManGetSessionIdMethod != null && TryConvertToLong(ZDOManGetSessionIdMethod.Invoke(null, null), out uid) && uid != 0L) { return true; } } catch { } return false; } internal static int GetZdoPrefabHash(object zdo) { try { if (zdo == null) { return 0; } if (ZDOGetPrefabMethod != null) { object obj = ZDOGetPrefabMethod.Invoke(zdo, null); if (obj is int result) { return result; } if (obj != null) { return Convert.ToInt32(obj); } } if (ZDOPrefabField != null) { object value = ZDOPrefabField.GetValue(zdo); if (value is int result2) { return result2; } if (value != null) { return Convert.ToInt32(value); } } } catch { } return 0; } internal static Quaternion GetZdoRotation(object zdo, Quaternion fallback) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) try { if (zdo == null) { return fallback; } object obj = ZDOGetRotationMethod?.Invoke(zdo, null) ?? ZDORotationField?.GetValue(zdo); if (obj is Quaternion) { return (Quaternion)obj; } } catch { } return fallback; } internal static int GetZdoTypeValue(object zdo) { try { if (zdo == null || ZDOTypeGetter == null) { return 0; } object obj = ZDOTypeGetter.Invoke(zdo, null); return (obj != null) ? Convert.ToInt32(obj) : 0; } catch { } return 0; } internal static bool TryGetConnectionType(object connection, out int type) { type = 0; if (connection == null) { return false; } try { object obj = CachedField(connection.GetType(), "m_type")?.GetValue(connection); if (obj == null) { return false; } type = Convert.ToInt32(obj); return true; } catch { return false; } } internal static Vector3 TryGetZdoPosition(object zdo, Vector3 fallback) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) try { if (zdo != null) { object obj = ZDOGetPositionMethod?.Invoke(zdo, null); if (obj is Vector3) { return (Vector3)obj; } } } catch { } return fallback; } } internal static class ReflectionDelegateFactory { internal static Func BoxedFieldGetter(FieldInfo field) { try { if (field == null) { return null; } ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "instance"); return Expression.Lambda>(Expression.Convert(Expression.Field(field.IsStatic ? null : Expression.Convert(parameterExpression, field.DeclaringType), field), typeof(object)), new ParameterExpression[1] { parameterExpression }).Compile(); } catch { return null; } } internal static Action BoxedFieldSetter(FieldInfo field) { try { if (field == null || field.IsInitOnly) { return null; } ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "instance"); ParameterExpression parameterExpression2 = Expression.Parameter(typeof(object), "value"); return Expression.Lambda>(Expression.Assign(Expression.Field(field.IsStatic ? null : Expression.Convert(parameterExpression, field.DeclaringType), field), Expression.Convert(parameterExpression2, field.FieldType)), new ParameterExpression[2] { parameterExpression, parameterExpression2 }).Compile(); } catch { return null; } } internal static Func BoxedInstanceMethod(MethodInfo method) { try { if (method == null || method.ContainsGenericParameters || method.GetParameters().Length != 0) { return null; } ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "instance"); MethodCallExpression methodCallExpression = Expression.Call(method.IsStatic ? null : Expression.Convert(parameterExpression, method.DeclaringType), method); return Expression.Lambda>((method.ReturnType == typeof(void)) ? ((Expression)Expression.Block(methodCallExpression, Expression.Constant(null))) : ((Expression)Expression.Convert(methodCallExpression, typeof(object))), new ParameterExpression[1] { parameterExpression }).Compile(); } catch { return null; } } internal static Action InstanceAction1(MethodInfo method) { try { if (method == null || method.ContainsGenericParameters) { return null; } ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length != 1) { return null; } ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "instance"); ParameterExpression parameterExpression2 = Expression.Parameter(typeof(object), "arg"); MethodCallExpression methodCallExpression = Expression.Call(method.IsStatic ? null : Expression.Convert(parameterExpression, method.DeclaringType), method, Expression.Convert(parameterExpression2, parameters[0].ParameterType)); return Expression.Lambda>((method.ReturnType == typeof(void)) ? ((Expression)methodCallExpression) : ((Expression)Expression.Block(methodCallExpression, Expression.Empty())), new ParameterExpression[2] { parameterExpression, parameterExpression2 }).Compile(); } catch { return null; } } internal static Func Vector3FieldGetter(FieldInfo field) { try { if (field == null || field.FieldType != typeof(Vector3)) { return null; } ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "instance"); return Expression.Lambda>(Expression.Field(field.IsStatic ? null : Expression.Convert(parameterExpression, field.DeclaringType), field), new ParameterExpression[1] { parameterExpression }).Compile(); } catch { return null; } } internal static Func Vector3InstanceMethod(MethodInfo method) { try { if (method == null || method.ContainsGenericParameters || method.ReturnType != typeof(Vector3) || method.GetParameters().Length != 0) { return null; } ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "instance"); return Expression.Lambda>(Expression.Call(method.IsStatic ? null : Expression.Convert(parameterExpression, method.DeclaringType), method), new ParameterExpression[1] { parameterExpression }).Compile(); } catch { return null; } } internal static Func QuaternionFieldGetter(FieldInfo field) { try { if (field == null || field.FieldType != typeof(Quaternion)) { return null; } ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "instance"); return Expression.Lambda>(Expression.Field(field.IsStatic ? null : Expression.Convert(parameterExpression, field.DeclaringType), field), new ParameterExpression[1] { parameterExpression }).Compile(); } catch { return null; } } internal static Func QuaternionInstanceMethod(MethodInfo method) { try { if (method == null || method.ContainsGenericParameters || method.ReturnType != typeof(Quaternion) || method.GetParameters().Length != 0) { return null; } ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "instance"); return Expression.Lambda>(Expression.Call(method.IsStatic ? null : Expression.Convert(parameterExpression, method.DeclaringType), method), new ParameterExpression[1] { parameterExpression }).Compile(); } catch { return null; } } } internal enum RpcAoiKind { Visual, StateCritical, Unknown } internal static class RpcAoiRouter { private static readonly Dictionary Known = new Dictionary(); 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 list = new List(); 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 != 0) { 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); } } internal static class RpcReflection { private static Func _senderPeerIdGetter; private static Func _targetPeerIdGetter; private static Action _targetPeerIdSetter; private static Func _targetZdoGetter; private static Func _methodHashGetter; private static Action _deserializeAction; private static Action _routeRpcAction; internal static Type ZRoutedRpcType => ReflectionCache.ZRoutedRpcType; internal static Type RoutedRPCDataType => ReflectionCache.RoutedRPCDataType; internal static void Initialize() { _senderPeerIdGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.RoutedRpcDataSenderPeerIdField); _targetPeerIdGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.RoutedRpcDataTargetPeerIdField); _targetPeerIdSetter = ReflectionDelegateFactory.BoxedFieldSetter(ReflectionCache.RoutedRpcDataTargetPeerIdField); _targetZdoGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.RoutedRpcDataTargetZdoField); _methodHashGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.RoutedRpcDataMethodHashField); _deserializeAction = ReflectionDelegateFactory.InstanceAction1(ReflectionCache.RoutedRpcDataDeserializeMethod); _routeRpcAction = ReflectionDelegateFactory.InstanceAction1(ReflectionCache.ZRoutedRpcRouteRPCMethod); } internal static object CreateRoutedRpcData() { try { return (RoutedRPCDataType != null) ? Activator.CreateInstance(RoutedRPCDataType) : null; } catch { return null; } } internal static bool DeserializeRoutedRpcData(object routedData, object package) { if (routedData == null || package == null) { return false; } try { if (_deserializeAction != null) { _deserializeAction(routedData, package); } else { ReflectionCache.RoutedRpcDataDeserializeMethod?.Invoke(routedData, new object[1] { package }); } return true; } catch { return false; } } internal static int GetMethodHash(object routedData) { object obj = TryGet(_methodHashGetter, routedData); if (obj == null) { try { obj = ReflectionCache.RoutedRpcDataMethodHashField?.GetValue(routedData); } catch { } } if (obj == null) { return 0; } return Convert.ToInt32(obj); } internal static long GetTargetPeerId(object routedData) { object obj = TryGet(_targetPeerIdGetter, routedData); if (obj == null) { try { obj = ReflectionCache.RoutedRpcDataTargetPeerIdField?.GetValue(routedData); } catch { } } if (obj == null) { return 0L; } return Convert.ToInt64(obj); } internal static void SetTargetPeerId(object routedData, long uid) { try { if (_targetPeerIdSetter != null) { _targetPeerIdSetter(routedData, uid); } else { ReflectionCache.RoutedRpcDataTargetPeerIdField?.SetValue(routedData, uid); } } catch { } } internal static object GetTargetZdo(object routedData) { object obj = TryGet(_targetZdoGetter, routedData); if (obj != null) { return obj; } try { return ReflectionCache.RoutedRpcDataTargetZdoField?.GetValue(routedData); } catch { return null; } } internal static long GetSenderPeerId(object routedData) { object obj = TryGet(_senderPeerIdGetter, routedData); if (obj == null) { try { obj = ReflectionCache.RoutedRpcDataSenderPeerIdField?.GetValue(routedData); } catch { } } if (obj == null) { return 0L; } return Convert.ToInt64(obj); } internal static bool RouteRpc(object routedRpc, object routedData) { if (routedRpc == null || routedData == null) { return false; } try { if (_routeRpcAction != null) { _routeRpcAction(routedRpc, routedData); } else { ReflectionCache.ZRoutedRpcRouteRPCMethod?.Invoke(routedRpc, new object[1] { routedData }); } return true; } catch { return false; } } private static object TryGet(Func getter, object instance) { try { return (getter != null && instance != null) ? getter(instance) : null; } catch { return null; } } } [HarmonyPatch] internal static class ZSteamSocketRegisterGlobalCallbacksRatePatch { [CompilerGenerated] private sealed class d__1 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private CodeInstruction <>2__current; private int <>l__initialThreadId; private IEnumerable instructions; public IEnumerable <>3__instructions; private IEnumerator <>7__wrap1; CodeInstruction IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__1(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || (uint)(num - 1) <= 1u) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>7__wrap1 = instructions.GetEnumerator(); <>1__state = -3; break; case 1: <>1__state = -3; break; case 2: <>1__state = -3; break; } if (<>7__wrap1.MoveNext()) { CodeInstruction current = <>7__wrap1.Current; if (current.opcode == OpCodes.Ldc_I4 && current.operand is int num && num == 153600) { <>2__current = new CodeInstruction(OpCodes.Call, (object)AccessTools.PropertyGetter(typeof(ZSteamSocketRegisterGlobalCallbacksRatePatch), "SteamSendRateBytes")); <>1__state = 1; return true; } <>2__current = current; <>1__state = 2; return true; } <>m__Finally1(); <>7__wrap1 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap1 != null) { <>7__wrap1.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__1 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__1(0); } d__.instructions = <>3__instructions; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } 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); } [IteratorStateMachine(typeof(d__1))] private static IEnumerable Transpiler(IEnumerable instructions) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__1(-2) { <>3__instructions = instructions }; } } internal readonly struct ZdoIdKey : IEquatable { internal readonly long UserId; internal readonly uint Id; internal readonly int RuntimeId; internal readonly bool RuntimeFallback; internal ZdoIdKey(long userId, uint id) { UserId = userId; Id = id; RuntimeId = 0; RuntimeFallback = false; } private ZdoIdKey(int runtimeId) { UserId = 0L; Id = 0u; RuntimeId = runtimeId; RuntimeFallback = true; } internal static ZdoIdKey FromRuntimeObject(object obj) { return new ZdoIdKey((obj != null) ? RuntimeHelpers.GetHashCode(obj) : 0); } public bool Equals(ZdoIdKey other) { if (UserId == other.UserId && Id == other.Id && RuntimeId == other.RuntimeId) { return RuntimeFallback == other.RuntimeFallback; } return false; } public override bool Equals(object obj) { if (obj is ZdoIdKey other) { return Equals(other); } return false; } public override int GetHashCode() { int num = 17 * 31; long userId = UserId; int num2 = (num + userId.GetHashCode()) * 31; uint id = Id; return ((num2 + id.GetHashCode()) * 31 + RuntimeId) * 31 + (RuntimeFallback ? 1 : 0); } public override string ToString() { if (!RuntimeFallback) { long userId = UserId; string text = userId.ToString(); uint id = Id; return text + ":" + id; } int runtimeId = RuntimeId; return "runtime:" + runtimeId; } } internal static class ZdoKeyPolicy { private static readonly HashSet CriticalHashes = new HashSet(); private static readonly HashSet VelocityLikeHashes = new HashSet(); private static readonly HashSet PlayerLikeHashes = new HashSet(); private static readonly HashSet ShipLikeHashes = new HashSet(); internal static void Initialize() { CriticalHashes.Clear(); VelocityLikeHashes.Clear(); PlayerLikeHashes.Clear(); ShipLikeHashes.Clear(); Type zDOVarsType = ZdoReflection.ZDOVarsType; if (zDOVarsType == null) { return; } FieldInfo[] fields = zDOVarsType.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (!(fieldInfo.FieldType != typeof(int))) { int item; try { item = (int)fieldInfo.GetValue(null); } catch { continue; } string text = fieldInfo.Name.ToLowerInvariant(); bool flag = text.Contains("owner") || text.Contains("target") || text.Contains("health") || text.Contains("level") || text.Contains("spawn") || text.Contains("dead") || text.Contains("attack") || text.Contains("tame") || text.Contains("rider") || text.Contains("rudder") || text.Contains("bodyvel") || text.Contains("velocity") || text.Contains("velhash"); bool flag2 = text.Contains("vel") || text.Contains("velocity") || text.Contains("body") || text.Contains("initvel"); bool flag3 = text.Contains("player") || text.Contains("username") || text.Contains("playername") || text.Contains("emote") || text.Contains("hair") || text.Contains("beard") || text.Contains("skin") || text.Contains("modelindex") || text.Contains("gender"); bool num = text.Contains("ship") || text.Contains("rudder") || text.Contains("sail") || text.Contains("anchor") || text.Contains("waterlevel") || text.Contains("inwater") || text.Contains("forward") || text.Contains("ashlandsailer"); if (flag) { CriticalHashes.Add(item); } if (flag2) { VelocityLikeHashes.Add(item); } if (flag3) { PlayerLikeHashes.Add(item); } if (num) { ShipLikeHashes.Add(item); } } } if (ModConfig.DebugLogging.Value) { Plugin.Log.LogInfo((object)$"ZDO key policy initialized: critical={CriticalHashes.Count}, velocityLike={VelocityLikeHashes.Count}, playerLike={PlayerLikeHashes.Count}, shipLike={ShipLikeHashes.Count}"); } } internal static bool ShouldNeverCull(int hash) { if (CriticalHashes.Contains(hash)) { return true; } if (VelocityLikeHashes.Contains(hash)) { return true; } return false; } internal static bool LooksPlayerLike(object zdo) { return HasAnyHash(zdo, PlayerLikeHashes); } internal static bool LooksShipLike(object zdo) { return HasAnyHash(zdo, ShipLikeHashes); } private static bool HasAnyHash(object zdo, HashSet hashes) { if (zdo == null || hashes == null || hashes.Count == 0) { return false; } object idObject = ZdoReflection.GetIdObject(zdo); if (idObject == null) { return false; } if (!HasAnyHashInGroup(ReflectionCache.ZDOExtraDataGetFloatsMethod, idObject, hashes) && !HasAnyHashInGroup(ReflectionCache.ZDOExtraDataGetVec3sMethod, idObject, hashes) && !HasAnyHashInGroup(ReflectionCache.ZDOExtraDataGetQuaternionsMethod, idObject, hashes) && !HasAnyHashInGroup(ReflectionCache.ZDOExtraDataGetIntsMethod, idObject, hashes) && !HasAnyHashInGroup(ReflectionCache.ZDOExtraDataGetLongsMethod, idObject, hashes) && !HasAnyHashInGroup(ReflectionCache.ZDOExtraDataGetStringsMethod, idObject, hashes)) { return HasAnyHashInGroup(ReflectionCache.ZDOExtraDataGetByteArraysMethod, idObject, hashes); } return true; } private static bool HasAnyHashInGroup(MethodInfo method, object id, HashSet hashes) { if (method == null || id == null) { return false; } try { if (!(method.Invoke(null, new object[1] { id }) is IEnumerable enumerable)) { return false; } foreach (object item in enumerable) { if (item != null && ReflectionCache.TryReadKeyValueKey(item, out var key) && hashes.Contains(key)) { return true; } } } catch { } return false; } } internal static class ZdoReflection { [CompilerGenerated] private sealed class d__30 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; private int <>l__initialThreadId; private object zdoMan; public object <>3__zdoMan; private IEnumerator <>7__wrap1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__30(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; if (zdoMan == null || ReflectionCache.ZDOManPeersField == null) { return false; } IEnumerable enumerable = null; try { enumerable = ReflectionCache.ZDOManPeersField.GetValue(zdoMan) as IEnumerable; } catch { } if (enumerable == null) { return false; } <>7__wrap1 = enumerable.GetEnumerator(); <>1__state = -3; break; } case 1: <>1__state = -3; break; } if (<>7__wrap1.MoveNext()) { object current = <>7__wrap1.Current; <>2__current = current; <>1__state = 1; return true; } <>m__Finally1(); <>7__wrap1 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap1 is IDisposable disposable) { disposable.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__30 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__30(0); } d__.zdoMan = <>3__zdoMan; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } private static Func _zdoUidGetter; private static Func _zdoOwnerGetter; private static Func _zdoPrefabGetter; private static Func _zdoPrefabFieldGetter; private static Func _zdoPersistentGetter; private static Func _zdoDistantGetter; private static Func _zdoTypeGetter; private static Func _connectionTypeGetter; private static Func _zdoPositionGetter; private static Func _zdoRotationGetter; private static Func _zdoRotationFieldGetter; internal static Type ZDOManType => ReflectionCache.ZDOManType; internal static Type ZDOType => ReflectionCache.ZDOType; internal static Type ZDOIDType => ReflectionCache.ZDOIDType; internal static Type ZDOVarsType => ReflectionCache.ZDOVarsType; internal static MethodInfo SendZDOsMethod => ReflectionCache.SendZDOsMethod; internal static object ZDOManInstance => ReflectionCache.ZDOManInstanceField?.GetValue(null); internal static void Initialize() { _zdoUidGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.ZDOUidField); _zdoOwnerGetter = ReflectionDelegateFactory.BoxedInstanceMethod(ReflectionCache.ZDOGetOwnerMethod); _zdoPrefabGetter = ReflectionDelegateFactory.BoxedInstanceMethod(ReflectionCache.ZDOGetPrefabMethod); _zdoPrefabFieldGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.ZDOPrefabField); _zdoPersistentGetter = ReflectionDelegateFactory.BoxedInstanceMethod(ReflectionCache.ZDOPersistentGetter); _zdoDistantGetter = ReflectionDelegateFactory.BoxedInstanceMethod(ReflectionCache.ZDODistantGetter); _zdoTypeGetter = ReflectionDelegateFactory.BoxedInstanceMethod(ReflectionCache.ZDOTypeGetter); _connectionTypeGetter = null; _zdoPositionGetter = ReflectionDelegateFactory.Vector3InstanceMethod(ReflectionCache.ZDOGetPositionMethod); _zdoRotationGetter = ReflectionDelegateFactory.QuaternionInstanceMethod(ReflectionCache.ZDOGetRotationMethod); _zdoRotationFieldGetter = ReflectionDelegateFactory.QuaternionFieldGetter(ReflectionCache.ZDORotationField); } internal static bool TryGetOwner(object zdo, out long owner) { owner = 0L; if (zdo == null) { return false; } try { return ReflectionCache.TryConvertToLong(TryGet(_zdoOwnerGetter, zdo) ?? ReflectionCache.ZDOGetOwnerMethod?.Invoke(zdo, null), out owner); } catch { return false; } } internal static bool TrySetOwner(object zdo, long uid) { try { if (zdo == null) { return false; } if (ReflectionCache.ZDOSetOwnerMethod != null) { ReflectionCache.ZDOSetOwnerMethod.Invoke(zdo, new object[1] { uid }); return true; } if (ReflectionCache.ZDOSetOwnerInternalMethod != null) { ReflectionCache.ZDOSetOwnerInternalMethod.Invoke(zdo, new object[1] { uid }); return true; } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("SetOwner failed: " + ex.Message)); } } return false; } internal static object GetIdObject(object zdo) { if (zdo == null) { return null; } try { return TryGet(_zdoUidGetter, zdo) ?? ReflectionCache.ZDOUidField?.GetValue(zdo); } catch { return null; } } internal static bool TryGetIdKey(object zdo, out ZdoIdKey key) { key = default(ZdoIdKey); if (zdo == null) { return false; } if (TryGetIdKeyFromId(GetIdObject(zdo), out key)) { return true; } key = ZdoIdKey.FromRuntimeObject(zdo); return true; } internal static bool TryGetIdKeyFromId(object zdoId, out ZdoIdKey key) { key = default(ZdoIdKey); if (zdoId == null) { return false; } try { object? value = ReflectionCache.ZDOIDUserIDGetter?.Invoke(zdoId, null); object value2 = ReflectionCache.ZDOIDIDGetter?.Invoke(zdoId, null); if (ReflectionCache.TryConvertToLong(value, out var result) && ReflectionCache.TryConvertToUInt(value2, out var result2)) { key = new ZdoIdKey(result, result2); return true; } } catch { } try { string text = zdoId.ToString(); int num = text.IndexOf(':'); if (num > 0 && long.TryParse(text.Substring(0, num), out var result3) && uint.TryParse(text.Substring(num + 1), out var result4)) { key = new ZdoIdKey(result3, result4); return true; } } catch { } return false; } internal static void ForceSend(object zdo) { try { object zDOManInstance = ZDOManInstance; object idObject = GetIdObject(zdo); if (zDOManInstance != null && idObject != null && !(ReflectionCache.ZDOManForceSendZDOMethod == null)) { ReflectionCache.ZDOManForceSendZDOMethod.Invoke(zDOManInstance, new object[1] { idObject }); } } catch { } } [IteratorStateMachine(typeof(d__30))] internal static IEnumerable EnumeratePeers(object zdoMan) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__30(-2) { <>3__zdoMan = zdoMan }; } internal static object GetById(object zdoId) { try { object zDOManInstance = ZDOManInstance; if (zDOManInstance == null || zdoId == null || ReflectionCache.ZDOManGetZDOMethod == null) { return null; } return ReflectionCache.ZDOManGetZDOMethod.Invoke(zDOManInstance, new object[1] { zdoId }); } catch { return null; } } internal static bool IsIdNone(object zdoId) { try { if (zdoId == null) { return true; } if (ReflectionCache.ZDOIDIsNoneMethod != null) { object obj = ReflectionCache.ZDOIDIsNoneMethod.Invoke(zdoId, null); if (obj is bool) { return (bool)obj; } } } catch { } return false; } internal static bool TryGetPersistent(object zdo, out bool persistent) { persistent = false; if (zdo == null) { return false; } try { object obj = TryGet(_zdoPersistentGetter, zdo) ?? ReflectionCache.ZDOPersistentGetter?.Invoke(zdo, null); if (obj is bool) { bool flag = (bool)obj; persistent = flag; return true; } } catch { } return false; } internal static bool TryGetDistant(object zdo, out bool distant) { distant = false; if (zdo == null) { return false; } try { object obj = TryGet(_zdoDistantGetter, zdo) ?? ReflectionCache.ZDODistantGetter?.Invoke(zdo, null); if (obj is bool) { bool flag = (bool)obj; distant = flag; return true; } } catch { } return false; } internal static bool TryGetServerSessionId(out long uid) { uid = 0L; try { object zDOManInstance = ZDOManInstance; if (zDOManInstance != null && ReflectionCache.ZDOManSessionIdField != null && ReflectionCache.TryConvertToLong(ReflectionCache.ZDOManSessionIdField.GetValue(zDOManInstance), out uid) && uid != 0L) { return true; } if (ReflectionCache.ZDOManGetSessionIdMethod != null && ReflectionCache.TryConvertToLong(ReflectionCache.ZDOManGetSessionIdMethod.Invoke(null, null), out uid) && uid != 0L) { return true; } } catch { } return false; } internal static int GetPrefabHash(object zdo) { try { if (zdo == null) { return 0; } object obj = TryGet(_zdoPrefabGetter, zdo) ?? TryGet(_zdoPrefabFieldGetter, zdo); if (obj is int result) { return result; } if (obj != null) { return Convert.ToInt32(obj); } } catch { } return 0; } internal static Quaternion GetRotation(object zdo, Quaternion fallback) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) try { if (zdo == null) { return fallback; } if (TryGetQuaternion(_zdoRotationGetter, zdo, out var value)) { return value; } if (TryGetQuaternion(_zdoRotationFieldGetter, zdo, out var value2)) { return value2; } object obj = ReflectionCache.ZDOGetRotationMethod?.Invoke(zdo, null) ?? ReflectionCache.ZDORotationField?.GetValue(zdo); if (obj is Quaternion) { return (Quaternion)obj; } } catch { } return fallback; } internal static int GetTypeValue(object zdo) { try { if (zdo == null) { return 0; } object obj = TryGet(_zdoTypeGetter, zdo) ?? ReflectionCache.ZDOTypeGetter?.Invoke(zdo, null); return (obj != null) ? Convert.ToInt32(obj) : 0; } catch { } return 0; } internal static bool TryGetConnectionType(object connection, out int type) { type = 0; if (connection == null) { return false; } try { object obj = TryGet(_connectionTypeGetter, connection); if (obj == null) { FieldInfo fieldInfo = ReflectionCache.CachedField(connection.GetType(), "m_type"); _connectionTypeGetter = ReflectionDelegateFactory.BoxedFieldGetter(fieldInfo); obj = TryGet(_connectionTypeGetter, connection) ?? fieldInfo?.GetValue(connection); } if (obj == null) { return false; } type = Convert.ToInt32(obj); return true; } catch { return false; } } internal static Vector3 GetPosition(object zdo, Vector3 fallback) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) try { if (zdo == null) { return fallback; } if (TryGetVector3(_zdoPositionGetter, zdo, out var value)) { return value; } object obj = ReflectionCache.ZDOGetPositionMethod?.Invoke(zdo, null); if (obj is Vector3) { return (Vector3)obj; } } catch { } return fallback; } private static object TryGet(Func getter, object instance) { try { return (getter != null && instance != null) ? getter(instance) : null; } catch { return null; } } private static bool TryGetVector3(Func getter, object instance, out Vector3 value) { //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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) value = Vector3.zero; try { if (getter == null || instance == null) { return false; } value = getter(instance); return true; } catch { return false; } } private static bool TryGetQuaternion(Func getter, object instance, out Quaternion value) { //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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) value = Quaternion.identity; try { if (getter == null || instance == null) { return false; } value = getter(instance); return true; } catch { return false; } } } [HarmonyPatch] internal static class ZDOManSendSchedulerPatch { private sealed class LaggingPeerSkipState { public double FirstSkipTime; public double LastBackfillTime; public int SkipCount; } private const double SummaryIntervalSeconds = 10.0; private static readonly Dictionary LaggingPeerSkips = new Dictionary(); private static double _nextSummaryTime; private static int _summaryPasses; private static int _summaryAttempted; private static int _summarySent; private static int _summarySkipped; private static int _summaryBackfills; private static double _summaryElapsedSeconds; private static double _summaryMaxPassSeconds; private static double _summaryMaxPeerSeconds; private static long _summaryMaxPeerUid; private static int _summaryMaxQueueBefore; private static int _summaryMaxQueueAfter; private static int _lastPeerCount; private static int _lastPressuredPeers; private static int _lastMaxQueue; private static float _lastInterval; private static double _lastPassTime; private static double _lastPassSeconds; private static double _lastMaxPeerSeconds; private static int _lastBatch; private static int _lastAttempted; private static int _lastSent; private static int _lastSkipped; private static int _lastBackfilled; private static long _lastMaxPeerUid; private static int _lastMaxPeerQueueBefore; private static int _lastMaxPeerQueueAfter; private static MethodBase TargetMethod() { return AccessTools.Method(ReflectionCache.ZDOManType ?? AccessTools.TypeByName("ZDOMan"), "SendZDOToPeers2", new Type[1] { typeof(float) }, (Type[])null); } internal static void ClearPeer(long uid) { if (uid != 0L) { LaggingPeerSkips.Remove(uid); } } internal static string DescribeRecentState() { double num = ((_lastPassTime > 0.0) ? Math.Max(0.0, Time.realtimeSinceStartupAsDouble - _lastPassTime) : (-1.0)); string arg = ((num >= 0.0) ? $"{num:F2}s" : "n/a"); return $"schedulerRecent age={arg} passMs={_lastPassSeconds * 1000.0:F2} maxPeerMs={_lastMaxPeerSeconds * 1000.0:F2} " + $"peers={_lastPeerCount} pressured={_lastPressuredPeers} maxQueue={FormatBytes(_lastMaxQueue)} interval={_lastInterval:F3}s " + $"batch={_lastBatch} attempted={_lastAttempted} sent={_lastSent} skipped={_lastSkipped} backfills={_lastBackfilled} " + $"maxPeer={_lastMaxPeerUid} queue={FormatBytes(_lastMaxPeerQueueBefore)}->{FormatBytes(_lastMaxPeerQueueAfter)}"; } private static bool Prefix(object __instance, float __0) { if (!EffectiveConfig.SchedulerEnabled) { return true; } if (!NetReflection.IsServer()) { return true; } if (__instance == null || ZdoReflection.SendZDOsMethod == null || ReflectionCache.ZDOManPeersField == null) { return true; } try { float num = 0f; if (ReflectionCache.ZDOManSendTimerField != null) { num = (float)ReflectionCache.ZDOManSendTimerField.GetValue(__instance); } num += __0; if (num < (_lastInterval = ComputeAdaptiveInterval(__instance))) { ReflectionCache.ZDOManSendTimerField?.SetValue(__instance, num); return false; } ReflectionCache.ZDOManSendTimerField?.SetValue(__instance, 0f); SendToPeerBatch(__instance); OwnershipManager.TickLightweight(__instance); return false; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Adaptive scheduler failed; falling back to vanilla for this tick: " + ex.Message)); return true; } } private static float ComputeAdaptiveInterval(object zdoMan) { int num = 0; int num2 = 0; int num3 = 0; foreach (object item in ZdoReflection.EnumeratePeers(zdoMan)) { num++; int sendQueueSizeForPeer = NetReflection.GetSendQueueSizeForPeer(item); if (sendQueueSizeForPeer > EffectiveConfig.PeerQueueSoftLimitBytes) { num2++; } if (sendQueueSizeForPeer > num3) { num3 = sendQueueSizeForPeer; } } float sendInterval = EffectiveConfig.SendInterval; _lastPeerCount = num; _lastPressuredPeers = num2; _lastMaxQueue = num3; if (num == 0) { return sendInterval; } float num4 = (float)num2 / (float)Math.Max(1, num); if (num4 > 0.5f) { return Math.Min(EffectiveConfig.MaxSendInterval, sendInterval * 1.5f); } if (num4 < 0.1f && num > 4) { return Math.Max(EffectiveConfig.MinSendInterval, sendInterval * 0.75f); } return sendInterval; } private static void SendToPeerBatch(object zdoMan) { if (!(ReflectionCache.ZDOManPeersField.GetValue(zdoMan) is IList list) || list.Count == 0) { return; } int num = 0; if (ReflectionCache.ZDOManNextSendPeerField != null) { try { num = (int)ReflectionCache.ZDOManNextSendPeerField.GetValue(zdoMan); } catch { num = 0; } } if (num < 0 || num >= list.Count) { num = 0; } int num2 = ComputePeerBatchSize(list.Count); int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; double num7 = 0.0; long maxPeerUid = 0L; int maxQueueBefore = 0; int maxQueueAfter = 0; while (num3 < list.Count && num4 < num2) { if (num >= list.Count) { num = 0; } object obj2 = list[num]; num++; num3++; int sendQueueSizeForPeer = NetReflection.GetSendQueueSizeForPeer(obj2); if (ShouldSkipLaggingPeer(obj2, sendQueueSizeForPeer, out var wasBackfill)) { num5++; continue; } if (wasBackfill) { num6++; } PeerQualityMeter.GetByPeer(obj2); NetReflection.TryGetPeerUid(obj2, out var uid); double realtimeSinceStartupAsDouble2 = Time.realtimeSinceStartupAsDouble; ZdoReflection.SendZDOsMethod.Invoke(zdoMan, new object[2] { obj2, false }); num4++; double num8 = Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble2; int sendQueueSizeForPeer2 = NetReflection.GetSendQueueSizeForPeer(obj2); if (num8 > num7) { num7 = num8; maxPeerUid = uid; maxQueueBefore = sendQueueSizeForPeer; maxQueueAfter = sendQueueSizeForPeer2; } } if (ReflectionCache.ZDOManNextSendPeerField != null) { try { ReflectionCache.ZDOManNextSendPeerField.SetValue(zdoMan, num % Math.Max(1, list.Count)); } catch { } } double elapsed = Time.realtimeSinceStartupAsDouble - realtimeSinceStartupAsDouble; if (ModConfig.DebugLogging.Value) { RecordSchedulerDiagnostics(list.Count, num2, num3, num4, num5, num6, elapsed, num7, maxPeerUid, maxQueueBefore, maxQueueAfter); } } private static bool ShouldSkipLaggingPeer(object peer, int queue, out bool wasBackfill) { wasBackfill = false; if (queue <= EffectiveConfig.PeerQueueHardLimitBytes) { if (NetReflection.TryGetPeerUid(peer, out var uid)) { LaggingPeerSkips.Remove(uid); } return false; } if (!NetReflection.TryGetPeerUid(peer, out var uid2) || uid2 == 0L) { return true; } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; float num = Math.Max(0.1f, EffectiveConfig.LaggingPeerMaxSkipSeconds); if (!LaggingPeerSkips.TryGetValue(uid2, out var value)) { value = new LaggingPeerSkipState { FirstSkipTime = realtimeSinceStartupAsDouble }; LaggingPeerSkips[uid2] = value; return true; } value.SkipCount++; if (realtimeSinceStartupAsDouble - value.FirstSkipTime >= (double)num && realtimeSinceStartupAsDouble - value.LastBackfillTime >= (double)num) { value.FirstSkipTime = realtimeSinceStartupAsDouble; value.LastBackfillTime = realtimeSinceStartupAsDouble; value.SkipCount = 0; if (ModConfig.DebugLogging.Value) { Plugin.Log.LogDebug((object)$"ZDO scheduler backfill for lagging peer={uid2}, queue={queue}"); } wasBackfill = true; return false; } return true; } private static void RecordSchedulerDiagnostics(int peers, int batch, int attempted, int sent, int skipped, int backfilled, double elapsed, double maxPeerSeconds, long maxPeerUid, int maxQueueBefore, int maxQueueAfter) { _summaryPasses++; _summaryAttempted += attempted; _summarySent += sent; _summarySkipped += skipped; _summaryBackfills += backfilled; _summaryElapsedSeconds += elapsed; if (elapsed > _summaryMaxPassSeconds) { _summaryMaxPassSeconds = elapsed; } if (maxPeerSeconds > _summaryMaxPeerSeconds) { _summaryMaxPeerSeconds = maxPeerSeconds; _summaryMaxPeerUid = maxPeerUid; _summaryMaxQueueBefore = maxQueueBefore; _summaryMaxQueueAfter = maxQueueAfter; } _lastPassTime = Time.realtimeSinceStartupAsDouble; _lastPassSeconds = elapsed; _lastMaxPeerSeconds = maxPeerSeconds; _lastBatch = batch; _lastAttempted = attempted; _lastSent = sent; _lastSkipped = skipped; _lastBackfilled = backfilled; _lastMaxPeerUid = maxPeerUid; _lastMaxPeerQueueBefore = maxQueueBefore; _lastMaxPeerQueueAfter = maxQueueAfter; double num = 0.008; if (elapsed >= num) { Plugin.Log.LogInfo((object)($"ZDO scheduler slow pass: elapsed={elapsed * 1000.0:F2}ms " + $"peers={peers} batch={batch} attempted={attempted} sent={sent} skipped={skipped} backfills={backfilled} " + $"maxPeer={maxPeerUid} maxPeerMs={maxPeerSeconds * 1000.0:F2} queue={maxQueueBefore}->{maxQueueAfter} " + $"pressure={_lastPressuredPeers}/{_lastPeerCount} maxQueue={_lastMaxQueue} interval={_lastInterval:F3}s")); } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (!(realtimeSinceStartupAsDouble < _nextSummaryTime)) { _nextSummaryTime = realtimeSinceStartupAsDouble + 10.0; if (_summaryPasses > 0) { Plugin.Log.LogInfo((object)($"ZDO scheduler summary {10.0:F0}s: passes={_summaryPasses} attempted={_summaryAttempted} sent={_summarySent} " + $"skipped={_summarySkipped} backfills={_summaryBackfills} " + $"avgPassMs={_summaryElapsedSeconds / (double)Math.Max(1, _summaryPasses) * 1000.0:F2} maxPassMs={_summaryMaxPassSeconds * 1000.0:F2} " + $"maxPeer={_summaryMaxPeerUid} maxPeerMs={_summaryMaxPeerSeconds * 1000.0:F2} queue={_summaryMaxQueueBefore}->{_summaryMaxQueueAfter} " + $"lastPressure={_lastPressuredPeers}/{_lastPeerCount} lastMaxQueue={_lastMaxQueue}")); } _summaryPasses = 0; _summaryAttempted = 0; _summarySent = 0; _summarySkipped = 0; _summaryBackfills = 0; _summaryElapsedSeconds = 0.0; _summaryMaxPassSeconds = 0.0; _summaryMaxPeerSeconds = 0.0; _summaryMaxPeerUid = 0L; _summaryMaxQueueBefore = 0; _summaryMaxQueueAfter = 0; } } private static int ComputePeerBatchSize(int peerCount) { int num = Math.Max(1, EffectiveConfig.BasePeersPerTick); int val = Math.Max(num, EffectiveConfig.MaxPeersPerTick); if (peerCount <= num) { return peerCount; } int val2 = num + Math.Max(0, peerCount - 4) / 4; return Math.Max(1, Math.Min(peerCount, Math.Min(val, val2))); } 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"; } } [HarmonyPatch] internal static class ZDOManSendZDOsQueueLimitTranspiler { [CompilerGenerated] private sealed class d__1 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { private int <>1__state; private CodeInstruction <>2__current; private int <>l__initialThreadId; private IEnumerable instructions; public IEnumerable <>3__instructions; private IEnumerator <>7__wrap1; CodeInstruction IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__1(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || (uint)(num - 1) <= 2u) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Expected O, but got Unknown try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>7__wrap1 = instructions.GetEnumerator(); <>1__state = -3; break; case 1: <>1__state = -3; break; case 2: <>1__state = -3; break; case 3: <>1__state = -3; break; } if (<>7__wrap1.MoveNext()) { CodeInstruction current = <>7__wrap1.Current; if (current.opcode == OpCodes.Ldc_I4 && current.operand is int num) { switch (num) { case 10240: { CodeInstruction val2 = new CodeInstruction(OpCodes.Call, (object)AccessTools.PropertyGetter(typeof(ZDOManSendZDOsQueueLimitTranspiler), "ZdoQueueLimit")); val2.labels.AddRange(current.labels); val2.blocks.AddRange(current.blocks); <>2__current = val2; <>1__state = 1; return true; } case 2048: { CodeInstruction val = new CodeInstruction(OpCodes.Call, (object)AccessTools.PropertyGetter(typeof(ZDOManSendZDOsQueueLimitTranspiler), "ZdoQueueMinPackage")); val.labels.AddRange(current.labels); val.blocks.AddRange(current.blocks); <>2__current = val; <>1__state = 2; return true; } } } else if (current.opcode == OpCodes.Ldc_I4_S) { object operand = current.operand; if (operand is sbyte) { _ = (sbyte)operand; } } <>2__current = current; <>1__state = 3; return true; } <>m__Finally1(); <>7__wrap1 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap1 != null) { <>7__wrap1.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__1 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__1(0); } d__.instructions = <>3__instructions; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } public static int ZdoQueueLimit { get { if (!EffectiveConfig.SchedulerEnabled) { return 10240; } return Math.Max(4096, EffectiveConfig.ZdoQueueLimitBytes); } } public static int ZdoQueueMinPackage { get { if (!EffectiveConfig.SchedulerEnabled) { return 2048; } return Math.Max(512, EffectiveConfig.ZdoQueueMinPackageBytes); } } private static MethodBase TargetMethod() { Type type = ReflectionCache.ZDOManType ?? AccessTools.TypeByName("ZDOMan"); if (type == null) { return null; } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodBase methodBase in methods) { if (methodBase.Name == "SendZDOs" && methodBase.GetParameters().Length == 2) { return methodBase; } } return null; } [IteratorStateMachine(typeof(d__1))] private static IEnumerable Transpiler(IEnumerable instructions) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__1(-2) { <>3__instructions = instructions }; } } internal static class ZPackageTools { internal const int CompressionMagic = 1129204563; internal const int CompressionProtocol = 1; internal const int CompressionAlgoDeflate = 1; private static ConstructorInfo _ctorEmpty; private static ConstructorInfo _ctorBytes; private static MethodInfo _writeInt; private static MethodInfo _writeUInt16; private static MethodInfo _writeByte; private static MethodInfo _writeFloat; private static MethodInfo _writeLong; private static MethodInfo _writeString; private static MethodInfo _writeByteArray; private static MethodInfo _writeVector3; private static MethodInfo _writeZdoId; private static MethodInfo _readInt; private static MethodInfo _readByte; 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); _writeUInt16 = AccessTools.Method(zPackageType, "Write", new Type[1] { typeof(ushort) }, (Type[])null); _writeByte = AccessTools.Method(zPackageType, "Write", new Type[1] { typeof(byte) }, (Type[])null); _writeFloat = AccessTools.Method(zPackageType, "Write", new Type[1] { typeof(float) }, (Type[])null); _writeLong = AccessTools.Method(zPackageType, "Write", new Type[1] { typeof(long) }, (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); _writeVector3 = AccessTools.Method(zPackageType, "Write", new Type[1] { typeof(Vector3) }, (Type[])null); _writeZdoId = ((ReflectionCache.ZDOIDType != null) ? AccessTools.Method(zPackageType, "Write", new Type[1] { ReflectionCache.ZDOIDType }, (Type[])null) : null); _readInt = AccessTools.Method(zPackageType, "ReadInt", (Type[])null, (Type[])null); _readByte = AccessTools.Method(zPackageType, "ReadByte", (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 bool TryAppendPackagePayload(object target, object source) { if (target == null || source == null || _writeByte == null || _loadBytes == null || _getArray == null) { return false; } byte[] array = GetArray(target); int pos = GetPos(target); byte[] array2 = GetArray(source); if (array2 == null || array2.Length == 0) { return false; } try { for (int i = 0; i < array2.Length; i++) { WriteByte(target, array2[i]); } return true; } catch { try { _loadBytes.Invoke(target, new object[1] { array ?? Array.Empty() }); SetPos(target, pos); } catch { } return false; } } 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(); } catch { return Array.Empty(); } } 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 WriteUShort(object pkg, ushort v) { _writeUInt16.Invoke(pkg, new object[1] { v }); } internal static void WriteByte(object pkg, byte v) { _writeByte.Invoke(pkg, new object[1] { v }); } internal static void WriteFloat(object pkg, float v) { _writeFloat.Invoke(pkg, new object[1] { v }); } internal static void WriteLong(object pkg, long v) { _writeLong.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() }); } internal static void WriteVector3(object pkg, Vector3 v) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) _writeVector3.Invoke(pkg, new object[1] { v }); } internal static void WriteZdoId(object pkg, object zdoId) { _writeZdoId.Invoke(pkg, new object[1] { zdoId }); } internal static int ReadInt(object pkg) { return (int)_readInt.Invoke(pkg, null); } internal static byte ReadByte(object pkg) { return (byte)_readByte.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(); } internal static bool TryBuildCompressedPackage(object original, out object compressedPackage) { compressedPackage = null; try { byte[] array = GetArray(original); if (array == null || array.Length < Math.Max(64, EffectiveConfig.CompressionThresholdBytes)) { 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; } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("Compression encode failed: " + ex.Message)); } return false; } } internal static bool TryDecompressPackage(object maybeCompressed, out object rawPackage) { rawPackage = maybeCompressed; if (maybeCompressed == null) { return false; } int pos = GetPos(maybeCompressed); try { SetPos(maybeCompressed, 0); if (ReadInt(maybeCompressed) != 1129204563) { SetPos(maybeCompressed, pos); return false; } int num = ReadInt(maybeCompressed); int num2 = ReadInt(maybeCompressed); int num3 = ReadInt(maybeCompressed); byte[] compressed = ReadByteArray(maybeCompressed); if (num != 1 || num2 != 1 || num3 < 0) { throw new InvalidDataException($"Unsupported SkadiNet compression header protocol={num}, algo={num2}, size={num3}"); } byte[] bytes = Inflate(compressed, num3); rawPackage = NewPackage(bytes); return true; } catch (Exception ex) { SetPos(maybeCompressed, pos); if (ModConfig.DebugLogging.Value) { Plugin.Log.LogWarning((object)("Compression decode failed: " + ex.Message)); } return false; } } 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((expectedSize > 0) ? expectedSize : 0); deflateStream.CopyTo(memoryStream); byte[] array = memoryStream.ToArray(); if (expectedSize > 0 && array.Length != expectedSize) { throw new InvalidDataException($"Inflated size mismatch expected={expectedSize}, actual={array.Length}"); } return array; } } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry : OwnConfigEntryBase { public readonly ConfigEntry SourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public SyncedConfigEntry(ConfigEntry sourceConfig) { SourceConfig = sourceConfig; base..ctor(); } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register(configSync2.Name + " ConfigSync", (Action)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List CurrentList = new List(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List(adminList.GetList()); List adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register(configSync.Name + " ConfigSync", (Action)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary configValues = new Dictionary(); public readonly Dictionary customValues = new Dictionary(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List Package = new List(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary __state, ZNet __instance, ZRpc rpc) { ZRpc rpc2 = rpc; ZNet __instance2 = __instance; Dictionary __state2 = __state; ZNetPeer peer; if (__instance2.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance2, new object[1] { rpc2 }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance2).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc2.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc2, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance2, new object[1] { rpc2 }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state2[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List entries = new List(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc2.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc2.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance2).StartCoroutine(configSync.sendZPackage(new List { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet configSyncs; private readonly HashSet allConfigs = new HashSet(); private HashSet allCustomValues = new HashSet(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary> configValueCache = new Dictionary>(); private readonly List> cacheExpirations = new List>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag.GetValueOrDefault(); } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0053: return (byte)result != 0; IL_0052: result = 0; goto IL_0053; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry AddConfigEntry(ConfigEntry configEntry) { ConfigEntry configEntry2 = configEntry; OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry2); SyncedConfigEntry syncedEntry = ownConfigEntryBase as SyncedConfigEntry; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry(configEntry2); AccessTools.DeclaredField(typeof(ConfigDescription), "k__BackingField").SetValue(((ConfigEntryBase)configEntry2).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry2).Description.Tags ?? Array.Empty()).Concat(new SyncedConfigEntry[1] { syncedEntry }).ToArray()); configEntry2.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)configEntry2); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry AddLockingConfigEntry(ConfigEntry lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { CustomSyncedValueBase customValue2 = customValue; if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue2.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue2); allCustomValues = new HashSet(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue2.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(ZRoutedRpc.Everybody, customValue2); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2u) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary value)) { value = new SortedDictionary(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4u) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int i = 0; i < num; i++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { OwnConfigEntryBase config2 = config; ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config2)); if (configSync == null) { return true; } return configSync.IsSourceOfTruth || !config2.SynchronizedConfig || config2.LocalBaseValue == null || (!configSync.IsLocked && (config2 != configSync.lockedConfig || lockExempt)); } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null)) { if (val == null) { val = item.BaseConfig.ConfigFile; saveOnConfigSet = val.SaveOnConfigSet; val.SaveOnConfigSet = false; } item.BaseConfig.BoxedValue = item.LocalBaseValue; item.LocalBaseValue = null; } if (val != null) { val.SaveOnConfigSet = saveOnConfigSet; } foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator distributeConfigToPeers(ZNetPeer peer, ZPackage package) { ZNetPeer peer2 = peer; ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc == null) { yield break; } byte[] data = package.GetArray(); if (data != null && data.LongLength > 250000) { int fragments = (int)(1 + (data.LongLength - 1) / 250000); long packageIdentifier = ++packageCounter; int fragment = 0; while (fragment < fragments) { foreach (bool item in waitForQueue()) { yield return item; } if (peer2.m_socket.IsConnected()) { ZPackage fragmentedPackage = new ZPackage(); fragmentedPackage.Write((byte)2); fragmentedPackage.Write(packageIdentifier); fragmentedPackage.Write(fragment); fragmentedPackage.Write(fragments); fragmentedPackage.Write(data.Skip(250000 * fragment).Take(250000).ToArray()); SendPackage(fragmentedPackage); if (fragment != fragments - 1) { yield return true; } int num = fragment + 1; fragment = num; continue; } break; } yield break; } foreach (bool item2 in waitForQueue()) { yield return item2; } SendPackage(package); void SendPackage(ZPackage pkg) { string text = Name + " ConfigSync"; if (isServer) { peer2.m_rpc.Invoke(text, new object[1] { pkg }); } else { rpc.InvokeRoutedRPC(peer2.m_server ? 0 : peer2.m_uid, text, new object[1] { pkg }); } } IEnumerable waitForQueue() { float timeout = Time.time + 30f; while (peer2.m_socket.GetSendQueueSize() > 20000) { if (Time.time > timeout) { Debug.Log((object)$"Disconnecting {peer2.m_uid} after 30 seconds config sending timeout"); peer2.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 }); ZNet.instance.Disconnect(peer2); break; } yield return false; } } } private IEnumerator sendZPackage(long target, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { return Enumerable.Empty().GetEnumerator(); } List list = (List)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance); if (target != ZRoutedRpc.Everybody) { list = list.Where((ZNetPeer p) => p.m_uid == target).ToList(); } return sendZPackage(list, package); } private IEnumerator sendZPackage(List peers, ZPackage package) { ZPackage package2 = package; if (!Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } byte[] rawData = package2.GetArray(); if (rawData != null && rawData.LongLength > 10000) { ZPackage compressedPackage = new ZPackage(); compressedPackage.Write((byte)4); MemoryStream output = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(output, CompressionLevel.Optimal)) { deflateStream.Write(rawData, 0, rawData.Length); } compressedPackage.Write(output.ToArray()); package2 = compressedPackage; } List> writers = (from peer in peers where peer.IsReady() select peer into p select distributeConfigToPeers(p, package2)).ToList(); writers.RemoveAll((IEnumerator writer) => !writer.MoveNext()); while (writers.Count > 0) { yield return null; writers.RemoveAll((IEnumerator writer) => !writer.MoveNext()); } } private void Broadcast(long target, params ConfigEntryBase[] configs) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(configs); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private void Broadcast(long target, params CustomSyncedValueBase[] customValues) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(null, customValues); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private static OwnConfigEntryBase? configData(ConfigEntryBase config) { return config.Description.Tags?.OfType().SingleOrDefault(); } public static SyncedConfigEntry? ConfigData(ConfigEntry config) { return ((ConfigEntryBase)config).Description.Tags?.OfType>().SingleOrDefault(); } private static T configAttribute(ConfigEntryBase config) { return config.Description.Tags.OfType().First(); } private static Type configType(ConfigEntryBase config) { return configType(config.SettingType); } private static Type configType(Type type) { return type.IsEnum ? Enum.GetUnderlyingType(type) : type; } private static ZPackage ConfigsToPackage(IEnumerable? configs = null, IEnumerable? customValues = null, IEnumerable? packageEntries = null, bool partial = true) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown List list = configs?.Where((ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List(); List list2 = customValues?.ToList() ?? new List(); ZPackage val = new ZPackage(); val.Write((byte)(partial ? 1 : 0)); val.Write(list.Count + list2.Count + (packageEntries?.Count() ?? 0)); foreach (PackageEntry item in packageEntries ?? Array.Empty()) { AddEntryToPackage(val, item); } foreach (CustomSyncedValueBase item2 in list2) { AddEntryToPackage(val, new PackageEntry { section = "Internal", key = item2.Identifier, type = item2.Type, value = item2.BoxedValue }); } foreach (ConfigEntryBase item3 in list) { AddEntryToPackage(val, new PackageEntry { section = item3.Definition.Section, key = item3.Definition.Key, type = configType(item3), value = item3.BoxedValue }); } return val; } private static void AddEntryToPackage(ZPackage package, PackageEntry entry) { package.Write(entry.section); package.Write(entry.key); package.Write((entry.value == null) ? "" : GetZPackageTypeString(entry.type)); AddValueToZPackage(package, entry.value); } private static string GetZPackageTypeString(Type type) { return type.AssemblyQualifiedName; } private static void AddValueToZPackage(ZPackage package, object? value) { Type type = value?.GetType(); if (value is Enum) { value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture); } else { if (value is ICollection collection) { package.Write(collection.Count); { foreach (object item in collection) { AddValueToZPackage(package, item); } return; } } if ((object)type != null && type.IsValueType && !type.IsPrimitive) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); package.Write(fields.Length); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { package.Write(GetZPackageTypeString(fieldInfo.FieldType)); AddValueToZPackage(package, fieldInfo.GetValue(value)); } return; } } ZRpc.Serialize(new object[1] { value }, ref package); } private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type) { if ((object)type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int num = package.ReadInt(); if (num != fields.Length) { throw new InvalidDeserializationTypeException { received = $"(field count: {num})", expected = $"(field count: {fields.Length})" }; } object uninitializedObject = FormatterServices.GetUninitializedObject(type); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { string text = package.ReadString(); if (text != GetZPackageTypeString(fieldInfo.FieldType)) { throw new InvalidDeserializationTypeException { received = text, expected = GetZPackageTypeString(fieldInfo.FieldType), field = fieldInfo.Name }; } fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType)); } return uninitializedObject; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { int num2 = package.ReadInt(); IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments); FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); for (int j = 0; j < num2; j++) { object obj = ReadValueWithTypeFromZPackage(package, type2); dictionary.Add(field.GetValue(obj), field2.GetValue(obj)); } return dictionary; } if (type != typeof(List) && type.IsGenericType) { Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]); if ((object)type3 != null && type3.IsAssignableFrom(type)) { int num3 = package.ReadInt(); object obj2 = Activator.CreateInstance(type); MethodInfo method = type3.GetMethod("Add"); for (int k = 0; k < num3; k++) { method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) }); } return obj2; } } ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo)); AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type); List source = new List(); ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source); return source.First(); } } [PublicAPI] [HarmonyPatch] internal class VersionCheck { private static readonly HashSet versionChecks; private static readonly Dictionary notProcessedNames; public string Name; private string? displayName; private string? currentVersion; private string? minimumRequiredVersion; public bool ModRequired = true; private string? ReceivedCurrentVersion; private string? ReceivedMinimumRequiredVersion; private readonly List ValidatedClients = new List(); private ConfigSync? ConfigSync; public string DisplayName { get { return displayName ?? Name; } set { displayName = value; } } public string CurrentVersion { get { return currentVersion ?? "0.0.0"; } set { currentVersion = value; } } public string MinimumRequiredVersion { get { return minimumRequiredVersion ?? (ModRequired ? CurrentVersion : "0.0.0"); } set { minimumRequiredVersion = value; } } private static void PatchServerSync() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown Patches patchInfo = PatchProcessor.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null)); if (patchInfo != null && patchInfo.Postfixes.Count((Patch p) => p.PatchMethod.DeclaringType == typeof(ConfigSync.RegisterRPCPatch)) > 0) { return; } Harmony val = new Harmony("org.bepinex.helpers.ServerSync"); foreach (Type item in from t in typeof(ConfigSync).GetNestedTypes(BindingFlags.NonPublic).Concat(new Type[1] { typeof(VersionCheck) }) where t.IsClass select t) { val.PatchAll(item); } } static VersionCheck() { versionChecks = new HashSet(); notProcessedNames = new Dictionary(); typeof(ThreadingHelper).GetMethod("StartSyncInvoke").Invoke(ThreadingHelper.Instance, new object[1] { new Action(PatchServerSync) }); } public VersionCheck(string name) { Name = name; ModRequired = true; versionChecks.Add(this); } public VersionCheck(ConfigSync configSync) { ConfigSync = configSync; Name = ConfigSync.Name; versionChecks.Add(this); } public void Initialize() { ReceivedCurrentVersion = null; ReceivedMinimumRequiredVersion = null; if (ConfigSync != null) { Name = ConfigSync.Name; DisplayName = ConfigSync.DisplayName; CurrentVersion = ConfigSync.CurrentVersion; MinimumRequiredVersion = ConfigSync.MinimumRequiredVersion; ModRequired = ConfigSync.ModRequired; } } private bool IsVersionOk() { if (ReceivedMinimumRequiredVersion == null || ReceivedCurrentVersion == null) { return !ModRequired; } bool flag = new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion); bool flag2 = new Version(ReceivedCurrentVersion) >= new Version(MinimumRequiredVersion); return flag && flag2; } private string ErrorClient() { if (ReceivedMinimumRequiredVersion == null) { return DisplayName + " is not installed on the server."; } return (new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion)) ? (DisplayName + " may not be higher than version " + ReceivedCurrentVersion + ". You have version " + CurrentVersion + ".") : (DisplayName + " needs to be at least version " + ReceivedMinimumRequiredVersion + ". You have version " + CurrentVersion + "."); } private string ErrorServer(ZRpc rpc) { return "Disconnect: The client (" + rpc.GetSocket().GetHostName() + ") doesn't have the correct " + DisplayName + " version " + MinimumRequiredVersion; } private string Error(ZRpc? rpc = null) { return (rpc == null) ? ErrorClient() : ErrorServer(rpc); } private static VersionCheck[] GetFailedClient() { return versionChecks.Where((VersionCheck check) => !check.IsVersionOk()).ToArray(); } private static VersionCheck[] GetFailedServer(ZRpc rpc) { ZRpc rpc2 = rpc; return versionChecks.Where((VersionCheck check) => check.ModRequired && !check.ValidatedClients.Contains(rpc2)).ToArray(); } private static void Logout() { Game.instance.Logout(true, true); AccessTools.DeclaredField(typeof(ZNet), "m_connectionStatus").SetValue(null, (object)(ConnectionStatus)3); } private static void DisconnectClient(ZRpc rpc) { rpc.Invoke("Error", new object[1] { 3 }); } private static void CheckVersion(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, null); } private static void CheckVersion(ZRpc rpc, ZPackage pkg, Action? original) { string text = pkg.ReadString(); string text2 = pkg.ReadString(); string text3 = pkg.ReadString(); bool flag = false; foreach (VersionCheck versionCheck in versionChecks) { if (!(text != versionCheck.Name)) { Debug.Log((object)("Received " + versionCheck.DisplayName + " version " + text3 + " and minimum version " + text2 + " from the " + (ZNet.instance.IsServer() ? "client" : "server") + ".")); versionCheck.ReceivedMinimumRequiredVersion = text2; versionCheck.ReceivedCurrentVersion = text3; if (ZNet.instance.IsServer() && versionCheck.IsVersionOk()) { versionCheck.ValidatedClients.Add(rpc); } flag = true; } } if (flag) { return; } pkg.SetPos(0); if (original != null) { original(rpc, pkg); if (pkg.GetPos() == 0) { notProcessedNames.Add(text, text3); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] [HarmonyPrefix] private static bool RPC_PeerInfo(ZRpc rpc, ZNet __instance) { VersionCheck[] array = (__instance.IsServer() ? GetFailedServer(rpc) : GetFailedClient()); if (array.Length == 0) { return true; } VersionCheck[] array2 = array; foreach (VersionCheck versionCheck in array2) { Debug.LogWarning((object)versionCheck.Error(rpc)); } if (__instance.IsServer()) { DisconnectClient(rpc); } else { Logout(); } return false; } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPrefix] private static void RegisterAndCheckVersion(ZNetPeer peer, ZNet __instance) { //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown notProcessedNames.Clear(); IDictionary dictionary = (IDictionary)typeof(ZRpc).GetField("m_functions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(peer.m_rpc); if (dictionary.Contains(StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck"))) { object obj = dictionary[StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")]; Action action = (Action)obj.GetType().GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj); peer.m_rpc.Register("ServerSync VersionCheck", (Action)delegate(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, action); }); } else { peer.m_rpc.Register("ServerSync VersionCheck", (Action)CheckVersion); } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.Initialize(); if (versionCheck.ModRequired || __instance.IsServer()) { Debug.Log((object)("Sending " + versionCheck.DisplayName + " version " + versionCheck.CurrentVersion + " and minimum version " + versionCheck.MinimumRequiredVersion + " to the " + (__instance.IsServer() ? "client" : "server") + ".")); ZPackage val = new ZPackage(); val.Write(versionCheck.Name); val.Write(versionCheck.MinimumRequiredVersion); val.Write(versionCheck.CurrentVersion); peer.m_rpc.Invoke("ServerSync VersionCheck", new object[1] { val }); } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPrefix] private static void RemoveDisconnected(ZNetPeer peer, ZNet __instance) { if (!__instance.IsServer()) { return; } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.ValidatedClients.Remove(peer.m_rpc); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] [HarmonyPostfix] private static void ShowConnectionError(FejdStartup __instance) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) if (!__instance.m_connectionFailedPanel.activeSelf || (int)ZNet.GetConnectionStatus() != 3) { return; } bool flag = false; VersionCheck[] failedClient = GetFailedClient(); if (failedClient.Length != 0) { string text = string.Join("\n", failedClient.Select((VersionCheck check) => check.Error())); TMP_Text connectionFailedError = __instance.m_connectionFailedError; connectionFailedError.text = connectionFailedError.text + "\n" + text; flag = true; } foreach (KeyValuePair item in notProcessedNames.OrderBy, string>((KeyValuePair kv) => kv.Key)) { if (!__instance.m_connectionFailedError.text.Contains(item.Key)) { TMP_Text connectionFailedError2 = __instance.m_connectionFailedError; connectionFailedError2.text = connectionFailedError2.text + "\nServer expects you to have " + item.Key + " (Version: " + item.Value + ") installed."; flag = true; } } if (flag) { RectTransform component = ((Component)__instance.m_connectionFailedPanel.transform.Find("Image")).GetComponent(); Vector2 sizeDelta = component.sizeDelta; sizeDelta.x = 675f; component.sizeDelta = sizeDelta; __instance.m_connectionFailedError.ForceMeshUpdate(false, false); float num = __instance.m_connectionFailedError.renderedHeight + 105f; RectTransform component2 = ((Component)((Component)component).transform.Find("ButtonOk")).GetComponent(); component2.anchoredPosition = new Vector2(component2.anchoredPosition.x, component2.anchoredPosition.y - (num - component.sizeDelta.y) / 2f); sizeDelta = component.sizeDelta; sizeDelta.y = num; component.sizeDelta = sizeDelta; } } } }