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 <CleanupScheduler>d__42 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <CleanupScheduler>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<ClientCriticalWindow, double> CriticalUntil = new Dictionary<ClientCriticalWindow, double>();
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(<CleanupScheduler>d__42))]
private static IEnumerator CleanupScheduler()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <CleanupScheduler>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<ClientCriticalWindow, double> 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 <TargetMethods>d__0 : IEnumerable<MethodBase>, IEnumerable, IEnumerator<MethodBase>, IDisposable, IEnumerator
{
private int <>1__state;
private MethodBase <>2__current;
private int <>l__initialThreadId;
private MethodInfo[] <>7__wrap1;
private int <>7__wrap2;
MethodBase IEnumerator<MethodBase>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <TargetMethods>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<MethodBase> IEnumerable<MethodBase>.GetEnumerator()
{
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
return this;
}
return new <TargetMethods>d__0(0);
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<MethodBase>)this).GetEnumerator();
}
}
[IteratorStateMachine(typeof(<TargetMethods>d__0))]
private static IEnumerable<MethodBase> TargetMethods()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <TargetMethods>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 <TargetMethods>d__0 : IEnumerable<MethodBase>, IEnumerable, IEnumerator<MethodBase>, IDisposable, IEnumerator
{
private int <>1__state;
private MethodBase <>2__current;
private int <>l__initialThreadId;
private MethodInfo[] <>7__wrap1;
private int <>7__wrap2;
MethodBase IEnumerator<MethodBase>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <TargetMethods>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<MethodBase> IEnumerable<MethodBase>.GetEnumerator()
{
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
return this;
}
return new <TargetMethods>d__0(0);
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<MethodBase>)this).GetEnumerator();
}
}
[IteratorStateMachine(typeof(<TargetMethods>d__0))]
private static IEnumerable<MethodBase> TargetMethods()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <TargetMethods>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<bool> LockServerConfig { get; private set; }
internal static void Initialize(ConfigFile config)
{
//IL_0064: Unknown result type (might be due to invalid IL or missing references)
//IL_006e: Expected O, but got Unknown
Sync = new ConfigSync("sighsorry.SkadiNet")
{
DisplayName = "SkadiNet",
CurrentVersion = "1.0.0",
MinimumRequiredVersion = "1.0.0",
ModRequired = true
};
LockServerConfig = config.Bind<bool>("General", "LockServerConfig", true, new ConfigDescription("Lock server-synced config for non-admin clients.", (AcceptableValueBase)null, new object[1]
{
new ConfigurationManagerAttributes
{
Order = 90
}
}));
Sync.AddLockingConfigEntry<bool>(LockServerConfig).SynchronizedConfig = true;
}
internal static ConfigEntry<T> Bind<T>(ConfigFile config, string group, string name, T value, 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<object>()), scope);
}
internal static ConfigEntry<T> Bind<T>(ConfigFile config, string group, string name, T value, ConfigDescription description, ConfigSyncScope scope)
{
//IL_0023: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Expected O, but got Unknown
string text = SuffixFor(scope);
ConfigDescription val = new ConfigDescription(description.Description + text, description.AcceptableValues, description.Tags);
ConfigEntry<T> val2 = config.Bind<T>(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<int> entry)
{
return Clamp(entry?.Value ?? 0, 0, 100) > 0;
}
private static float Strength(ConfigEntry<int> entry)
{
return (float)Clamp(entry?.Value ?? 50, 0, 100) / 100f;
}
private static float Map(ConfigEntry<int> entry, float safe, float current, float aggressive)
{
float num = Strength(entry);
if (!(num <= 0.5f))
{
return Lerp(current, aggressive, (num - 0.5f) * 2f);
}
return Lerp(safe, current, num * 2f);
}
private static int MapInt(ConfigEntry<int> entry, int safe, int current, int aggressive)
{
return (int)Math.Round(Map(entry, safe, current, aggressive));
}
private static float Clamp(float value, float min, float max)
{
if (float.IsNaN(value) || float.IsInfinity(value))
{
return min;
}
return Math.Max(min, Math.Min(max, value));
}
private static int Clamp(int value, int min, int max)
{
return Math.Max(min, Math.Min(max, value));
}
private static float Lerp(float a, float b, float t)
{
return a + (b - a) * Math.Max(0f, Math.Min(1f, t));
}
}
[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<object, PeerFeatureState> ByRpc = new Dictionary<object, PeerFeatureState>();
private static readonly Dictionary<object, PeerFeatureState> BySocket = new Dictionary<object, PeerFeatureState>();
private static readonly Dictionary<long, PeerFeatureState> ByUid = new Dictionary<long, PeerFeatureState>();
internal static PeerFeatureFlags LocalCapabilities => PeerFeatureFlags.Compression | PeerFeatureFlags.RpcAoi;
internal static void Initialize()
{
lock (Lock)
{
ByRpc.Clear();
BySocket.Clear();
ByUid.Clear();
}
}
internal static PeerFeatureState GetOrCreateByRpc(object rpc, long uid = 0L)
{
if (rpc == null)
{
return null;
}
lock (Lock)
{
if (!ByRpc.TryGetValue(rpc, out var value))
{
value = new PeerFeatureState
{
Rpc = rpc,
Uid = uid
};
ByRpc[rpc] = value;
}
if (uid != 0L)
{
value.Uid = uid;
ByUid[uid] = value;
}
object socketFromRpc = NetReflection.GetSocketFromRpc(rpc);
if (socketFromRpc != null)
{
value.Socket = socketFromRpc;
BySocket[socketFromRpc] = value;
}
return value;
}
}
internal static PeerFeatureState GetBySocket(object socket)
{
if (socket == null)
{
return null;
}
lock (Lock)
{
BySocket.TryGetValue(socket, out var value);
return value;
}
}
internal static PeerFeatureState GetByUid(long uid)
{
lock (Lock)
{
ByUid.TryGetValue(uid, out var value);
return value;
}
}
internal static void ClearPeer(object peerOrRpc, long uid)
{
object obj = NetReflection.GetPeerRpc(peerOrRpc);
if (obj == null && peerOrRpc != null && ReflectionCache.ZRpcType != null && ReflectionCache.ZRpcType.IsInstanceOfType(peerOrRpc))
{
obj = peerOrRpc;
}
object socketFromRpc = NetReflection.GetSocketFromRpc(obj);
lock (Lock)
{
PeerFeatureState value = null;
if (uid != 0L)
{
ByUid.TryGetValue(uid, out value);
}
if (value == null && obj != null)
{
ByRpc.TryGetValue(obj, out value);
}
if (value == null && socketFromRpc != null)
{
BySocket.TryGetValue(socketFromRpc, out value);
}
if (uid != 0L)
{
ByUid.Remove(uid);
}
if (obj != null)
{
ByRpc.Remove(obj);
}
if (socketFromRpc != null)
{
BySocket.Remove(socketFromRpc);
}
if (value != null)
{
if (value.Uid != 0L)
{
ByUid.Remove(value.Uid);
}
if (value.Rpc != null)
{
ByRpc.Remove(value.Rpc);
}
if (value.Socket != null)
{
BySocket.Remove(value.Socket);
}
RemoveState(ByRpc, value);
RemoveState(BySocket, value);
RemoveState(ByUid, value);
}
}
}
private static void RemoveState<TKey>(Dictionary<TKey, PeerFeatureState> map, PeerFeatureState state)
{
if (state == null || map.Count == 0)
{
return;
}
List<TKey> list = new List<TKey>();
foreach (KeyValuePair<TKey, PeerFeatureState> item in map)
{
if (item.Value == state)
{
list.Add(item.Key);
}
}
foreach (TKey item2 in list)
{
map.Remove(item2);
}
}
internal static bool IsCompressionActiveForSocket(object socket)
{
if (!EffectiveConfig.CompressionEnabled)
{
return false;
}
PeerFeatureState bySocket = GetBySocket(socket);
if (bySocket == null)
{
return false;
}
if (bySocket.CompressionFailures >= 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, TPkg>(TRpc rpc, TPkg pkg)
{
RPC_Features(rpc, pkg);
}
private static void RPC_Features(object rpc, object pkg)
{
try
{
PeerFeatureState orCreateByRpc = GetOrCreateByRpc(rpc, 0L);
if (orCreateByRpc == null || pkg == null)
{
return;
}
int pos = ZPackageTools.GetPos(pkg);
ZPackageTools.SetPos(pkg, 0);
if (ZPackageTools.ReadInt(pkg) != 1179536211)
{
ZPackageTools.SetPos(pkg, pos);
return;
}
int num = ZPackageTools.ReadInt(pkg);
int remoteCapabilities = ZPackageTools.ReadInt(pkg);
string text = ZPackageTools.ReadString(pkg);
orCreateByRpc.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<int> 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<bool> Enabled;
internal static ConfigEntry<bool> LockServerConfig;
internal static ConfigEntry<bool> DebugLogging;
internal static ConfigEntry<int> SchedulerThroughput;
internal static ConfigEntry<int> PayloadReducerStrength;
internal static ConfigEntry<int> OwnershipIntensity;
internal static ConfigEntry<int> CompressionAggression;
internal static ConfigEntry<int> RpcAoiAggression;
internal static ConfigEntry<int> ClientStutterGuardStrength;
internal static void Bind(ConfigFile config)
{
Enabled = ConfigSyncManager.Bind(config, "General", "Enabled", value: true, Description("Master switch.", 100), ConfigSyncScope.ServerSynced);
LockServerConfig = ConfigSyncManager.LockServerConfig;
DebugLogging = ConfigSyncManager.Bind(config, "General", "DebugLogging", value: false, Description("Diagnostic log output. Enable temporarily to inspect 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<int>(0, 100), new object[1]
{
new ConfigurationManagerAttributes
{
Order = order
}
});
}
}
internal static class NetReflection
{
[CompilerGenerated]
private sealed class <EnumerateZNetPeers>d__24 : IEnumerable<object>, IEnumerable, IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
private int <>l__initialThreadId;
private IEnumerator <>7__wrap1;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <EnumerateZNetPeers>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<object> IEnumerable<object>.GetEnumerator()
{
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
return this;
}
return new <EnumerateZNetPeers>d__24(0);
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<object>)this).GetEnumerator();
}
}
private static Func<object, object> _peerRpcGetter;
private static Func<object, object> _peerUidGetter;
private static Func<object, Vector3> _peerRefPosGetter;
private static Func<object, object> _rpcSocketGetter;
private static Func<object, object> _socketSendQueueSizeGetter;
internal static Type ZNetType => ReflectionCache.ZNetType;
internal static Type ZNetPeerType => ReflectionCache.ZNetPeerType;
internal static Type ZRpcType => ReflectionCache.ZRpcType;
internal static Type ZPackageType => ReflectionCache.ZPackageType;
internal static object ZNetInstance => ReflectionCache.ZNetInstanceField?.GetValue(null);
internal static void Initialize()
{
_peerRpcGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.PeerRpcField);
_peerUidGetter = ReflectionDelegateFactory.BoxedFieldGetter(ReflectionCache.PeerUidField);
_peerRefPosGetter = ReflectionDelegateFactory.Vector3FieldGetter(ReflectionCache.PeerRefPosField);
_rpcSocketGetter = ReflectionDelegateFactory.BoxedInstanceMethod(ReflectionCache.ZRpcGetSocketMethod);
_socketSendQueueSizeGetter = ReflectionDelegateFactory.BoxedInstanceMethod(ReflectionCache.ZSteamSocketGetSendQueueSizeMethod);
}
internal static bool IsServer()
{
try
{
object zNetInstance = ZNetInstance;
if (zNetInstance == null || ReflectionCache.ZNetIsServerMethod == null)
{
return false;
}
return (bool)ReflectionCache.ZNetIsServerMethod.Invoke(zNetInstance, null);
}
catch
{
return false;
}
}
internal static bool IsDedicatedServer()
{
try
{
object zNetInstance = ZNetInstance;
if (zNetInstance == null || ReflectionCache.ZNetIsDedicatedMethod == null)
{
return false;
}
return (bool)ReflectionCache.ZNetIsDedicatedMethod.Invoke(zNetInstance, null);
}
catch
{
return false;
}
}
internal static bool TryGetPeerUid(object peerOrRpc, out long uid)
{
uid = 0L;
if (peerOrRpc == null)
{
return false;
}
try
{
if (ReflectionCache.TryConvertToLong(TryGet(_peerUidGetter, peerOrRpc), out uid))
{
return true;
}
if (ReflectionCache.TryConvertToLong(ReflectionCache.CachedField(peerOrRpc.GetType(), "m_uid")?.GetValue(peerOrRpc), out uid))
{
return true;
}
return TryGetUidFromPeerObject(GetPeerRpc(peerOrRpc), out uid);
}
catch
{
return false;
}
}
internal static bool TryGetUidFromPeerObject(object peerOrRpc, out long uid)
{
uid = 0L;
if (peerOrRpc == null)
{
return false;
}
try
{
return ReflectionCache.TryConvertToLong(ReflectionCache.CachedField(peerOrRpc.GetType(), "m_uid")?.GetValue(peerOrRpc), out uid);
}
catch
{
return false;
}
}
internal static object GetPeerRpc(object peer)
{
if (peer == null)
{
return null;
}
try
{
object obj = ReflectionCache.CachedField(peer.GetType(), "m_rpc")?.GetValue(peer);
if (obj != null)
{
return obj;
}
object obj2 = TryGet(_peerRpcGetter, peer);
if (obj2 == null)
{
return null;
}
return ReflectionCache.CachedField(obj2.GetType(), "m_rpc")?.GetValue(obj2) ?? obj2;
}
catch
{
return null;
}
}
internal static object GetSocketFromRpc(object rpc)
{
if (rpc == null)
{
return null;
}
try
{
return TryGet(_rpcSocketGetter, rpc) ?? ReflectionCache.ZRpcGetSocketMethod?.Invoke(rpc, null);
}
catch
{
return null;
}
}
internal static Vector3 GetPeerRefPos(object peer)
{
//IL_0003: Unknown result type (might be due to invalid IL or missing references)
//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0051: Unknown result type (might be due to invalid IL or missing references)
//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
if (peer == null)
{
return Vector3.zero;
}
try
{
if (TryGetVector3(_peerRefPosGetter, peer, out var value))
{
return value;
}
if (ReflectionCache.CachedField(peer.GetType(), "m_refPos")?.GetValue(peer) is Vector3 result)
{
return result;
}
object obj = ReflectionCache.CachedField(peer.GetType(), "m_peer")?.GetValue(peer);
if (obj != null && obj != peer)
{
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(<EnumerateZNetPeers>d__24))]
internal static IEnumerable<object> EnumerateZNetPeers()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <EnumerateZNetPeers>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<object, object> getter, object instance)
{
try
{
return (getter != null && instance != null) ? getter(instance) : null;
}
catch
{
return null;
}
}
private static bool TryGetVector3(Func<object, Vector3> 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<ZdoIdKey, OwnerState> ByZdoId = new Dictionary<ZdoIdKey, OwnerState>();
private static readonly Dictionary<long, int> OwnerLoadEstimate = new Dictionary<long, int>();
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<ZdoIdKey> list = new List<ZdoIdKey>();
foreach (KeyValuePair<ZdoIdKey, OwnerState> item in ByZdoId)
{
OwnerState value = item.Value;
if (value == null || (value.LastTouchedTime > 0.0 && now - value.LastTouchedTime >= 600.0))
{
list.Add(item.Key);
}
}
foreach (ZdoIdKey item2 in list)
{
ByZdoId.Remove(item2);
}
while (ByZdoId.Count > 50000 && RemoveOldestOwnerState())
{
}
}
private static bool RemoveOldestOwnerState()
{
ZdoIdKey key = default(ZdoIdKey);
double num = double.MaxValue;
bool flag = false;
foreach (KeyValuePair<ZdoIdKey, OwnerState> 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<PayloadCullKey>
{
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<PayloadCullKey, double> LastAllowedByZdoKey = new Dictionary<PayloadCullKey, double>();
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<PayloadCullKey> list = new List<PayloadCullKey>();
foreach (KeyValuePair<PayloadCullKey, double> 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<PayloadCullKey, double> 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)
i