Decompiled source of BeanheimNet v0.2.2

BeanheimNet.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BeanheimNet.Features;
using BeanheimNet.Telemetry;
using BeanheimNet.Util;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
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("BeanheimNet")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("BeanheimNet")]
[assembly: AssemblyTitle("BeanheimNet")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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]
	[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;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BeanheimNet
{
	internal readonly struct PeerSnap
	{
		public readonly bool Found;

		public readonly uint DataRevision;

		public readonly ushort OwnerRevision;

		public readonly float SyncTime;

		public PeerSnap(uint dataRevision, ushort ownerRevision, float syncTime)
		{
			Found = true;
			DataRevision = dataRevision;
			OwnerRevision = ownerRevision;
			SyncTime = syncTime;
		}
	}
	internal static class Bindings
	{
		public static bool ZdoManOk;

		public static bool PeerInfoOk;

		public static bool SyncTransformOk;

		public static bool RpcOk;

		public static bool SteamSocketOk;

		public static string ZdoManReason = "";

		public static string PeerInfoReason = "";

		public static string SyncTransformReason = "";

		public static string RpcReason = "";

		public static string SteamSocketReason = "";

		public static Type ZdoPeerType = null;

		public static Type PeerInfoType = null;

		public static MethodInfo SendZDOsMi = null;

		public static MethodInfo SendZDOToPeers2Mi = null;

		public static MethodInfo CreateSyncListMi = null;

		public static MethodInfo ServerSortMi = null;

		public static FieldRef<object, ZNetPeer> PeerNetPeer = null;

		public static FieldRef<object, HashSet<ZDOID>> PeerForceSend = null;

		public static FieldRef<object, IList> Peers = null;

		public static FieldRef<ZDOMan, List<ZDO>> TempToSync = null;

		public static FieldRef<ZDOMan, int> ZdosSent = null;

		public static FieldRef<ZDOMan, float> SendTimer = null;

		public static FieldRef<ZDOMan, int> NextSendPeer = null;

		public static FieldRef<ZDOMan, Dictionary<ZDOID, ZDO>> ObjectsById = null;

		public static Func<ZDOMan, object, bool, bool> SendZDOs = null;

		public static Func<object, ZDOID, PeerSnap> PeerInfo = null;

		public static MethodInfo DeserializeMi = null;

		public static MethodInfo? IncreaseDataRevisionMi;

		public static MethodInfo OwnerSyncMi = null;

		public static FieldRef<ZSyncTransform, Vector3> StPositionCached = null;

		public static FieldRef<ZSyncTransform, Vector3> StVelocityCached = null;

		public static FieldRef<ZSyncTransform, Vector3> StLastScale = null;

		public static FieldRef<ZSyncTransform, bool> StWasOwner = null;

		public static FieldRef<ZSyncTransform, ZNetView> StNview = null;

		public static FieldRef<ZSyncTransform, Rigidbody> StBody = null;

		public static FieldRef<ZSyncTransform, ZDOID>? StParentCached;

		public static Func<ZSyncTransform, Vector3> StGetPosition = null;

		public static Func<ZSyncTransform, Vector3> StGetVelocity = null;

		public static MethodInfo UpdatePingMi = null;

		public static MethodInfo ReceivePingMi = null;

		public static FieldRef<ZRpc, int> RpcSentData = null;

		public static FieldRef<ZRpc, int> RpcRecvData = null;

		public static FieldRef<ZRpc, float> RpcPingTimer = null;

		public static float PingInterval = 1f;

		public static MethodInfo RegisterGlobalCallbacksMi = null;

		public static FieldRef<ZSteamSocket, HSteamNetConnection> SteamCon = null;

		public static FieldRef<ZSteamSocket, Queue<byte[]>> SteamSendQueue = null;

		public static MethodInfo? InitTerminalMi;

		public static bool ShipOk;

		public static string ShipReason = "";

		public static Func<Ship, bool> ShipHasPlayerOnboard = null;

		public static bool ItemDropOk;

		public static string ItemDropReason = "";

		public static MethodInfo SlowUpdateMi = null;

		public static FieldRef<ItemDrop, ZNetView> ItemNview = null;

		public static Action<ItemDrop> ItemSave = null;

		public static readonly List<MethodBase> PatchedMethods = new List<MethodBase>();

		public static void Init(ManualLogSource log)
		{
			ZdoManOk = Try(BindZdoMan, out ZdoManReason);
			PeerInfoOk = ZdoManOk && Try(BindPeerInfo, out PeerInfoReason);
			SyncTransformOk = Try(BindSyncTransform, out SyncTransformReason);
			RpcOk = Try(BindRpc, out RpcReason);
			SteamSocketOk = Try(BindSteamSocket, out SteamSocketReason);
			InitTerminalMi = AccessTools.Method(typeof(Terminal), "InitTerminal", (Type[])null, (Type[])null);
			ItemDropOk = Try(BindItemDrop, out ItemDropReason);
			ShipOk = Try(BindShip, out ShipReason);
			Report(log, "ZDOMan", ZdoManOk, ZdoManReason);
			Report(log, "ZDOMan.ZDOPeer.PeerZDOInfo", PeerInfoOk, PeerInfoReason);
			Report(log, "ZSyncTransform", SyncTransformOk, SyncTransformReason);
			Report(log, "ZRpc", RpcOk, RpcReason);
			Report(log, "ZSteamSocket", SteamSocketOk, SteamSocketReason);
			Report(log, "ItemDrop", ItemDropOk, ItemDropReason);
			Report(log, "Ship", ShipOk, ShipReason);
		}

		private static void Report(ManualLogSource log, string group, bool ok, string reason)
		{
			if (ok)
			{
				log.LogInfo((object)("[bind] " + group + ": ok"));
				return;
			}
			log.LogError((object)("[bind] " + group + ": FAILED - " + reason + ". Features needing it are skipped; the game changed shape (AUTO_UPDATE?) or another mod rewrote it."));
		}

		private static bool Try(Action bind, out string reason)
		{
			try
			{
				bind();
				reason = "";
				return true;
			}
			catch (Exception ex)
			{
				reason = ex.GetType().Name + ": " + ex.Message;
				return false;
			}
		}

		private static T Need<T>(T? value, string what) where T : class
		{
			if (value == null)
			{
				throw new MissingMemberException(what + " not found");
			}
			return value;
		}

		private static void BindZdoMan()
		{
			ZdoPeerType = Need(AccessTools.Inner(typeof(ZDOMan), "ZDOPeer"), "ZDOMan.ZDOPeer");
			SendZDOsMi = Need(AccessTools.Method(typeof(ZDOMan), "SendZDOs", new Type[2]
			{
				ZdoPeerType,
				typeof(bool)
			}, (Type[])null), "ZDOMan.SendZDOs(ZDOPeer,bool)");
			SendZDOToPeers2Mi = Need(AccessTools.Method(typeof(ZDOMan), "SendZDOToPeers2", new Type[1] { typeof(float) }, (Type[])null), "ZDOMan.SendZDOToPeers2(float)");
			CreateSyncListMi = Need(AccessTools.Method(typeof(ZDOMan), "CreateSyncList", new Type[2]
			{
				ZdoPeerType,
				typeof(List<ZDO>)
			}, (Type[])null), "ZDOMan.CreateSyncList(ZDOPeer,List<ZDO>)");
			ServerSortMi = Need(AccessTools.Method(typeof(ZDOMan), "ServerSortSendZDOS", new Type[3]
			{
				typeof(List<ZDO>),
				typeof(Vector3),
				ZdoPeerType
			}, (Type[])null), "ZDOMan.ServerSortSendZDOS");
			DeserializeMi = Need(AccessTools.Method(typeof(ZDO), "Deserialize", new Type[1] { typeof(ZPackage) }, (Type[])null), "ZDO.Deserialize(ZPackage)");
			IncreaseDataRevisionMi = AccessTools.Method(typeof(ZDO), "IncreaseDataRevision", (Type[])null, (Type[])null);
			PeerNetPeer = AccessTools.FieldRefAccess<ZNetPeer>(ZdoPeerType, "m_peer");
			PeerForceSend = AccessTools.FieldRefAccess<HashSet<ZDOID>>(ZdoPeerType, "m_forceSend");
			Peers = AccessTools.FieldRefAccess<IList>(typeof(ZDOMan), "m_peers");
			TempToSync = AccessTools.FieldRefAccess<ZDOMan, List<ZDO>>("m_tempToSync");
			ZdosSent = AccessTools.FieldRefAccess<ZDOMan, int>("m_zdosSent");
			SendTimer = AccessTools.FieldRefAccess<ZDOMan, float>("m_sendTimer");
			NextSendPeer = AccessTools.FieldRefAccess<ZDOMan, int>("m_nextSendPeer");
			ObjectsById = AccessTools.FieldRefAccess<ZDOMan, Dictionary<ZDOID, ZDO>>("m_objectsByID");
			ParameterExpression parameterExpression = Expression.Parameter(typeof(ZDOMan), "man");
			ParameterExpression parameterExpression2 = Expression.Parameter(typeof(object), "peer");
			ParameterExpression parameterExpression3 = Expression.Parameter(typeof(bool), "flush");
			SendZDOs = Expression.Lambda<Func<ZDOMan, object, bool, bool>>(Expression.Call(parameterExpression, SendZDOsMi, Expression.Convert(parameterExpression2, ZdoPeerType), parameterExpression3), new ParameterExpression[3] { parameterExpression, parameterExpression2, parameterExpression3 }).Compile();
		}

		private static void BindPeerInfo()
		{
			PeerInfoType = Need(AccessTools.Inner(ZdoPeerType, "PeerZDOInfo"), "ZDOMan.ZDOPeer.PeerZDOInfo");
			FieldInfo fieldInfo = Need(AccessTools.Field(ZdoPeerType, "m_zdos"), "ZDOPeer.m_zdos");
			MethodInfo method = Need(fieldInfo.FieldType.GetMethod("TryGetValue"), "Dictionary.TryGetValue");
			FieldInfo fieldInfo2 = Need(AccessTools.Field(PeerInfoType, "m_dataRevision"), "PeerZDOInfo.m_dataRevision");
			FieldInfo fieldInfo3 = Need(AccessTools.Field(PeerInfoType, "m_ownerRevision"), "PeerZDOInfo.m_ownerRevision");
			FieldInfo fieldInfo4 = Need(AccessTools.Field(PeerInfoType, "m_syncTime"), "PeerZDOInfo.m_syncTime");
			if (fieldInfo2.FieldType != typeof(uint) || fieldInfo3.FieldType != typeof(ushort) || fieldInfo4.FieldType != typeof(float))
			{
				throw new MissingMemberException("PeerZDOInfo field types changed");
			}
			ConstructorInfo? constructor = typeof(PeerSnap).GetConstructor(new Type[3]
			{
				typeof(uint),
				typeof(ushort),
				typeof(float)
			});
			ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "peer");
			ParameterExpression parameterExpression2 = Expression.Parameter(typeof(ZDOID), "id");
			ParameterExpression parameterExpression3 = Expression.Variable(PeerInfoType, "info");
			Expression test = Expression.Call(Expression.Field(Expression.Convert(parameterExpression, ZdoPeerType), fieldInfo), method, parameterExpression2, parameterExpression3);
			Expression ifTrue = Expression.New(constructor, Expression.Field(parameterExpression3, fieldInfo2), Expression.Field(parameterExpression3, fieldInfo3), Expression.Field(parameterExpression3, fieldInfo4));
			PeerInfo = Expression.Lambda<Func<object, ZDOID, PeerSnap>>(Expression.Block(new ParameterExpression[1] { parameterExpression3 }, Expression.Condition(test, ifTrue, Expression.Default(typeof(PeerSnap)))), new ParameterExpression[2] { parameterExpression, parameterExpression2 }).Compile();
		}

		private static void BindSyncTransform()
		{
			OwnerSyncMi = Need(AccessTools.Method(typeof(ZSyncTransform), "OwnerSync", (Type[])null, (Type[])null), "ZSyncTransform.OwnerSync");
			StPositionCached = AccessTools.FieldRefAccess<ZSyncTransform, Vector3>("m_positionCached");
			StVelocityCached = AccessTools.FieldRefAccess<ZSyncTransform, Vector3>("m_velocityCached");
			StLastScale = AccessTools.FieldRefAccess<ZSyncTransform, Vector3>("m_lastLocalScale");
			StWasOwner = AccessTools.FieldRefAccess<ZSyncTransform, bool>("m_wasOwner");
			StNview = AccessTools.FieldRefAccess<ZSyncTransform, ZNetView>("m_nview");
			StBody = AccessTools.FieldRefAccess<ZSyncTransform, Rigidbody>("m_body");
			try
			{
				StParentCached = AccessTools.FieldRefAccess<ZSyncTransform, ZDOID>("m_tempParentCached");
			}
			catch
			{
				StParentCached = null;
			}
			MethodInfo method = Need(AccessTools.Method(typeof(ZSyncTransform), "GetPosition", (Type[])null, (Type[])null), "ZSyncTransform.GetPosition");
			MethodInfo method2 = Need(AccessTools.Method(typeof(ZSyncTransform), "GetVelocity", (Type[])null, (Type[])null), "ZSyncTransform.GetVelocity");
			ParameterExpression parameterExpression = Expression.Parameter(typeof(ZSyncTransform), "st");
			StGetPosition = Expression.Lambda<Func<ZSyncTransform, Vector3>>(Expression.Call(parameterExpression, method), new ParameterExpression[1] { parameterExpression }).Compile();
			StGetVelocity = Expression.Lambda<Func<ZSyncTransform, Vector3>>(Expression.Call(parameterExpression, method2), new ParameterExpression[1] { parameterExpression }).Compile();
		}

		private static void BindRpc()
		{
			UpdatePingMi = Need(AccessTools.Method(typeof(ZRpc), "UpdatePing", new Type[1] { typeof(float) }, (Type[])null), "ZRpc.UpdatePing(float)");
			ReceivePingMi = Need(AccessTools.Method(typeof(ZRpc), "ReceivePing", new Type[1] { typeof(ZPackage) }, (Type[])null), "ZRpc.ReceivePing(ZPackage)");
			RpcSentData = AccessTools.FieldRefAccess<ZRpc, int>("m_sentData");
			RpcRecvData = AccessTools.FieldRefAccess<ZRpc, int>("m_recvData");
			RpcPingTimer = AccessTools.FieldRefAccess<ZRpc, float>("m_pingTimer");
			PingInterval = (float)Need(AccessTools.Field(typeof(ZRpc), "m_pingInterval"), "ZRpc.m_pingInterval").GetValue(null);
		}

		private static void BindItemDrop()
		{
			SlowUpdateMi = Need(AccessTools.Method(typeof(ItemDrop), "SlowUpdate", (Type[])null, (Type[])null), "ItemDrop.SlowUpdate");
			ItemNview = AccessTools.FieldRefAccess<ItemDrop, ZNetView>("m_nview");
			MethodInfo method = Need(AccessTools.Method(typeof(ItemDrop), "Save", Type.EmptyTypes, (Type[])null), "ItemDrop.Save()");
			ParameterExpression parameterExpression = Expression.Parameter(typeof(ItemDrop), "item");
			ItemSave = Expression.Lambda<Action<ItemDrop>>(Expression.Call(parameterExpression, method), new ParameterExpression[1] { parameterExpression }).Compile();
		}

		private static void BindShip()
		{
			MethodInfo methodInfo = Need(AccessTools.Method(typeof(Ship), "HasPlayerOnboard", Type.EmptyTypes, (Type[])null), "Ship.HasPlayerOnboard()");
			if (methodInfo.ReturnType != typeof(bool))
			{
				throw new MissingMemberException("Ship.HasPlayerOnboard() no longer returns bool");
			}
			ParameterExpression parameterExpression = Expression.Parameter(typeof(Ship), "ship");
			ShipHasPlayerOnboard = Expression.Lambda<Func<Ship, bool>>(Expression.Call(parameterExpression, methodInfo), new ParameterExpression[1] { parameterExpression }).Compile();
		}

		private static void BindSteamSocket()
		{
			RegisterGlobalCallbacksMi = Need(AccessTools.Method(typeof(ZSteamSocket), "RegisterGlobalCallbacks", (Type[])null, (Type[])null), "ZSteamSocket.RegisterGlobalCallbacks");
			SteamCon = AccessTools.FieldRefAccess<ZSteamSocket, HSteamNetConnection>("m_con");
			SteamSendQueue = AccessTools.FieldRefAccess<ZSteamSocket, Queue<byte[]>>("m_sendQueue");
		}

		public static int UnsentBytes(ISocket socket)
		{
			if (!SteamSocketOk)
			{
				return 0;
			}
			ZSteamSocket val = Env.Unwrap(socket);
			if (val == null)
			{
				return 0;
			}
			Queue<byte[]> queue = SteamSendQueue.Invoke(val);
			if (queue == null || queue.Count == 0)
			{
				return 0;
			}
			int num = 0;
			foreach (byte[] item in queue)
			{
				num += item.Length;
			}
			return num;
		}
	}
	internal static class Cfg
	{
		private static ConfigFile _cfg = null;

		public static ConfigEntry<bool> Enabled = null;

		public static ConfigEntry<float> ConfigReloadSeconds = null;

		public static ConfigEntry<bool> F1Enabled = null;

		public static ConfigEntry<int> TargetRateKBps = null;

		public static ConfigEntry<float> BdpFactor = null;

		public static ConfigEntry<int> MaxWindowBytes = null;

		public static ConfigEntry<int> MinWindowBytes = null;

		public static ConfigEntry<int> HeadroomBytes = null;

		public static ConfigEntry<float> DrainIntervalSeconds = null;

		public static ConfigEntry<int> DrainFloorBytes = null;

		public static ConfigEntry<bool> RaiseJotunnQueueLimit = null;

		public static ConfigEntry<bool> F2Enabled = null;

		public static ConfigEntry<float> SendIntervalSeconds = null;

		public static ConfigEntry<float> FrameBudgetMs = null;

		public static ConfigEntry<int> ServerTargetFrameRate = null;

		public static ConfigEntry<bool> F3Enabled = null;

		public static ConfigEntry<int> SendRateKBps = null;

		public static ConfigEntry<int> SendBufferBytes = null;

		public static ConfigEntry<bool> F4Enabled = null;

		public static ConfigEntry<bool> F4ClientUplink = null;

		public static ConfigEntry<float> NearMeters = null;

		public static ConfigEntry<float> FarMeters = null;

		public static ConfigEntry<string> FishHz = null;

		public static ConfigEntry<string> CreatureHz = null;

		public static ConfigEntry<string> ItemHz = null;

		public static ConfigEntry<string> OtherHz = null;

		public static ConfigEntry<string> FloatingHz = null;

		public static ConfigEntry<string> EffectHz = null;

		public static ConfigEntry<bool> F5Enabled = null;

		public static ConfigEntry<bool> LiveRefPos = null;

		public static ConfigEntry<float> AgeWeight = null;

		public static ConfigEntry<float> SolidBiasMeters = null;

		public static ConfigEntry<float> NewNearBonusMeters = null;

		public static ConfigEntry<bool> F6Enabled = null;

		public static ConfigEntry<float> ItemPosMeters = null;

		public static ConfigEntry<float> ItemRotDegrees = null;

		public static ConfigEntry<float> CreaturePosMeters = null;

		public static ConfigEntry<float> CreatureRotDegrees = null;

		public static ConfigEntry<float> OtherPosMeters = null;

		public static ConfigEntry<float> OtherRotDegrees = null;

		public static ConfigEntry<float> VelocityMps = null;

		public static ConfigEntry<float> MooredShipPosMeters = null;

		public static ConfigEntry<float> MooredShipRotDegrees = null;

		public static ConfigEntry<bool> TelemetryEnabled = null;

		public static ConfigEntry<float> IntervalSeconds = null;

		public static ConfigEntry<bool> PerPrefab = null;

		public static ConfigEntry<int> TopN = null;

		public static ConfigEntry<bool> StatsFile = null;

		public static ConfigEntry<int> StatsLogMaxMB = null;

		public static ConfigEntry<bool> CensusOnBoot = null;

		public static ConfigEntry<bool> OwnerWriteStats = null;

		public static ConfigEntry<bool> ItemReport = null;

		public static ConfigEntry<float> SweepMinutes = null;

		public static ConfigEntry<bool> ItemCleanup = null;

		public static ConfigEntry<float> MaxAgeHours = null;

		public static ConfigEntry<float> ProtectRadiusMeters = null;

		public static ConfigEntry<bool> OnlyInWater = null;

		public static ConfigEntry<string> ProtectedPatterns = null;

		public static ConfigEntry<string> BaseProxyPrefabs = null;

		public static ConfigEntry<float> BaseProxyRadiusMeters = null;

		public static ConfigEntry<int> MaxDeletionsPerSweep = null;

		public static ConfigEntry<bool> F9Enabled = null;

		public static ConfigEntry<float> MergeRadiusMeters = null;

		public static ConfigEntry<float> MergeIntervalSeconds = null;

		public static ConfigEntry<bool> TriggerCensus = null;

		public static ConfigEntry<bool> TriggerItemReport = null;

		public static ConfigEntry<bool> TriggerItemCleanupOnce = null;

		public static ConfigEntry<bool> TriggerStats = null;

		public static HzTable Fish;

		public static HzTable Creature;

		public static HzTable Item;

		public static HzTable Other;

		public static HzTable Floating;

		public static HzTable Effect;

		public static string[] ProtectedPatternList = Array.Empty<string>();

		public static HashSet<int> BaseProxyHashes = new HashSet<int>();

		private static DateTime _lastWrite;

		private static float _pollTimer;

		private static bool _suppress;

		public static event Action? Changed;

		public static event Action? Triggers;

		public static void Init(ConfigFile cfg)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Expected O, but got Unknown
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Expected O, but got Unknown
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Expected O, but got Unknown
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Expected O, but got Unknown
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Expected O, but got Unknown
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Expected O, but got Unknown
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Expected O, but got Unknown
			//IL_0258: Unknown result type (might be due to invalid IL or missing references)
			//IL_0262: Expected O, but got Unknown
			//IL_0290: Unknown result type (might be due to invalid IL or missing references)
			//IL_029a: Expected O, but got Unknown
			//IL_02bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c7: Expected O, but got Unknown
			//IL_0310: Unknown result type (might be due to invalid IL or missing references)
			//IL_031a: Expected O, but got Unknown
			//IL_0340: Unknown result type (might be due to invalid IL or missing references)
			//IL_034a: Expected O, but got Unknown
			//IL_03ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b8: Expected O, but got Unknown
			//IL_03e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f0: Expected O, but got Unknown
			//IL_050e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0518: Expected O, but got Unknown
			//IL_0546: Unknown result type (might be due to invalid IL or missing references)
			//IL_0550: Expected O, but got Unknown
			//IL_057e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0588: Expected O, but got Unknown
			//IL_05d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_05db: Expected O, but got Unknown
			//IL_0609: Unknown result type (might be due to invalid IL or missing references)
			//IL_0613: Expected O, but got Unknown
			//IL_0641: Unknown result type (might be due to invalid IL or missing references)
			//IL_064b: Expected O, but got Unknown
			//IL_0679: Unknown result type (might be due to invalid IL or missing references)
			//IL_0683: Expected O, but got Unknown
			//IL_06b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_06bb: Expected O, but got Unknown
			//IL_06e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_06f3: Expected O, but got Unknown
			//IL_0721: Unknown result type (might be due to invalid IL or missing references)
			//IL_072b: Expected O, but got Unknown
			//IL_0759: Unknown result type (might be due to invalid IL or missing references)
			//IL_0763: Expected O, but got Unknown
			//IL_0791: Unknown result type (might be due to invalid IL or missing references)
			//IL_079b: Expected O, but got Unknown
			//IL_07e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_07ee: Expected O, but got Unknown
			//IL_082c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0836: Expected O, but got Unknown
			//IL_0878: Unknown result type (might be due to invalid IL or missing references)
			//IL_0882: Expected O, but got Unknown
			//IL_0901: Unknown result type (might be due to invalid IL or missing references)
			//IL_090b: Expected O, but got Unknown
			//IL_0954: Unknown result type (might be due to invalid IL or missing references)
			//IL_095e: Expected O, but got Unknown
			//IL_098c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0996: Expected O, but got Unknown
			//IL_0a1d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a27: Expected O, but got Unknown
			//IL_0a51: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a5b: Expected O, but got Unknown
			//IL_0aa4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0aae: Expected O, but got Unknown
			//IL_0adc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ae6: Expected O, but got Unknown
			_cfg = cfg;
			Enabled = cfg.Bind<bool>("01 General", "Enabled", true, "Master switch. false = every patch body behaves exactly like vanilla on its next call (telemetry included). Live.");
			ConfigReloadSeconds = cfg.Bind<float>("01 General", "ConfigReloadSeconds", 5f, new ConfigDescription("How often the cfg file is checked for hand edits and reloaded. 0 disables polling.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>()));
			F1Enabled = cfg.Bind<bool>("02 SendWindow", "Enabled", true, "Size each peer's in-flight ZDO window from its measured round-trip time instead of vanilla's fixed 10240 bytes. Throughput through a fixed window is window/RTT, so vanilla starves anyone past ~70 ms. Needs SteamTransport to matter above 150 KB/s. Live.");
			TargetRateKBps = cfg.Bind<int>("02 SendWindow", "TargetRateKBps", 300, new ConfigDescription("ZDO throughput per peer the window is sized for (KB/s). Clamped to the Steam send rate actually in effect (SteamTransport).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(32, 2048), Array.Empty<object>()));
			BdpFactor = cfg.Bind<float>("02 SendWindow", "BdpFactor", 1.25f, new ConfigDescription("Multiplier on RTT x rate. 1.0 = one round trip of data in flight; more adds headroom at the cost of standing queue.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 3f), Array.Empty<object>()));
			MaxWindowBytes = cfg.Bind<int>("02 SendWindow", "MaxWindowBytes", 65536, new ConfigDescription("Upper bound of the computed window. The lower bound is always vanilla's 10240.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10240, 262144), Array.Empty<object>()));
			MinWindowBytes = cfg.Bind<int>("02 SendWindow", "MinWindowBytes", 32768, new ConfigDescription("Lower bound of the computed window (vanilla 10240). A LAN peer with a ~0 ms ping gets no benefit from RTT sizing, yet on a dedicated server whose send lap runs at ~10 rounds/s 10240 x 10 = ~100 KB/s is its hard cap; 32768 lifts that to ~320 KB/s. Raise together with SteamTransport.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10240, 65536), Array.Empty<object>()));
			HeadroomBytes = cfg.Bind<int>("02 SendWindow", "HeadroomBytes", 2048, new ConfigDescription("Minimum free window before a round sends anything (vanilla 2048).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(512, 16384), Array.Empty<object>()));
			DrainIntervalSeconds = cfg.Bind<float>("02 SendWindow", "DrainIntervalSeconds", 2f, new ConfigDescription("Compatibility with mods that wait for the socket queue to drop under a fixed size (ServerSync, Jotunn, ConditionalConfigSync) before sending. If a peer's queue stays above DrainFloorBytes for this long, that peer's window is clamped to the floor until one round sees it below (max 3 s). 0 disables.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			DrainFloorBytes = cfg.Bind<int>("02 SendWindow", "DrainFloorBytes", 8192, new ConfigDescription("Must sit below the smallest threshold any installed mod waits for (older ServerSync 10000, current ones 20000) with room for one ZDO of overshoot.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2048, 20000), Array.Empty<object>()));
			RaiseJotunnQueueLimit = cfg.Bind<bool>("02 SendWindow", "RaiseJotunnQueueLimit", true, "Raise Jotunn.Entities.CustomRPC.MaximumSendQueueSize to MaxWindowBytes + 20000 while SendWindow is on (restored when off). Jotunn otherwise blocks its senders behind the widened window.");
			F2Enabled = cfg.Bind<bool>("03 Scheduler", "Enabled", true, "Serve every connected peer each send interval instead of vanilla's one-peer-per-frame lap with an idle arming frame. Costs one CreateSyncList scan per peer per round; the frame budget bounds it. Live.");
			SendIntervalSeconds = cfg.Bind<float>("03 Scheduler", "SendIntervalSeconds", 0.05f, new ConfigDescription("Seconds between send rounds per peer (vanilla 0.05).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.02f, 0.2f), Array.Empty<object>()));
			FrameBudgetMs = cfg.Bind<float>("03 Scheduler", "FrameBudgetMs", 4f, new ConfigDescription("Max milliseconds per frame spent sending; the remainder is owed to the next frame so nobody starves.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 16f), Array.Empty<object>()));
			ServerTargetFrameRate = cfg.Bind<int>("03 Scheduler", "ServerTargetFrameRate", 0, new ConfigDescription("Dedicated server only. The server build requests a hard 30 fps (GraphicsSettingsManager.RequestTargetFrameRateFromPreset), so vanilla's one-peer-per-frame lap runs at ~10 Hz per peer with two players and every relayed RPC waits up to 33 ms. 60 halves both at roughly double the main-thread CPU. 0 = leave vanilla's 30. Live.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 120), Array.Empty<object>()));
			F3Enabled = cfg.Bind<bool>("04 SteamTransport", "Enabled", true, "Raise Steam's per-connection send rate clamp from vanilla's 153600 B/s. SendRateMin and SendRateMax are set to the SAME value (the Steam header requires it; there is no estimator). Without this a bigger SendWindow only builds standing queue. Live (re-applied globally and per existing connection).");
			SendRateKBps = cfg.Bind<int>("04 SteamTransport", "SendRateKBps", 1024, new ConfigDescription("Send rate clamp per connection (KB/s). 150 = vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(150, 8192), Array.Empty<object>()));
			SendBufferBytes = cfg.Bind<int>("04 SteamTransport", "SendBufferBytes", 0, new ConfigDescription("Steam SendBufferSize (bytes). 0 leaves Steam's default 524288.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16777216), Array.Empty<object>()));
			F4Enabled = cfg.Bind<bool>("05 RelayThrottle", "Enabled", true, "Cap how often Default-type objects are relayed to a peer, by class and distance. Never drops anything: a held object stays dirty and goes next round. Never throttles: Prioritized/Solid/Terrain types, players, ships, carts, never-sent objects, ownership changes, force-sends. Live.");
			F4ClientUplink = cfg.Bind<bool>("05 RelayThrottle", "ApplyToClientUplink", true, "Also throttle the client's own uploads to the server with the same tables. Distance is measured from the NEAREST loaded player (yours or anyone else's), so nothing a player is looking at is ever slowed down.");
			NearMeters = cfg.Bind<float>("05 RelayThrottle", "NearMeters", 40f, new ConfigDescription("Inside this distance the Near rate applies.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 200f), Array.Empty<object>()));
			FarMeters = cfg.Bind<float>("05 RelayThrottle", "FarMeters", 100f, new ConfigDescription("Between Near and Far the Far rate applies; beyond it the Beyond rate.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 400f), Array.Empty<object>()));
			FishHz = cfg.Bind<string>("05 RelayThrottle", "FishHz", "6,3,2", "Max updates/s for Fish prefabs as near,far,beyond. 0 = unlimited.");
			CreatureHz = cfg.Bind<string>("05 RelayThrottle", "CreatureHz", "0,10,5", "Max updates/s for other Character prefabs as near,far,beyond. 0 = unlimited.");
			ItemHz = cfg.Bind<string>("05 RelayThrottle", "ItemHz", "0,5,3", "Max updates/s for ItemDrop prefabs as near,far,beyond. 0 = unlimited.");
			OtherHz = cfg.Bind<string>("05 RelayThrottle", "OtherHz", "0,0,0", "Max updates/s for every other Default-type prefab. 0 = unlimited (default: untouched).");
			FloatingHz = cfg.Bind<string>("05 RelayThrottle", "FloatingHz", "0,5,3", "Max updates/s for floating objects that are neither items nor creatures (prefabs with a Floating component: logs, debris) as near,far,beyond. A log bobbing in the water otherwise talks every frame. 0 = unlimited.");
			EffectHz = cfg.Bind<string>("05 RelayThrottle", "EffectHz", "10,5,3", "Max updates/s for networked effect objects (prefab names starting with sfx_ or vfx_, e.g. a sound that follows a bird) as near,far,beyond. 0 = unlimited.");
			F5Enabled = cfg.Bind<bool>("06 Priority", "Enabled", true, "Server only. Replace vanilla's strict Terrain > Solid > Prioritized > Default send order with an age-fair score so a starving creature, cart or drop near the player can outrank a distant building backlog. Foreign-owned Prioritized objects and Terrain keep their top tiers. Live.");
			LiveRefPos = cfg.Bind<bool>("06 Priority", "LiveRefPos", true, "Use the peer's live character position (x/z) for interest sorting and the RelayThrottle distance bands instead of the position the client reports every 2 s. Active while Priority is on.");
			AgeWeight = cfg.Bind<float>("06 Priority", "AgeWeight", 1.5f, new ConfigDescription("Metres of priority per second an object has waited since its last sync to this peer (vanilla 1.5).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>()));
			SolidBiasMeters = cfg.Bind<float>("06 Priority", "SolidBiasMeters", 64f, new ConfigDescription("Solid (building) objects sort as if this many metres closer. Vanilla is effectively infinite.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f), Array.Empty<object>()));
			NewNearBonusMeters = cfg.Bind<float>("06 Priority", "NewNearBonusMeters", 200f, new ConfigDescription("Objects never sent to this peer that are inside NearMeters sort as if this many metres closer (drops and spawns next to the player pop in first).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f), Array.Empty<object>()));
			F6Enabled = cfg.Bind<bool>("07 Deadband", "Enabled", true, "Owner-side: skip position/rotation/velocity ZDO writes that moved less than a deadband since the LAST WRITE (not since last frame). Stops physics jitter and wave bobbing from producing per-frame updates. Slow movers still write every deadband of travel. Never applies to Prioritized objects, players, ships or carts. Live.");
			ItemPosMeters = cfg.Bind<float>("07 Deadband", "ItemPosMeters", 0.02f, new ConfigDescription("Position deadband for ItemDrop prefabs (m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>()));
			ItemRotDegrees = cfg.Bind<float>("07 Deadband", "ItemRotDegrees", 2f, new ConfigDescription("Rotation deadband for ItemDrop prefabs (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			CreaturePosMeters = cfg.Bind<float>("07 Deadband", "CreaturePosMeters", 0.01f, new ConfigDescription("Position deadband for creatures and fish (m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>()));
			CreatureRotDegrees = cfg.Bind<float>("07 Deadband", "CreatureRotDegrees", 1f, new ConfigDescription("Rotation deadband for creatures and fish (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			OtherPosMeters = cfg.Bind<float>("07 Deadband", "OtherPosMeters", 0.01f, new ConfigDescription("Position deadband for every other synced object (m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>()));
			OtherRotDegrees = cfg.Bind<float>("07 Deadband", "OtherRotDegrees", 1f, new ConfigDescription("Rotation deadband for every other synced object (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			VelocityMps = cfg.Bind<float>("07 Deadband", "VelocityMps", 0.05f, new ConfigDescription("Velocity deadband (m/s). When an object comes to rest its velocity is written as exactly zero once so receivers stop extrapolating.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			MooredShipPosMeters = cfg.Bind<float>("07 Deadband", "MooredShipPosMeters", 0.05f, new ConfigDescription("Position deadband for a ship with nobody aboard (m); a moored ship only bobs. A ship with a player aboard is never touched.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>()));
			MooredShipRotDegrees = cfg.Bind<float>("07 Deadband", "MooredShipRotDegrees", 2f, new ConfigDescription("Rotation deadband for a ship with nobody aboard (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			TelemetryEnabled = cfg.Bind<bool>("08 Telemetry", "Enabled", true, "Periodic per-peer stats: Steam RTT vs app RTT, window, queue, in-flight, rounds, refused rounds, candidates, sent, bytes, DEFERRED (candidates - sent), throttled, top prefabs. Live.");
			IntervalSeconds = cfg.Bind<float>("08 Telemetry", "IntervalSeconds", 10f, new ConfigDescription("Seconds between stats lines.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 300f), Array.Empty<object>()));
			PerPrefab = cfg.Bind<bool>("08 Telemetry", "PerPrefab", true, "Tally sent/received ZDO updates by prefab (a dictionary increment per update).");
			TopN = cfg.Bind<int>("08 Telemetry", "TopN", 8, new ConfigDescription("How many prefabs to list per line.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 30), Array.Empty<object>()));
			StatsFile = cfg.Bind<bool>("08 Telemetry", "StatsFile", true, "Also append every stats/census/item line to BepInEx/BeanheimNet-stats.log, which survives restarts (LogOutput.log does not).");
			StatsLogMaxMB = cfg.Bind<int>("08 Telemetry", "StatsLogMaxMB", 16, new ConfigDescription("Rotate the stats file to .1 when it exceeds this size.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 512), Array.Empty<object>()));
			CensusOnBoot = cfg.Bind<bool>("08 Telemetry", "CensusOnBoot", true, "Server: count every ZDO by prefab and type once the world is loaded (sliced over frames). Also available as 'bnet census'.");
			OwnerWriteStats = cfg.Bind<bool>("08 Telemetry", "OwnerWriteStats", false, "Tally owner-side ZDO revision bumps by prefab (which objects talk the most). Patches a hot path, so it is installed only when true at startup: needs a restart to turn on.");
			ItemReport = cfg.Bind<bool>("09 Items", "ReportEnabled", true, "Server: periodic report of ItemDrop ZDOs (count by prefab, age, in water, far from players). Live.");
			SweepMinutes = cfg.Bind<float>("09 Items", "SweepMinutes", 15f, new ConfigDescription("Minutes between sweeps (sliced over frames).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 1440f), Array.Empty<object>()));
			ItemCleanup = cfg.Bind<bool>("09 Items", "Cleanup", false, "DESTROY items that match every condition below. Each deletion is logged with prefab, position and age. Off = report only. Live. Hard rule that no setting can relax: an item with custom data (EpicLoot enchantments and other mod data), a quality above 1, or a max stack size of 1 (all gear) is never deleted.");
			MaxAgeHours = cfg.Bind<float>("09 Items", "MaxAgeHours", 6f, new ConfigDescription("Older than this many hours of played time (world time only advances with players online). Items never instantiated have no age and are never deleted.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1000f), Array.Empty<object>()));
			ProtectRadiusMeters = cfg.Bind<float>("09 Items", "ProtectRadiusMeters", 200f, new ConfigDescription("Never delete within this distance of any connected player (a player's active area reaches ~181 m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(50f, 2000f), Array.Empty<object>()));
			OnlyInWater = cfg.Bind<bool>("09 Items", "OnlyInWater", true, "Only delete items at or below sea level (floating or sunk).");
			ProtectedPatterns = cfg.Bind<string>("09 Items", "ProtectedPatterns", "Trophy,Wishbone,DragonEgg,CryptKey,Totem", "Comma-separated substrings; a prefab name containing any of them is never deleted (case-insensitive).");
			BaseProxyPrefabs = cfg.Bind<string>("09 Items", "BaseProxyPrefabs", "piece_workbench,bed,guard_stone,piece_bed02,fire_pit,forge,piece_chest_wood,piece_chest", "Server has no 'inside base' test; an item within BaseProxyRadiusMeters of any of these prefabs counts as in a base and is never deleted.");
			BaseProxyRadiusMeters = cfg.Bind<float>("09 Items", "BaseProxyRadiusMeters", 32f, new ConfigDescription("Radius for the base proxy check.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(8f, 128f), Array.Empty<object>()));
			MaxDeletionsPerSweep = cfg.Bind<int>("09 Items", "MaxDeletionsPerSweep", 200, new ConfigDescription("Safety cap per sweep.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 5000), Array.Empty<object>()));
			F9Enabled = cfg.Bind<bool>("10 ItemMerge", "Enabled", false, "Client side (the machine that owns the items). Merge same-name, same-quality, same-variant stackable drops within MergeRadiusMeters into one stack, up to the item's max stack size; the merged-away drops are destroyed. NEVER touches an item that carries custom data (every EpicLoot enchantment lives there), a quality above 1, or a max stack of 1 (all gear). Vanilla does the same at 4 m, once per item, and only while more than 200 drops are loaded (ItemDrop.AutoStackItems); this runs every MergeIntervalSeconds regardless. Live.");
			MergeRadiusMeters = cfg.Bind<float>("10 ItemMerge", "MergeRadiusMeters", 5f, new ConfigDescription("Merge radius (vanilla auto-stack uses 4).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 16f), Array.Empty<object>()));
			MergeIntervalSeconds = cfg.Bind<float>("10 ItemMerge", "MergeIntervalSeconds", 30f, new ConfigDescription("Seconds between merge attempts per item (checked from the item's 10 s slow update).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 600f), Array.Empty<object>()));
			TriggerCensus = cfg.Bind<bool>("11 Triggers", "Census", false, "One-shot: set true (hand-edit the server cfg; it is re-read within ConfigReloadSeconds) to run a world census now. Resets to false by itself. Same as 'bnet census'.");
			TriggerItemReport = cfg.Bind<bool>("11 Triggers", "ItemReport", false, "One-shot: run the item report now (no deletions). Same as 'bnet items'.");
			TriggerItemCleanupOnce = cfg.Bind<bool>("11 Triggers", "ItemCleanupOnce", false, "One-shot: run ONE item cleanup sweep with the configured criteria even if Cleanup is false. Every deletion is logged. Same as 'bnet cleanup run'.");
			TriggerStats = cfg.Bind<bool>("11 Triggers", "Stats", false, "One-shot: write a stats report now without waiting for the interval. Same as 'bnet stats'.");
			cfg.SettingChanged += delegate
			{
				if (!_suppress)
				{
					Refresh();
				}
			};
			RememberWriteTime();
			Refresh();
		}

		public static void Refresh()
		{
			Fish = HzTable.Parse(FishHz.Value);
			Creature = HzTable.Parse(CreatureHz.Value);
			Item = HzTable.Parse(ItemHz.Value);
			Other = HzTable.Parse(OtherHz.Value);
			Floating = HzTable.Parse(FloatingHz.Value);
			Effect = HzTable.Parse(EffectHz.Value);
			ProtectedPatternList = Split(ProtectedPatterns.Value);
			BaseProxyHashes = new HashSet<int>();
			string[] array = Split(BaseProxyPrefabs.Value);
			foreach (string text in array)
			{
				BaseProxyHashes.Add(StringExtensionMethods.GetStableHashCode(text));
			}
			try
			{
				Cfg.Changed?.Invoke();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"config change handler threw: {arg}");
			}
			try
			{
				Cfg.Triggers?.Invoke();
			}
			catch (Exception arg2)
			{
				Plugin.Log.LogError((object)$"trigger handler threw: {arg2}");
			}
		}

		public static void ResetTrigger(ConfigEntry<bool> entry)
		{
			if (!entry.Value)
			{
				return;
			}
			try
			{
				_suppress = true;
				entry.Value = false;
				_cfg.Save();
				RememberWriteTime();
			}
			finally
			{
				_suppress = false;
			}
		}

		private static string[] Split(string csv)
		{
			List<string> list = new List<string>();
			string[] array = csv.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					list.Add(text);
				}
			}
			return list.ToArray();
		}

		public static HzTable? HzFor(PrefabClass.Cls cls)
		{
			HzTable value = cls switch
			{
				PrefabClass.Cls.Fish => Fish, 
				PrefabClass.Cls.Creature => Creature, 
				PrefabClass.Cls.Item => Item, 
				PrefabClass.Cls.Other => Other, 
				PrefabClass.Cls.Floating => Floating, 
				PrefabClass.Cls.Effect => Effect, 
				_ => default(HzTable), 
			};
			if (!value.IsUnlimited)
			{
				return value;
			}
			return null;
		}

		public static void DeadbandFor(PrefabClass.Cls cls, out float posMeters, out float rotDegrees)
		{
			switch (cls)
			{
			case PrefabClass.Cls.Item:
			case PrefabClass.Cls.Floating:
				posMeters = ItemPosMeters.Value;
				rotDegrees = ItemRotDegrees.Value;
				break;
			case PrefabClass.Cls.Fish:
			case PrefabClass.Cls.Creature:
				posMeters = CreaturePosMeters.Value;
				rotDegrees = CreatureRotDegrees.Value;
				break;
			default:
				posMeters = OtherPosMeters.Value;
				rotDegrees = OtherRotDegrees.Value;
				break;
			}
		}

		private static void RememberWriteTime()
		{
			try
			{
				_lastWrite = File.GetLastWriteTimeUtc(_cfg.ConfigFilePath);
			}
			catch
			{
				_lastWrite = DateTime.MinValue;
			}
		}

		public static void PollReload(float dt)
		{
			float value = ConfigReloadSeconds.Value;
			if (value <= 0f)
			{
				return;
			}
			_pollTimer += dt;
			if (_pollTimer < value)
			{
				return;
			}
			_pollTimer = 0f;
			DateTime lastWriteTimeUtc;
			try
			{
				lastWriteTimeUtc = File.GetLastWriteTimeUtc(_cfg.ConfigFilePath);
			}
			catch
			{
				return;
			}
			if (lastWriteTimeUtc == _lastWrite)
			{
				return;
			}
			_lastWrite = lastWriteTimeUtc;
			try
			{
				_suppress = true;
				_cfg.Reload();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("config reload failed: " + ex.Message));
				return;
			}
			finally
			{
				_suppress = false;
			}
			Refresh();
			Plugin.Log.LogInfo((object)("config reloaded from disk: " + Summary()));
		}

		public static void ForceReload()
		{
			try
			{
				_suppress = true;
				_cfg.Reload();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("config reload failed: " + ex.Message));
				return;
			}
			finally
			{
				_suppress = false;
			}
			RememberWriteTime();
			Refresh();
			Plugin.Log.LogInfo((object)("config reloaded on request: " + Summary()));
		}

		public static string Summary()
		{
			return "window " + (F1Enabled.Value ? "on" : "off") + ", scheduler " + (F2Enabled.Value ? "on" : "off") + " fps " + ((ServerTargetFrameRate.Value > 0) ? ServerTargetFrameRate.Value.ToString() : "vanilla") + ", steam " + (F3Enabled.Value ? "on" : "off") + ", throttle " + (F4Enabled.Value ? "on" : "off") + ", priority " + (F5Enabled.Value ? "on" : "off") + ", deadband " + (F6Enabled.Value ? "on" : "off") + ", telemetry " + (TelemetryEnabled.Value ? "on" : "off") + ", items report " + (ItemReport.Value ? "on" : "off") + " cleanup " + (ItemCleanup.Value ? "ON" : "off") + ", merge " + (F9Enabled.Value ? "on" : "off");
		}
	}
	internal static class Env
	{
		public static readonly bool IsHeadless = (int)SystemInfo.graphicsDeviceType == 4;

		private static readonly Dictionary<Type, FieldInfo?> OriginalField = new Dictionary<Type, FieldInfo>();

		private static int _statusFailures;

		private static bool _statusDisabled;

		private static bool _statusLogged;

		public static int StatusNotOk;

		public static int StatusOk;

		public static string LastStatusResult = "";

		public static bool IsServer
		{
			get
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					return ZNet.instance.IsServer();
				}
				return false;
			}
		}

		public static bool IsPluginLoaded(string guid)
		{
			return Chainloader.PluginInfos.ContainsKey(guid);
		}

		public static ZSteamSocket? Unwrap(ISocket? socket)
		{
			int num = 0;
			while (socket != null && num < 32)
			{
				ZSteamSocket val = (ZSteamSocket)(object)((socket is ZSteamSocket) ? socket : null);
				if (val != null)
				{
					return val;
				}
				Type type = ((object)socket).GetType();
				if (!OriginalField.TryGetValue(type, out FieldInfo value))
				{
					value = null;
					FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						if (fieldInfo.Name == "Original" && typeof(ISocket).IsAssignableFrom(fieldInfo.FieldType))
						{
							value = fieldInfo;
							break;
						}
					}
					if (value == null)
					{
						fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						foreach (FieldInfo fieldInfo2 in fields)
						{
							if (typeof(ISocket).IsAssignableFrom(fieldInfo2.FieldType))
							{
								value = fieldInfo2;
								break;
							}
						}
					}
					OriginalField[type] = value;
				}
				if (value == null)
				{
					return null;
				}
				object? value2 = value.GetValue(socket);
				socket = (ISocket?)((value2 is ISocket) ? value2 : null);
				num++;
			}
			return null;
		}

		public unsafe static bool TryGetStatus(ISocket socket, out SteamNetConnectionRealTimeStatus_t status)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: 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_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Invalid comparison between Unknown and I4
			status = default(SteamNetConnectionRealTimeStatus_t);
			if (_statusDisabled || !Bindings.SteamSocketOk)
			{
				return false;
			}
			ZSteamSocket val = Unwrap(socket);
			if (val == null)
			{
				return false;
			}
			try
			{
				HSteamNetConnection val2 = Bindings.SteamCon.Invoke(val);
				if (val2 == HSteamNetConnection.Invalid)
				{
					return false;
				}
				SteamNetConnectionRealTimeLaneStatus_t val3 = default(SteamNetConnectionRealTimeLaneStatus_t);
				EResult val4 = (IsHeadless ? SteamGameServerNetworkingSockets.GetConnectionRealTimeStatus(val2, ref status, 0, ref val3) : SteamNetworkingSockets.GetConnectionRealTimeStatus(val2, ref status, 0, ref val3));
				if ((int)val4 == 1)
				{
					_statusFailures = 0;
					StatusOk++;
					return true;
				}
				StatusNotOk++;
				LastStatusResult = ((object)(*(EResult*)(&val4))/*cast due to .constrained prefix*/).ToString();
				return false;
			}
			catch (Exception ex)
			{
				if (++_statusFailures >= 5 && !_statusLogged)
				{
					_statusLogged = true;
					_statusDisabled = true;
					Plugin.Log.LogWarning((object)("Steam connection status unavailable (" + ex.GetType().Name + ": " + ex.Message + "); per-peer RTT and queue readings stay off for this process, send window stays vanilla."));
				}
				return false;
			}
		}

		public static bool TrySetSteamInt(ESteamNetworkingConfigValue key, int value, ESteamNetworkingConfigScope scope, IntPtr scopeObj)
		{
			//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_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			GCHandle gCHandle = GCHandle.Alloc(value, GCHandleType.Pinned);
			try
			{
				return IsHeadless ? SteamGameServerNetworkingUtils.SetConfigValue(key, scope, scopeObj, (ESteamNetworkingConfigDataType)1, gCHandle.AddrOfPinnedObject()) : SteamNetworkingUtils.SetConfigValue(key, scope, scopeObj, (ESteamNetworkingConfigDataType)1, gCHandle.AddrOfPinnedObject());
			}
			finally
			{
				gCHandle.Free();
			}
		}

		public static bool TryGetSteamInt(ESteamNetworkingConfigValue key, out int value)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Invalid comparison between Unknown and I4
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Invalid comparison between Unknown and I4
			value = 0;
			int[] array = new int[1];
			GCHandle gCHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
			try
			{
				ulong num = 4uL;
				ESteamNetworkingConfigDataType val2 = default(ESteamNetworkingConfigDataType);
				ESteamNetworkingGetConfigValueResult val = (IsHeadless ? SteamGameServerNetworkingUtils.GetConfigValue(key, (ESteamNetworkingConfigScope)1, IntPtr.Zero, ref val2, gCHandle.AddrOfPinnedObject(), ref num) : SteamNetworkingUtils.GetConfigValue(key, (ESteamNetworkingConfigScope)1, IntPtr.Zero, ref val2, gCHandle.AddrOfPinnedObject(), ref num));
				if ((int)val == 1 || (int)val == 2)
				{
					value = array[0];
					return true;
				}
				return false;
			}
			finally
			{
				gCHandle.Free();
			}
		}
	}
	[BepInPlugin("helix.beanheim.net", "BeanheimNet", "0.2.2")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "helix.beanheim.net";

		public const string PluginName = "BeanheimNet";

		public const string PluginVersion = "0.2.2";

		internal static ManualLogSource Log = null;

		internal static Plugin Instance = null;

		internal static Harmony HarmonyInstance = null;

		internal static bool Refused;

		internal static string RefusedBy = "";

		private static readonly string[] IncompatibleGuids = new string[6] { "org.bepinex.plugins.network", "akoozie.valheimtune", "MidnightsFX.NetworkPerformanceSystem", "redseiko.valheim.returntosender", "CW_Jesse.BetterNetworking", "VitByr.VBNetTweaks" };

		private static readonly IFeature[] AllFeatures = new IFeature[10]
		{
			new TelemetryFeature(),
			new SteamTransportFeature(),
			new SendWindowFeature(),
			new SchedulerFeature(),
			new RelayThrottleFeature(),
			new PriorityFeature(),
			new DeadbandFeature(),
			new ItemSweepFeature(),
			new ItemMergeFeature(),
			new CommandsFeature()
		};

		private static readonly List<string> Applied = new List<string>();

		private static readonly List<string> Skipped = new List<string>();

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			Cfg.Init(((BaseUnityPlugin)this).Config);
			Log.LogInfo((object)("BeanheimNet 0.2.2 on " + (Env.IsHeadless ? "dedicated server (headless)" : "client") + ": " + Cfg.Summary()));
		}

		private void Start()
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			string[] incompatibleGuids = IncompatibleGuids;
			foreach (string text in incompatibleGuids)
			{
				if (Env.IsPluginLoaded(text))
				{
					Refused = true;
					RefusedBy = RefusedBy + ((RefusedBy.Length > 0) ? ", " : "") + text;
				}
			}
			if (Refused)
			{
				Log.LogError((object)(RefusedBy + " rewrites the same send path. BeanheimNet installs TELEMETRY ONLY; every improvement feature is skipped. Remove one of the two mods; do not run both."));
			}
			Bindings.Init(Log);
			HarmonyInstance = new Harmony("helix.beanheim.net");
			IFeature[] allFeatures = AllFeatures;
			foreach (IFeature feature in allFeatures)
			{
				try
				{
					if (feature.Apply(HarmonyInstance, Log))
					{
						Applied.Add(feature.Id);
					}
					else
					{
						Skipped.Add(feature.Id);
					}
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"[{feature.Id}] threw while applying, skipping it: {arg}");
					Skipped.Add(feature.Id);
				}
			}
			LogOtherOwners();
			Log.LogInfo((object)(string.Format("{0} {1}: {2} applied ({3}), ", "BeanheimNet", "0.2.2", Applied.Count, string.Join(", ", Applied.ToArray())) + string.Format("{0} skipped ({1}).", Skipped.Count, string.Join(", ", Skipped.ToArray()))));
			StatsFile.WriteBootMarker();
		}

		private static void LogOtherOwners()
		{
			foreach (MethodBase patchedMethod in Bindings.PatchedMethods)
			{
				try
				{
					Patches patchInfo = Harmony.GetPatchInfo(patchedMethod);
					if (patchInfo == null)
					{
						continue;
					}
					List<string> list = new List<string>();
					foreach (string owner in patchInfo.Owners)
					{
						if (owner != "helix.beanheim.net")
						{
							list.Add(owner);
						}
					}
					if (list.Count > 0)
					{
						Log.LogInfo((object)("[owners] " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name + " is also patched by: " + string.Join(", ", list.ToArray())));
					}
				}
				catch (Exception ex)
				{
					Log.LogDebug((object)("owner listing failed for " + patchedMethod.Name + ": " + ex.Message));
				}
			}
		}

		private void Update()
		{
			float unscaledDeltaTime = Time.unscaledDeltaTime;
			try
			{
				Cfg.PollReload(unscaledDeltaTime);
				FrameCap.Tick();
				Stats.Tick(unscaledDeltaTime);
				Census.Tick();
				ItemSweepFeature.Tick(unscaledDeltaTime);
			}
			catch (Exception ex)
			{
				Stats.LogOnce("update", ex);
			}
		}

		private void OnDestroy()
		{
			StatsFile.Flush();
		}
	}
}
namespace BeanheimNet.Util
{
	internal static class FrameCap
	{
		private const int VanillaServerFps = 30;

		private static int _applied;

		public static void Tick()
		{
			if (!Env.IsHeadless)
			{
				return;
			}
			int num = (Cfg.Enabled.Value ? Cfg.ServerTargetFrameRate.Value : 0);
			if (num <= 0)
			{
				if (_applied > 0)
				{
					Application.targetFrameRate = 30;
					Plugin.Log.LogInfo((object)$"[Scheduler] server target frame rate restored to vanilla {30}.");
					_applied = 0;
				}
			}
			else if (Application.targetFrameRate != num)
			{
				Application.targetFrameRate = num;
				if (_applied != num)
				{
					Plugin.Log.LogInfo((object)$"[Scheduler] server target frame rate {30} -> {num} (vanilla requests 30; re-asserted while the setting is on).");
					_applied = num;
				}
			}
		}
	}
	internal readonly struct HzTable
	{
		public readonly float Near;

		public readonly float Far;

		public readonly float Beyond;

		public bool IsUnlimited
		{
			get
			{
				if (Near <= 0f && Far <= 0f)
				{
					return Beyond <= 0f;
				}
				return false;
			}
		}

		public HzTable(float near, float far, float beyond)
		{
			Near = near;
			Far = far;
			Beyond = beyond;
		}

		public static HzTable Parse(string text)
		{
			try
			{
				string[] array = text.Split(new char[1] { ',' });
				if (array.Length != 3)
				{
					return default(HzTable);
				}
				return new HzTable(P(array[0]), P(array[1]), P(array[2]));
			}
			catch
			{
				return default(HzTable);
			}
			static float P(string s)
			{
				return Math.Max(0f, float.Parse(s.Trim(), CultureInfo.InvariantCulture));
			}
		}

		public float For(float distance, float nearM, float farM)
		{
			if (!(distance < nearM))
			{
				if (!(distance < farM))
				{
					return Beyond;
				}
				return Far;
			}
			return Near;
		}
	}
	internal static class PrefabClass
	{
		internal enum Cls : byte
		{
			Unknown,
			Other,
			Exempt,
			Fish,
			Creature,
			Item,
			Terrain,
			Floating,
			Effect
		}

		private static readonly Dictionary<int, Cls> Cache = new Dictionary<int, Cls>();

		private static readonly Dictionary<int, string> Names = new Dictionary<int, string>();

		public static Cls Get(int prefabHash)
		{
			if (Cache.TryGetValue(prefabHash, out var value))
			{
				return value;
			}
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return Cls.Unknown;
			}
			GameObject prefab = instance.GetPrefab(prefabHash);
			if ((Object)(object)prefab == (Object)null)
			{
				Cache[prefabHash] = Cls.Other;
				return Cls.Other;
			}
			Names[prefabHash] = ((Object)prefab).name;
			value = Cls.Other;
			if ((Object)(object)prefab.GetComponent<Player>() != (Object)null || (Object)(object)prefab.GetComponent<Ship>() != (Object)null || (Object)(object)prefab.GetComponent<Vagon>() != (Object)null)
			{
				value = Cls.Exempt;
			}
			else if ((Object)(object)prefab.GetComponent<Fish>() != (Object)null)
			{
				value = Cls.Fish;
			}
			else if ((Object)(object)prefab.GetComponent<Character>() != (Object)null)
			{
				value = Cls.Creature;
			}
			else if ((Object)(object)prefab.GetComponent<ItemDrop>() != (Object)null)
			{
				value = Cls.Item;
			}
			else if ((Object)(object)prefab.GetComponent<TerrainComp>() != (Object)null)
			{
				value = Cls.Terrain;
			}
			else if ((Object)(object)prefab.GetComponent<Floating>() != (Object)null)
			{
				value = Cls.Floating;
			}
			else if (((Object)prefab).name.StartsWith("sfx_", StringComparison.Ordinal) || ((Object)prefab).name.StartsWith("vfx_", StringComparison.Ordinal))
			{
				value = Cls.Effect;
			}
			Cache[prefabHash] = value;
			return value;
		}

		public static string Name(int prefabHash)
		{
			if (Names.TryGetValue(prefabHash, out string value))
			{
				return value;
			}
			Get(prefabHash);
			if (Names.TryGetValue(prefabHash, out value))
			{
				return value;
			}
			return "hash:" + prefabHash;
		}

		public static void Clear()
		{
			Cache.Clear();
			Names.Clear();
		}
	}
}
namespace BeanheimNet.Telemetry
{
	internal static class Census
	{
		private const int SliceSize = 20000;

		private static bool _bootDone;

		private static float _armTimer;

		private static List<ZDO>? _work;

		private static int _idx;

		private static string _reason = "";

		private static readonly Dictionary<int, int> ByPrefab = new Dictionary<int, int>();

		private static readonly int[] ByType = new int[4];

		private static int _total;

		private static int _items;

		private static int _creatures;

		private static int _fish;

		private static int _terrain;

		private static int _persistent;

		private static int _distant;

		private static int _owned;

		private static long _terrainBytes;

		private static int _terrainMax;

		public static bool Running => _work != null;

		public static void Tick()
		{
			if (_work != null)
			{
				Slice();
			}
			else if (!_bootDone && Cfg.Enabled.Value && Cfg.CensusOnBoot.Value && Env.IsServer && ZDOMan.instance != null && !((Object)(object)ZNetScene.instance == (Object)null) && ZDOMan.instance.NrOfObjects() != 0)
			{
				_armTimer += Time.unscaledDeltaTime;
				if (!(_armTimer < 30f))
				{
					_bootDone = true;
					Start("boot");
				}
			}
		}

		public static bool Start(string reason)
		{
			if (_work != null || !Bindings.ZdoManOk || ZDOMan.instance == null)
			{
				return false;
			}
			try
			{
				_work = new List<ZDO>(Bindings.ObjectsById.Invoke(ZDOMan.instance).Values);
			}
			catch (Exception ex)
			{
				Stats.LogOnce("census", ex);
				_work = null;
				return false;
			}
			_idx = 0;
			_reason = reason;
			ByPrefab.Clear();
			Array.Clear(ByType, 0, ByType.Length);
			_total = (_items = (_creatures = (_fish = (_terrain = (_persistent = (_distant = (_owned = 0)))))));
			_terrainBytes = 0L;
			_terrainMax = 0;
			Plugin.Log.LogInfo((object)$"census ({reason}): scanning {_work.Count:N0} ZDOs, {20000:N0} per frame.");
			return true;
		}

		private static void Slice()
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected I4, but got Unknown
			List<ZDO> work = _work;
			int num = Math.Min(_idx + 20000, work.Count);
			try
			{
				while (_idx < num)
				{
					ZDO val = work[_idx];
					if (val != null && val.IsValid())
					{
						_total++;
						int num2 = (int)val.Type;
						if (num2 >= 0 && num2 < 4)
						{
							ByType[num2]++;
						}
						if (val.Persistent)
						{
							_persistent++;
						}
						if (val.Distant)
						{
							_distant++;
						}
						if (val.HasOwner())
						{
							_owned++;
						}
						int prefab = val.GetPrefab();
						Stats.Inc(ByPrefab, prefab);
						switch (PrefabClass.Get(prefab))
						{
						case PrefabClass.Cls.Item:
							_items++;
							break;
						case PrefabClass.Cls.Fish:
							_fish++;
							_creatures++;
							break;
						case PrefabClass.Cls.Creature:
							_creatures++;
							break;
						case PrefabClass.Cls.Terrain:
						{
							_terrain++;
							byte[] byteArray = val.GetByteArray(ZDOVars.s_TCData, (byte[])null);
							if (byteArray != null)
							{
								_terrainBytes += byteArray.Length;
								if (byteArray.Length > _terrainMax)
								{
									_terrainMax = byteArray.Length;
								}
							}
							break;
						}
						}
					}
					_idx++;
				}
			}
			catch (Exception ex)
			{
				Stats.LogOnce("census.slice", ex);
				_work = null;
				return;
			}
			if (_idx >= work.Count)
			{
				Finish();
			}
		}

		private static void Finish()
		{
			_work = null;
			int num = 0;
			int num2 = 0;
			List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>(ByPrefab);
			foreach (KeyValuePair<int, int> item in list)
			{
				if (PrefabClass.Name(item.Key).EndsWith("_planned", StringComparison.Ordinal))
				{
					num += item.Value;
					num2++;
				}
			}
			list.Sort((KeyValuePair<int, int> a, KeyValuePair<int, int> b) => b.Value.CompareTo(a.Value));
			List<string> list2 = new List<string>();
			list2.Add($"census ({_reason}): {_total:N0} ZDOs | types Default {ByType[0]:N0} Prioritized {ByType[1]:N0} Solid {ByType[2]:N0} Terrain {ByType[3]:N0} | " + $"persistent {_persistent:N0} distant {_distant:N0} owned {_owned:N0} | items {_items:N0} | creatures {_creatures:N0} (fish {_fish:N0}) | " + $"planned ghosts {num:N0} ({num2} prefabs) | terrain blobs {_terrain:N0} total {(float)_terrainBytes / 1024f:F1}KB max {(float)_terrainMax / 1024f:F1}KB");
			List<string> list3 = list2;
			StringBuilder stringBuilder = new StringBuilder("census top: ");
			for (int num3 = 0; num3 < list.Count && num3 < 40; num3++)
			{
				if (num3 > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(PrefabClass.Name(list[num3].Key)).Append(' ').Append(list[num3].Value.ToString("N0"));
			}
			list3.Add(stringBuilder.ToString());
			foreach (string item2 in list3)
			{
				Plugin.Log.LogInfo((object)item2);
				StatsFile.Append(item2);
			}
		}
	}
	internal sealed class PeerStats
	{
		public long Uid;

		public string Name = "?";

		public ZRpc? Rpc;

		public int SteamPing;

		public int SteamRate;

		public int Inflight;

		public int Window = 10240;

		public int LastQueue = -1;

		public int AppRttMs;

		public float PingSentAt;

		public int CtxSent0;

		public int CtxData0;

		public long CtxT0;

		public bool CtxFlush;

		public long QueueSum;

		public int QueueN;

		public int QueueMax;

		public int InflightMax;

		public int UnsentMax;

		public int Rounds;

		public int Refused;

		public long CandSum;

		public int CandMax;

		public int Sent;

		public long Bytes;

		public int Deferred;

		public int Throttled;

		public double SendMsSum;

		public double SendMsMax;

		public readonly Dictionary<int, int> SentByPrefab = new Dictionary<int, int>();

		public float AboveFloorSince = -1f;

		public bool Draining;

		public float DrainStart;

		public int Drains;

		public int DrainAborts;

		public void ResetInterval()
		{
			QueueSum = 0L;
			QueueN = 0;
			QueueMax = 0;
			InflightMax = 0;
			UnsentMax = 0;
			Rounds = 0;
			Refused = 0;
			CandSum = 0L;
			CandMax = 0;
			Sent = 0;
			Bytes = 0L;
			Deferred = 0;
			Throttled = 0;
			SendMsSum = 0.0;
			SendMsMax = 0.0;
			Drains = 0;
			DrainAborts = 0;
			SentByPrefab.Clear();
		}
	}
	internal static class Stats
	{
		private static readonly Dictionary<long, PeerStats> Peers = new Dictionary<long, PeerStats>();

		private static readonly Dictionary<ZRpc, PeerStats> ByRpc = new Dictionary<ZRpc, PeerStats>();

		public static readonly Dictionary<int, int> RecvByPrefab = new Dictionary<int, int>();

		public static readonly Dictionary<int, int> OwnerWritesByPrefab = new Dictionary<int, int>();

		public static int RecvCount;

		public static int SchedulerFrames;

		public static int SchedulerServed;

		public static int SchedulerBudgetBreaks;

		public static int DbPos;

		public static int DbRot;

		public static int DbVel;

		public static int DbZero;

		public static int DbBody;

		private static float _acc;

		private static int _frames;

		private static float _frameSum;

		private static float _frameMax;

		private static int _frames50;

		private static int _frames100;

		private static int _gcBase = -1;

		private static TimeSpan _cpuBase;

		private static float _cpuWallBase = -1f;

		private static bool _cpuUnavailable;

		private static Process? _proc;

		private static readonly Dictionary<string, float> OnceAt = new Dictionary<string, float>();

		public static IEnumerable<PeerStats> All => Peers.Values;

		public static PeerStats PeerFor(ZNetPeer np)
		{
			if (!Peers.TryGetValue(np.m_uid, out PeerStats value))
			{
				value = new PeerStats
				{
					Uid = np.m_uid
				};
				Peers[np.m_uid] = value;
			}
			if (!string.IsNullOrEmpty(np.m_playerName))
			{
				value.Name = np.m_playerName;
			}
			if (np.m_rpc != null && value.Rpc != np.m_rpc)
			{
				value.Rpc = np.m_rpc;
				ByRpc[np.m_rpc] = value;
			}
			return value;
		}

		public static PeerStats? ByRpcOrNull(ZRpc rpc)
		{
			if (!ByRpc.TryGetValue(rpc, out PeerStats value))
			{
				return null;
			}
			return value;
		}

		public static void Inc(Dictionary<int, int> d, int key)
		{
			d.TryGetValue(key, out var value);
			d[key] = value + 1;
		}

		public static void LogOnce(string key, Exception ex)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!OnceAt.TryGetValue(key, out var value) || !(realtimeSinceStartup - value < 60f))
			{
				OnceAt[key] = realtimeSinceStartup;
				Plugin.Log.LogError((object)$"[{key}] threw (vanilla behaviour used for that call): {ex}");
			}
		}

		public static void Tick(float dt)
		{
			if (_cpuWallBase < 0f)
			{
				ResetBases();
			}
			_frames++;
			_frameSum += dt;
			if (dt > _frameMax)
			{
				_frameMax = dt;
			}
			if (dt > 0.05f)
			{
				_frames50++;
			}
			if (dt > 0.1f)
			{
				_frames100++;
			}
			_acc += dt;
			float value = Cfg.IntervalSeconds.Value;
			if (!(_acc < value))
			{
				_acc = 0f;
				if (Cfg.Enabled.Value && Cfg.TelemetryEnabled.Value && (Object)(object)ZNet.instance != (Object)null)
				{
					Report();
				}
				else
				{
					ResetAll();
				}
			}
		}

		public static void Report()
		{
			foreach (string item in BuildReport(reset: true))
			{
				Plugin.Log.LogInfo((object)item);
				StatsFile.Append(item);
			}
		}

		private static void ResetAll()
		{
			foreach (PeerStats value in Peers.Values)
			{
				value.ResetInterval();
			}
			RecvByPrefab.Clear();
			OwnerWritesByPrefab.Clear();
			RecvCount = 0;
			SchedulerFrames = (SchedulerServed = (SchedulerBudgetBreaks = 0));
			DbPos = (DbRot = (DbVel = (DbZero = (DbBody = 0))));
			_frames = 0;
			_frameSum = 0f;
			_frameMax = 0f;
			_frames50 = 0;
			_frames100 = 0;
			ResetBases();
		}

		private static void ResetBases()
		{
			_gcBase = GC.CollectionCount(0);
			_cpuBase = ReadCpu();
			_cpuWallBase = Time.realtimeSinceStartup;
		}

		private static TimeSpan ReadCpu()
		{
			if (_cpuUnavailable)
			{
				return TimeSpan.Zero;
			}
			try
			{
				if (_proc == null)
				{
					_proc = Process.GetCurrentProcess();
				}
				_proc.Refresh();
				return _proc.TotalProcessorTime;
			}
			catch
			{
				_cpuUnavailable = true;
				return TimeSpan.Zero;
			}
		}

		private static string ProcessLine()
		{
			int num = ((_gcBase >= 0) ? (GC.CollectionCount(0) - _gcBase) : 0);
			long num2 = GC.GetTotalMemory(forceFullCollection: false) / 1048576;
			string arg = "?";
			if (!_cpuUnavailable && _cpuWallBase >= 0f)
			{
				TimeSpan timeSpan = ReadCpu();
				float num3 = Time.realtimeSinceStartup - _cpuWallBase;
				if (!_cpuUnavailable && num3 > 0.5f)
				{
					arg = ((timeSpan - _cpuBase).TotalSeconds / (double)num3 * 100.0).ToString("F0");
				}
			}
			return $"gc {num} heap {num2}MB cpu {arg}% (process, all threads)";
		}

		public static List<string> BuildReport(bool reset)
		{
			List<string> list = new List<string>();
			float num = Math.Max(0.001f, _frameSum);
			int num2 = 0;
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					if (peer.IsReady())
					{
						num2++;
					}
				}
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append($"stats {num:F0}s | peers {num2} | ");
			if (_frames > 0)
			{
				stringBuilder.Append($"frame avg {_frameSum / (float)_frames * 1000f:F1}ms max {_frameMax * 1000f:F0}ms frames >50ms {_frames50} >100ms {_frames100} | ");
			}
			stringBuilder.Append(ProcessLine()).Append(" | ");
			stringBuilder.Append($"recv zdo {(float)RecvCount / num:F0}/s");
			if (Cfg.PerPrefab.Value && RecvByPrefab.Count > 0)
			{
				stringBuilder.Append(" top: ").Append(TopPrefabs(RecvByPrefab, Cfg.TopN.Value));
			}
			if (Env.StatusNotOk > 0)
			{
				stringBuilder.Append($" | steam-status ok {Env.StatusOk} not-ok {Env.StatusNotOk} last {Env.LastStatusResult}");
			}
			Env.StatusOk = 0;
			Env.StatusNotOk = 0;
			if (SchedulerFrames > 0)
			{
				stringBuilder.Append($" | sched frames {SchedulerFrames} served {SchedulerServed} budget-breaks {SchedulerBudgetBreaks}");
			}
			if (DbPos + DbRot + DbVel + DbBody > 0)
			{
				stringBuilder.Append($" | deadband pos {DbPos} rot {DbRot} vel {DbVel} zero-writes {DbZero} bodyvel {DbBody}");
			}
			if (ItemMergeFeature.Merges > 0)
			{
				stringBuilder.Append($" | merges {ItemMergeFeature.Merges} ({ItemMergeFeature.Absorbed} items)");
				ItemMergeFeature.Merges = 0;
				ItemMergeFeature.Absorbed = 0;
			}
			list.Add(stringBuilder.ToString());
			if (Cfg.OwnerWriteStats.Value && OwnerWritesByPrefab.Count > 0)
			{
				int num3 = 0;
				foreach (int value2 in OwnerWritesByPrefab.Values)
				{
					num3 += value2;
				}
				list.Add($"owner writes {(float)num3 / num:F0}/s top: {TopPrefabs(OwnerWritesByPrefab, Cfg.TopN.Value)}");
			}
			List<long> list2 = new List<long>();
			foreach (PeerStats value3 in Peers.Values)
			{
				if (!((Object)(object)ZNet.instance != (Object)null) || ZNet.instance.GetPeer(value3.Uid) == null)
				{
					if (value3.Rounds == 0)
					{
						list2.Add(value3.Uid);
					}
					if (value3.Rounds == 0)
					{
						continue;
					}
				}
				stringBuilder.Clear();
				stringBuilder.Append($" peer {value3.Name}: rtt {value3.SteamPing}ms app {value3.AppRttMs}ms steam-rate {value3.SteamRate / 1024}KB/s win {value3.Window}");
				if (value3.QueueN > 0)
				{
					stringBuilder.Append($" q avg {(float)(value3.QueueSum / value3.QueueN) / 1024f:F1}K max {(float)value3.QueueMax / 1024f:F1}K inflight max {(float)value3.InflightMax / 1024f:F1}K");
				}
				if (value3.UnsentMax > 0)
				{
					stringBuilder.Append($" unsent max {(float)value3.UnsentMax / 1024f:F1}K");
				}
				stringBuilder.Append($" | rounds {value3.Rounds} refused {value3.Refused}");
				int num4 = value3.Rounds - value3.Refused;
				if (num4 > 0)
				{
					stringBuilder.Append($" cand avg {value3.CandSum / num4} max {value3.CandMax}");
				}
				stringBuilder.Append($" sent {value3.Sent} ({(float)value3.Sent / num:F0}/s) {(float)value3.Bytes / num / 1024f:F1}KB/s deferred {value3.Deferred} throttled {value3.Throttled}");
				if (value3.Rounds > 0)
				{
					stringBuilder.Append($" | send {value3.SendMsSum / (double)value3.Rounds:F2}ms/call max {value3.SendMsMax:F1}ms");
				}
				if (value3.Drains > 0 || value3.DrainAborts > 0)
				{
					stringBuilder.Append($" | drains {value3.Drains} aborted {value3.DrainAborts}");
				}
				if (Cfg.PerPrefab.Value && value3.SentByPrefab.Count > 0)
				{
					stringBuilder.Append(" | top: ").Append(TopPrefabs(value3.SentByPrefab, Cfg.TopN.Value));
				}
				list.Add(stringBuilder.ToString());
			}
			foreach (long item in list2)
			{
				if (Peers.TryGetValue(item, out PeerStats value) && value.Rpc != null)
				{
					ByRpc.Remove(value.Rpc);
				}
				Peers.Remove(item);
			}
			if (reset)
			{
				ResetAll();
			}
			return list;
		}

		public static string TopPrefabs(Dictionary<int, int> d, int n)
		{
			int num = 0;
			foreach (int value in d.Values)
			{
				num += value;
			}
			if (num == 0)
			{
				return "-";
			}
			List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>(d);
			list.Sort((KeyValuePair<int, int> a, KeyValuePair<int, int> b) => b.Value.CompareTo(a.Value));
			StringBuilder stringBuilder = new StringBuilder();
			for (int num2 = 0; num2 < list.Count && num2 < n; num2++)
			{
				if (num2 > 0)
				{
					stringBuilder.Append(' ');
				}
				stringBuilder.Append(PrefabClass.Name(list[num2].Key)).Append(' ').Append((100f * (float)list[num2].Value / (float)num).ToString("F0"))
					.Append('%');
			}
			return stringBuilder.ToString();
		}
	}
	internal static class StatsFile
	{
		private static int _sinceSizeCheck;

		private static bool _failed;

		private static string Path => System.IO.Path.Combine(Paths.BepInExRootPath, "BeanheimNet-stats.log");

		public static void Append(string line)
		{
			if (_failed || !Cfg.StatsFile.Value)
			{
				return;
			}
			try
			{
				if (++_sinceSizeCheck >= 50)
				{
					_sinceSizeCheck = 0;
					FileInfo fileInfo = new FileInfo(Path);
					if (fileInfo.Exists && fileInfo.Length > (long)Cfg.StatsLogMaxMB.Value * 1024L * 1024)
					{
						string text = Path + ".1";
						if (File.Exists(text))
						{
							File.Delete(text);
						}
						File.Move(Path, text);
					}
				}
				File.AppendAllText(Path, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + " " + line + Environment.NewLine);
			}
			catch (Exception ex)
			{
				_failed = true;
				Plugin.Log.LogWarning((object)("stats file disabled: " + ex.Message));
			}
		}

		public static void WriteBootMarker()
		{
			Append("=== boot BeanheimNet 0.2.2 " + (Env.IsHeadless ? "server" : "client") + " | " + Cfg.Summary() + " | refused=" + (Plugin.Refused ? Plugin.RefusedBy : "no"));
		}

		public static void Flush()
		{
		}
	}
	internal sealed class TelemetryFeature : IFeature
	{
		public string Id => "Telemetry";

		public string Description => "per-peer send-path statistics, receive tally, app RTT";

		public bool Apply(Harmony harmony, ManualLogSource log)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			//IL_006d: Expected O, but got Unknown
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Expected O, but got Unknown
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Expected O, but got Unknown
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Expected O, but got Unknown
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Expected O, but got Unknown
			if (!Bindings.ZdoManOk)
			{
				log.LogWarning((object)("[" + Id + "] skipped: " + Bindings.ZdoManReason));
				return false;
			}
			harmony.Patch((MethodBase)Bindings.SendZDOsMi, new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "SendPrefix", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "SendPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Bindings.PatchedMethods.Add(Bindings.SendZDOsMi);
			harmony.Patch((MethodBase)Bindings.DeserializeMi, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "DeserializePostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Bindings.PatchedMethods.Add(Bindings.DeserializeMi);
			if (Bindings.RpcOk)
			{
				harmony.Patch((MethodBase)Bindings.UpdatePingMi, new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "UpdatePingPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)Bindings.ReceivePingMi, new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "ReceivePingPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Bindings.PatchedMethods.Add(Bindings.UpdatePingMi);
				Bindings.PatchedMethods.Add(Bindings.ReceivePingMi);
			}
			else
			{
				log.LogWarning((object)("[" + Id + "] app RTT off: " + Bindings.RpcReason));
			}
			if (Cfg.OwnerWriteStats.Value)
			{
				if (Bindings.IncreaseDataRevisionMi != null)
				{
					harmony.Patch((MethodBase)Bindings.IncreaseDataRevisionMi, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "IncreaseRevisionPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Bindings.PatchedMethods.Add(Bindings.IncreaseDataRevisionMi);
					log.LogInfo((object)("[" + Id + "] owner-write tally installed (hot path; OwnerWriteStats=true at startup)."));
				}
				else
				{
					log.LogWarning((object)("[" + Id + "] OwnerWriteStats requested but ZDO.IncreaseDataRevision not found."));
				}
			}
			log.LogInfo((object)string.Format("[{0}] applying: stats every {1:F0}s, file {2}.", Id, Cfg.IntervalSeconds.Value, Cfg.StatsFile.Value ? "on" : "off"));
			return true;
		}

		private static void SendPrefix(ZDOMan __instance, object peer, bool flush, out PeerStats? __state)
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			__state = null;
			if (!Cfg.Enabled.Value)
			{
				return;
			}
			try
			{
				ZNetPeer val = Bindings.PeerNetPeer.Invoke(peer);
				if (val == null || val.m_socket == null)
				{
					return;
				}
				PeerStats peerStats = Stats.PeerFor(val);
				float time = Time.time;
				int num = -1;
				int num2 = Bindings.UnsentBytes(val.m_socket);
				if (num2 > peerStats.UnsentMax)
				{
					peerStats.UnsentMax = num2;
				}
				if (Env.TryGetStatus(val.m_socket, out var status))
				{
					peerStats.SteamPing = status.m_nPing;
					peerStats.SteamRate = status.m_nSendRateBytesPerSecond;
					peerStats.Inflight = status.m_cbSentUnackedReliable;
					num = status.m_cbPendingReliable + status.m_cbPendingUnreliable + status.m_cbSentUnackedReliable + num2;
				}
				else
				{
					peerStats.SteamPing = 0;
				}
				peerStats.LastQueue = num;
				if (num >= 0)
				{
					peerStats.QueueSum += num;
					peerStats.QueueN++;
					if (num > peerStats.QueueMax)
					{
						peerStats.QueueMax = num;
					}
					if (peerStats.Inflight > peerStats.InflightMax)
					{
						peerStats.InflightMax = peerStats.Inflight;
					}
					SendWindowFeature.ObserveQueue(peerStats, num, time);
				}
				if (!SendWindowFeature.Active)
				{
					peerStats.Window = 10240;
				}
				Bindings.TempToSync.Invoke(__instance).Clear();
				peerStats.CtxSent0 = Bindings.ZdosSent.Invoke(__instance);
				peerStats.CtxData0 = ((val.m_rpc != null) ? Bindings.RpcSentData.Invoke(val.m_rpc) : 0);
				peerStats.CtxT0 = Stopwatch.GetTimestamp();
				peerStats.CtxFlush = flush;
				__state = peerStats;
			}
			catch (Exception ex)
			{
				Stats.LogOnce("telemetry.send-prefix", ex);
			}
		}

		private static void SendPostfix(ZDOMan __instance, bool __result, PeerStats? __state)
		{
			if (__state == null)
			{
				return;
			}
			try
			{
				__state.Rounds++;
				double num = (double)(Stopwatch.GetTimestamp() - __state.CtxT0) * 1000.0 / (double)Stopwatch.Frequency;
				__state.SendMsSum += num;
				if (num > __state.SendMsMax)
				{
					__state.SendMsMax = num;
				}
				int window = __state.Window;
				int currentHeadroom = SendWindowFeature.CurrentHeadroom;
				if (__state.LastQueue >= 0 && ((!__state.CtxFlush && __state.LastQueue > window) || window - __state.LastQueue < currentHeadroom))
				{
					__state.Refused++;
					return;
				}
				int num2 = Bindings.ZdosSent.Invoke(__instance) - __state.CtxSent0;
				int num3 = ((__state.Rpc != null) ? (Bindings.RpcSentData.Invoke(__state.Rpc) - __state.CtxData0) : 0);
				List<ZDO> list = Bindings.TempToSync.Invoke(__instance);
				int count = list.Count;
				__state.CandSum += count;
				if (count > __state.CandMax)
				{
					__state.CandMax = count;
				}
				__state.Sent += num2;
				__state.Bytes += num3;
				if (count > num2)
				{
					__state.Deferred += count - num2;
				}
				if (Cfg.PerPrefab.Value)
				{
					int num4 = Math.Min(num2, count);
					for (int i = 0; i < num4; i++)
					{
						Stats.Inc(__state.SentByPrefab, list[i].GetPrefab());
					}
				}
			}
			catch (Exception ex)
			{
				Stats.LogOnce("telemetry.send-postfix", ex);
			}
		}

		private static void DeserializePostfix(ZDO __instance)
		{
			if (Cfg.Enabled.Value && Cfg.TelemetryEnabled.Value)
			{
				Stats.RecvCount++;
				if (Cfg.PerPrefab.Value)
				{
					Stats.Inc(Stats.RecvByPrefab, __instance.GetPrefab());
				}
			}
		}

		private static void UpdatePingPrefix(ZRpc __instance, float dt)
		{
			try
			{
				if (!(Bindings.RpcPingTimer.Invoke(__instance) + dt <= Bindings.PingInterval))
				{
					PeerStats peerStats = Stats.ByRpcOrNull(__instance);
					if (peerStats != null)
					{
						peerStats.PingSentAt = Time.realtimeSinceStartup;
					}
				}
			}
			catch (Exception ex)
			{
				Stats.LogOnce("telemetry.ping", ex);
			}
		}

		private static void ReceivePingPrefix(ZRpc __instance, ZPackage package)
		{
			try
			{
				int pos = package.GetPos();
				bool flag;
				try
				{
					flag = package.ReadBool();
				}
				finally
				{
					package.SetPos(pos);
				}
				if (!flag)
				{
					PeerStats peerStats = Stats.ByRpcOrNull(__instance);
					if (peerStats != null && peerStats.PingSentAt > 0f)
					{
						peerStats.AppRttMs = (int)((Time.realtimeSinceStartup - peerStats.PingSentAt) * 1000f);
						peerStats.PingSentAt = 0f;
					}
				}
			}
			catch (Exception ex)
			{
				Stats.LogOnce("telemetry.pong", ex);
			}
		}

		private static void IncreaseRevisionPostfix(ZDO __instance)
		{
			if (Cfg.Enabled.Value && Cfg.OwnerWriteStats.Value)
			{
				Stats.Inc(Stats.OwnerWritesByPrefab, __instance.GetPrefab());
			}
		}
	}
}
namespace BeanheimNet.Features
{
	internal sealed class CommandsFeature : IFeature
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static Action <0>__RunTriggers;

			public static Action<string> <1>__Say;

			public static ConsoleEvent <2>__RunConsole;
		}

		public string Id => "Commands";

		public string Description => "bnet console command and cfg triggers";

		public bool Apply(Harmony harmony, ManualLogSource log)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Expected O, but got Unknown
			Cfg.Triggers += RunTriggers;
			if (Bindings.InitTerminalMi == null)
			{
				log.LogWarning((object)("[" + Id + "] cfg triggers only: Terminal.InitTerminal not found."));
				return true;
			}
			harmony.Patch((MethodBase)Bindings.InitTerminalMi, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CommandsFeature), "RegisterCommands", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(Terminal), "m_terminalInitialized");
				bool flag = default(bool);
				int num;
				if (fieldInfo != null)
				{
					object value = fieldInfo.GetValue(null);
					if (value is bool)
					{
						flag = (bool)value;
						num = 1;
					}
					else
					{
						num = 0;
					}
				}
				else
				{
					num = 0;
				}
				if (((uint)num & (flag ? 1u : 0u)) != 0)
				{
					RegisterCommands();
				}
			}
			catch (Exception ex)
			{
				log.LogDebug((object)("[" + Id + "] terminal state check failed: " + ex.Message));
			}
			log.LogInfo((object)("[" + Id + "] applying: 'bnet help' in the in-game console; [11 Triggers] in the cfg for the dedicated server."));
			return true;
		}

		private static void RunTriggers()
		{
			if (Cfg.TriggerCensus.Value)
			{
				Cfg.ResetTrigger(Cfg.TriggerCensus);
				Run("census", "", Say);
			}
			if (Cfg.TriggerItemReport.Value)
			{
				Cfg.ResetTrigger(Cfg.TriggerItemReport);
				Run("items", "", Say);
			}
			if (Cfg.TriggerItemCleanupOnce.Value)
			{
				Cfg.ResetTrigger(Cfg.TriggerItemCleanupOnce);
				Run("cleanup", "run", Say);
			}
			if (Cfg.TriggerStats.Value)
			{
				Cfg.ResetTrigger(Cfg.TriggerStats);
				Run("stats", "", Say);
			}
		}

		private static void Say(string line)
		{
			Plugin.Log.LogInfo((object)line);
		}

		private static void RegisterCommands()
		{
			//IL_002f: 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_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			try
			{
				object obj = <>O.<2>__RunConsole;
				if (obj == null)
				{
					ConsoleEvent val = RunConsole;
					<>O.<2>__RunConsole = val;
					obj = (object)val;
				}
				new ConsoleCommand("bnet", "BeanheimNet: bnet stats | census | items | cleanup dryrun|run | reload | config", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			}
			catch (Exception ex)
			{
				Stats.LogOnce("commands", ex);
			}
		}

		private static void RunConsole(ConsoleEventArgs args)
		{
			string sub = ((args.Args != null && args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "help");
			string arg = ((args.Args != null && args.Args.Length > 2) ? args.Args[2].ToLowerInvariant() : "");
			Run(sub, arg, delegate(string line)
			{
				Plugin.Log.LogInfo((object)line);
				try
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString(line);
					}
				}
				catch
				{
				}
			});
		}

		public static void Run(string sub, string arg, Action<string> say)
		{
			switch (sub)
			{
			case "stats":
			{
				foreach (string item in Stats.BuildReport(reset: false))
				{
					say(item);
				}
				break;
			}
			case "census":
				say(Census.Start("command") ? "census started; results follow in the log within a few seconds." : "census not started (already running, or ZDOMan/bindings unavailable).");
				break;
			case "items":
				say(ItemSweepFeature.RunNow(cleanup: false, "command") ? "item sweep started (report only); results follow in the log." : "sweep not started (already running, or not the server).");
				break;
			case "cleanup":
				if (arg == "run")
				{
					say(ItemSweepFeature.RunNow(cleanup: true, "command") ? "item CLEANUP sweep started with the configured criteria; every deletion is logged." : "sweep not started (already running, or not the server).");
				}
				else
				{
					say(ItemSweepFeature.RunNow(cleanup: false, "command dryrun") ? "dry run started; candidates are counted, nothing is deleted." : "sweep not started (already running, or not the server).");
				}
				break;
			case "reload":
				Cfg.ForceReload();
				say("config: " + Cfg.Summary());
				break;
			case "config":
				say("config: " + Cfg.Summary());
				break;
			default:
				say("bnet stats | census | items | cleanup dryrun|run | reload | config");
				break;
			}
		}
	}
	internal sealed class DeadbandFeature : IFeature
	{
		private sealed class State
		{
			public bool HasPos;

			public bool HasRot;

			public bool HasVel;

			public bool HasBody;

			public bool RestoreBodyFlag;

			public bool ShipChecked;

			public Ship? Ship;

			public Vector3 LastPos;

			public Vector3 LastVel;

			public Vector3 LastBodyVel;

			public Vector3 LastBodyAVel;

			public Quaternion LastRot;

			public void Reset()
			{
				HasPos = (HasRot = (HasVel = (HasBody = false)));
			}
		}

		private static readonly ConditionalWeakTable<ZSyncTransform, State> Table = new ConditionalWeakTable<ZSyncTransform, State>();

		private static readonly ConditionalWeakTable<ZSyncTransform, State>.CreateValueCallback Create = (ZSyncTransform _) => new State();

		public string Id => "Deadband";

		public string Description => "owner-side position/rotation/velocity write deadbands";

		public bool Apply(Harmony harmony, ManualLogSource log)
		{
			//IL_0088: 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_00b0: Expected O, but got Unknown
			//IL_00b0: Expected O, but got Unknown
			if (Plugin.Refused)
			{
				log.LogWarning((object)("[" + Id + "] skipped: refused (" + Plugin.RefusedBy + ")."));
				return false;
			}
			if (!Bindings.SyncTransformOk)
			{
				log.LogWarning((object)("[" + Id + "] skipped: " + Bindings.SyncTransformReason));
				return false;
			}
			harmony.Patch((MethodBase)Bindings.OwnerSyncMi, new HarmonyMethod(AccessTools.Method(typeof(DeadbandFeature), "Prefix", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.Method(typeof(DeadbandFeature), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Bindings.PatchedMethods.Add(Bindings.OwnerSyncMi);
			log.LogInfo((object)("[" + Id + "] applying: " + (Cfg.F6Enabled.Value ? $"item {Cfg.ItemPosMeters.Value}m/{Cfg.ItemRotDegrees.Value}deg, creature {Cfg.CreaturePosMeters.Value}m/{Cfg.CreatureRotDegrees.Value}deg, vel {Cfg.VelocityMps.Value}m/s" : "off until enabled") + (Bindings.ShipOk ? $"; moored ship {Cfg.MooredShipPosMeters.Value}m/{Cfg.MooredShipRotDegrees.Value}deg" : ("; moored-ship deadband off (" + Bindings.ShipReason + ")")) + "."));
			return true;
		}

		private static void Prefix(ZSyncTransform __instance, out State? __state)
		{
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Invalid comparison between Unknown and I4
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0300: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: 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_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0392: Unknown result type (might be due to invalid IL or missing references)
			//IL_0397: Unknown result type (might be due to invalid IL or missing references)
			//IL_039b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0305: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0403: Unknown result type (might be due to invalid IL or missing references)
			//IL_0405: Unknown result type (might be due to invalid IL or missing references)
			//IL_03aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0320: Unknown result type (might be due to invalid IL or missing references)
			//IL_028c: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_0299: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0277: Unknown result type (might be due to invalid IL or missing references)
			//IL_0279: Unknown result type (might be due to invalid IL or missing references)
			//IL_024b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0256: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_036a: Unknown result type (might be due to invalid IL or missing references)
			//IL_036c: Unknown result type (might be due to invalid IL or missing references)
			//IL_033e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0341: Unknown result type (might be due to invalid IL or missing references)
			__state = null;
			if (!Cfg.Enabled.Value || !Cfg.F6Enabled.Value)
			{
				return;
			}
			try
			{
				ZNetView val = Bindings.StNview.Invoke(__instance);
				ZDO val2 = (((Object)(object)val != (Object)null) ? val.GetZDO() : null);
				if (val2 == null || !val2.IsOwner())
				{
					return;
				}
				State value = Table.GetValue(__instance, Create);
				if (value.RestoreBodyFlag)
				{
					__instance.m_syncBodyVelocity = true;
					value.RestoreBodyFlag = false;
				}
				if (!Bindings.StWasOwner.Invoke(__instance))
				{