Decompiled source of BeanheimNet v0.3.1

BeanheimNet.dll

Decompiled a month 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 System.Threading;
using BeanheimNet.Features;
using BeanheimNet.Telemetry;
using BeanheimNet.Telemetry.Pure;
using BeanheimNet.Util;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;
using UnityEngine.Scripting;

[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+419851e0191e3da168c130229369fdfe12f5c2a6")]
[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 ZdoPeerOk;

		public static bool SendOk;

		public static bool LapOk;

		public static bool SyncListOk;

		public static bool SortOk;

		public static bool DeserializeOk;

		public static bool ObjectsOk;

		public static string ZdoPeerReason = "";

		public static string SendReason = "";

		public static string LapReason = "";

		public static string SyncListReason = "";

		public static string SortReason = "";

		public static string DeserializeReason = "";

		public static string ObjectsReason = "";

		public static bool PeerInfoOk;

		public static bool SyncTransformOk;

		public static bool RpcOk;

		public static bool SteamSocketOk;

		public static string PeerInfoReason = "";

		public static string SyncTransformReason = "";

		public static string RpcReason = "";

		public static string SteamSocketReason = "";

		public static Type ZdoPeerType = null;

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

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

		public static MethodInfo SendZDOsMi = null;

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

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

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

		public static MethodInfo SendZDOToPeers2Mi = null;

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

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

		public static MethodInfo CreateSyncListMi = null;

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

		public static MethodInfo ServerSortMi = null;

		public static MethodInfo DeserializeMi = null;

		public static MethodInfo? IncreaseDataRevisionMi;

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

		public static Type PeerInfoType = null;

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

		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 bool OwnerOk;

		public static string OwnerReason = "";

		public static MethodInfo SetOwnerInternalMi = null;

		public static bool HelmOk;

		public static string HelmReason = "";

		public static int HelmUserHash;

		public static int PlayerIdHash;

		public static bool SectorOk;

		public static string SectorReason = "";

		public static MethodInfo InternalSetPositionMi = null;

		public static bool RoutedOk;

		public static string RoutedReason = "";

		public static MethodInfo RouteRPCMi = null;

		public static MethodInfo HandleRoutedRPCMi = null;

		public static FieldRef<ZRoutedRpc, long> RoutedId = null;

		public static FieldRef<ZRoutedRpc, List<ZNetPeer>> RoutedPeers = null;

		public static bool ClientSyncOk;

		public static string ClientSyncReason = "";

		public static MethodInfo SyncPositionMi = null;

		public static MethodInfo ClientSyncMi = null;

		public static FieldRef<ZSyncTransform, float> CsTargetPosTimer = null;

		public static FieldRef<ZSyncTransform, uint> CsPosRevision = null;

		public static FieldRef<ZSyncTransform, int> CsLastUpdateFrame = null;

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

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

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

		public static bool NetTimeOk;

		public static string NetTimeReason = "";

		public static MethodInfo RpcNetTimeMi = null;

		public static FieldRef<ZNet, double> NetTimeValue = null;

		public static bool InstantiateOk;

		public static string InstantiateReason = "";

		public static MethodInfo CreateObjectsSortedMi = null;

		public static MethodInfo CreateObjectMi = null;

		public static FieldRef<ZNetScene, List<ZDO>> TempCurrentObjects2 = null;

		public static bool ZdoDataOk;

		public static string ZdoDataReason = "";

		public static MethodInfo RpcZdoDataMi = null;

		public static bool ProfTargetsOk;

		public static string ProfTargetsReason = "";

		public static readonly List<(MethodBase Method, string Label)> ProfTargets = new List<(MethodBase, string)>();

		public static bool ReflTargetsOk;

		public static string ReflTargetsReason = "";

		public static readonly List<(MethodBase Method, string Label)> ReflTargets = new List<(MethodBase, string)>();

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

		private const BindingFlags Declared = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly string[] PluginUpdateNames = new string[3] { "Update", "FixedUpdate", "LateUpdate" };

		private static readonly (string Type, string Method)[] ReflEntryPoints = new(string, string)[9]
		{
			("System.RuntimeType", "GetMethodsByName"),
			("System.RuntimeType", "GetPropertiesByName"),
			("System.RuntimeType", "GetFields_internal"),
			("System.MonoCustomAttrs", "GetCustomAttributesBase"),
			("System.MonoCustomAttrs", "IsDefined"),
			("System.RuntimeTypeHandle", "GetTypeByName"),
			("System.Reflection.RuntimeMethodInfo", "Invoke"),
			("System.Reflection.RuntimeFieldInfo", "GetValue"),
			("System.Reflection.RuntimeFieldInfo", "SetValue")
		};

		public static bool ZdoManOk
		{
			get
			{
				if (ZdoPeerOk && SendOk && LapOk && SyncListOk && SortOk && DeserializeOk)
				{
					return ObjectsOk;
				}
				return false;
			}
		}

		public static string ZdoManReason => FirstFailed(ZdoPeerReason, SendReason, LapReason, SyncListReason, SortReason, DeserializeReason, ObjectsReason);

		public static void Init(ManualLogSource log)
		{
			ZdoPeerOk = Try(BindZdoPeer, out ZdoPeerReason);
			SendOk = After(ZdoPeerOk, "ZdoPeer", BindSend, out SendReason);
			LapOk = Try(BindLap, out LapReason);
			SyncListOk = After(ZdoPeerOk, "ZdoPeer", BindSyncList, out SyncListReason);
			SortOk = After(ZdoPeerOk, "ZdoPeer", BindSort, out SortReason);
			DeserializeOk = Try(BindDeserialize, out DeserializeReason);
			ObjectsOk = Try(BindObjects, out ObjectsReason);
			PeerInfoOk = After(ZdoPeerOk, "ZdoPeer", 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);
			OwnerOk = Try(BindOwner, out OwnerReason);
			HelmOk = Try(BindHelm, out HelmReason);
			SectorOk = Try(BindSector, out SectorReason);
			RoutedOk = Try(BindRouted, out RoutedReason);
			ClientSyncOk = Try(BindClientSync, out ClientSyncReason);
			NetTimeOk = Try(BindNetTime, out NetTimeReason);
			InstantiateOk = Try(BindInstantiate, out InstantiateReason);
			ZdoDataOk = Try(BindZdoData, out ZdoDataReason);
			ProfTargetsOk = Try(BindProfTargets, out ProfTargetsReason);
			ReflTargetsOk = Try(BindReflTargets, out ReflTargetsReason);
			Report(log, "ZdoPeer", ZdoPeerOk, ZdoPeerReason);
			Report(log, "Send", SendOk, SendReason);
			Report(log, "Lap", LapOk, LapReason);
			Report(log, "SyncList", SyncListOk, SyncListReason);
			Report(log, "Sort", SortOk, SortReason);
			Report(log, "Deserialize", DeserializeOk, DeserializeReason);
			Report(log, "Objects", ObjectsOk, ObjectsReason);
			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);
			Report(log, "Owner", OwnerOk, OwnerReason);
			Report(log, "Helm", HelmOk, HelmReason);
			Report(log, "Sector", SectorOk, SectorReason);
			Report(log, "Routed", RoutedOk, RoutedReason);
			Report(log, "ClientSync", ClientSyncOk, ClientSyncReason);
			Report(log, "NetTime", NetTimeOk, NetTimeReason);
			Report(log, "Instantiate", InstantiateOk, InstantiateReason);
			Report(log, "ZdoData", ZdoDataOk, ZdoDataReason);
			Report(log, "ProfTargets", ProfTargetsOk, ProfTargetsReason);
			Report(log, "ReflTargets", ReflTargetsOk, ReflTargetsReason);
			if (ProfTargetsOk)
			{
				log.LogInfo((object)$"[prof-targets] {ProfTargets.Count} methods: {Labels(ProfTargets)}");
			}
		}

		public static string FirstFailed(params string[] reasons)
		{
			foreach (string text in reasons)
			{
				if (!string.IsNullOrEmpty(text))
				{
					return text;
				}
			}
			return "";
		}

		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 bool After(bool dependencyOk, string dependency, Action bind, out string reason)
		{
			if (!dependencyOk)
			{
				reason = "needs the " + dependency + " group, which failed";
				return false;
			}
			return Try(bind, out reason);
		}

		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 NeedParams(MethodInfo mi, string what, params string[] names)
		{
			ParameterInfo[] parameters = mi.GetParameters();
			for (int i = 0; i < names.Length; i++)
			{
				if (parameters.Length <= i || parameters[i].Name != names[i])
				{
					throw new MissingMemberException($"{what}: parameter {i} is no longer named '{names[i]}'");
				}
			}
		}

		private static void NeedField(Type type, string name, Type fieldType, bool isStatic = false)
		{
			FieldInfo fieldInfo = Need(AccessTools.Field(type, name), type.Name + "." + name);
			if (fieldInfo.FieldType != fieldType || fieldInfo.IsStatic != isStatic)
			{
				throw new MissingMemberException(type.Name + "." + name + " changed type");
			}
		}

		private static void BindZdoPeer()
		{
			ZdoPeerType = Need(AccessTools.Inner(typeof(ZDOMan), "ZDOPeer"), "ZDOMan.ZDOPeer");
			PeerNetPeer = AccessTools.FieldRefAccess<ZNetPeer>(ZdoPeerType, "m_peer");
			Peers = AccessTools.FieldRefAccess<IList>(typeof(ZDOMan), "m_peers");
		}

		private static void BindSend()
		{
			SendZDOsMi = Need(AccessTools.Method(typeof(ZDOMan), "SendZDOs", new Type[2]
			{
				ZdoPeerType,
				typeof(bool)
			}, (Type[])null), "ZDOMan.SendZDOs(ZDOPeer,bool)");
			NeedParams(SendZDOsMi, "ZDOMan.SendZDOs", "peer", "flush");
			TempToSync = AccessTools.FieldRefAccess<ZDOMan, List<ZDO>>("m_tempToSync");
			ZdosSent = AccessTools.FieldRefAccess<ZDOMan, int>("m_zdosSent");
			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 BindLap()
		{
			SendZDOToPeers2Mi = Need(AccessTools.Method(typeof(ZDOMan), "SendZDOToPeers2", new Type[1] { typeof(float) }, (Type[])null), "ZDOMan.SendZDOToPeers2(float)");
			SendTimer = AccessTools.FieldRefAccess<ZDOMan, float>("m_sendTimer");
			NextSendPeer = AccessTools.FieldRefAccess<ZDOMan, int>("m_nextSendPeer");
		}

		private static void BindSyncList()
		{
			CreateSyncListMi = Need(AccessTools.Method(typeof(ZDOMan), "CreateSyncList", new Type[2]
			{
				ZdoPeerType,
				typeof(List<ZDO>)
			}, (Type[])null), "ZDOMan.CreateSyncList(ZDOPeer,List<ZDO>)");
			PeerForceSend = AccessTools.FieldRefAccess<HashSet<ZDOID>>(ZdoPeerType, "m_forceSend");
		}

		private static void BindSort()
		{
			ServerSortMi = Need(AccessTools.Method(typeof(ZDOMan), "ServerSortSendZDOS", new Type[3]
			{
				typeof(List<ZDO>),
				typeof(Vector3),
				ZdoPeerType
			}, (Type[])null), "ZDOMan.ServerSortSendZDOS");
		}

		private static void BindDeserialize()
		{
			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);
		}

		private static void BindObjects()
		{
			ObjectsById = AccessTools.FieldRefAccess<ZDOMan, Dictionary<ZDOID, ZDO>>("m_objectsByID");
		}

		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");
		}

		private static void BindOwner()
		{
			SetOwnerInternalMi = Need(typeof(ZDO).GetMethod("SetOwnerInternal", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(long) }, null), "ZDO.SetOwnerInternal(long)");
			NeedParams(SetOwnerInternalMi, "ZDO.SetOwnerInternal", "uid");
		}

		private static void BindHelm()
		{
			HelmUserHash = ZdoVar("s_user", "user");
			PlayerIdHash = ZdoVar("s_playerID", "playerID");
			NeedField(typeof(ZNetPeer), "m_characterID", typeof(ZDOID));
		}

		private static int ZdoVar(string field, string name)
		{
			FieldInfo fieldInfo = Need(AccessTools.Field(typeof(ZDOVars), field), "ZDOVars." + field);
			if (!fieldInfo.IsStatic || fieldInfo.FieldType != typeof(int))
			{
				throw new MissingMemberException("ZDOVars." + field + " changed type");
			}
			int num = (int)fieldInfo.GetValue(null);
			if (num != StringExtensionMethods.GetStableHashCode(name))
			{
				throw new MissingMemberException("ZDOVars." + field + " no longer hashes \"" + name + "\"");
			}
			return num;
		}

		private static void BindSector()
		{
			InternalSetPositionMi = Need(typeof(ZDO).GetMethod("InternalSetPosition", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Vector3) }, null), "ZDO.InternalSetPosition(Vector3)");
			NeedParams(InternalSetPositionMi, "ZDO.InternalSetPosition", "pos");
			Need(typeof(ZDO).GetMethod("GetSectorIndex", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null), "ZDO.GetSectorIndex()");
			if (Need(typeof(ZoneSystem).GetMethod("GetSectorIndex", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Vector3) }, null), "ZoneSystem.GetSectorIndex(Vector3)").ReturnType != typeof(SectorIndex))
			{
				throw new MissingMemberException("ZoneSystem.GetSectorIndex no longer returns SectorIndex");
			}
			Need(typeof(ZNetScene).GetMethod("InActiveArea", BindingFlags.Static | BindingFlags.Public, null, new Type[2]
			{
				typeof(Vector3),
				typeof(Vector3)
			}, null), "ZNetScene.InActiveArea(Vector3,Vector3)");
			NeedField(typeof(ZNetPeer), "m_simulationDistance", typeof(SimulationDistance));
			Need(typeof(SimulationDistance).GetProperty("NearSimulationDistance", BindingFlags.Instance | BindingFlags.Public), "SimulationDistance.NearSimulationDistance");
			Need(typeof(SimulationDistance).GetProperty("IsClassic", BindingFlags.Instance | BindingFlags.Public), "SimulationDistance.IsClassic");
			Need(typeof(ZoneSystem).GetMethod("ZonesWithinRadius", BindingFlags.Instance | BindingFlags.Public, null, new Type[4]
			{
				typeof(Vector2s),
				typeof(Vector2s),
				typeof(int),
				typeof(bool)
			}, null), "ZoneSystem.ZonesWithinRadius(Vector2s,Vector2s,int,bool)");
			Need(typeof(ZoneSystem).GetMethod("GetZone", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Vector3) }, null), "ZoneSystem.GetZone(Vector3)");
			Need(typeof(ZDO).GetProperty("Distant", BindingFlags.Instance | BindingFlags.Public), "ZDO.Distant");
		}

		private static void BindRouted()
		{
			Type typeFromHandle = typeof(RoutedRPCData);
			RouteRPCMi = Need(typeof(ZRoutedRpc).GetMethod("RouteRPC", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeFromHandle }, null), "ZRoutedRpc.RouteRPC(RoutedRPCData)");
			NeedParams(RouteRPCMi, "ZRoutedRpc.RouteRPC", "rpcData");
			HandleRoutedRPCMi = Need(typeof(ZRoutedRpc).GetMethod("HandleRoutedRPC", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeFromHandle }, null), "ZRoutedRpc.HandleRoutedRPC(RoutedRPCData)");
			NeedParams(HandleRoutedRPCMi, "ZRoutedRpc.HandleRoutedRPC", "data");
			RoutedId = AccessTools.FieldRefAccess<ZRoutedRpc, long>("m_id");
			RoutedPeers = AccessTools.FieldRefAccess<ZRoutedRpc, List<ZNetPeer>>("m_peers");
			NeedField(typeFromHandle, "m_senderPeerID", typeof(long));
			NeedField(typeFromHandle, "m_targetPeerID", typeof(long));
			NeedField(typeFromHandle, "m_targetZDO", typeof(ZDOID));
			NeedField(typeFromHandle, "m_methodHash", typeof(int));
			Need(typeof(ZNetPeer).GetMethod("IsReady", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null), "ZNetPeer.IsReady()");
		}

		private static void BindClientSync()
		{
			SyncPositionMi = Need(typeof(ZSyncTransform).GetMethod("SyncPosition", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3]
			{
				typeof(ZDO),
				typeof(float),
				typeof(bool).MakeByRefType()
			}, null), "ZSyncTransform.SyncPosition(ZDO,float,out bool)");
			NeedParams(SyncPositionMi, "ZSyncTransform.SyncPosition", "zdo", "dt", "usedLocalRotation");
			ClientSyncMi = Need(typeof(ZSyncTransform).GetMethod("ClientSync", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(float) }, null), "ZSyncTransform.ClientSync(float)");
			NeedParams(ClientSyncMi, "ZSyncTransform.ClientSync", "dt");
			CsTargetPosTimer = AccessTools.FieldRefAccess<ZSyncTransform, float>("m_targetPosTimer");
			CsPosRevision = AccessTools.FieldRefAccess<ZSyncTransform, uint>("m_posRevision");
			CsLastUpdateFrame = AccessTools.FieldRefAccess<ZSyncTransform, int>("m_lastUpdateFrame");
			CsIsKinematicBody = AccessTools.FieldRefAccess<ZSyncTransform, bool>("m_isKinematicBody");
			CsNview = AccessTools.FieldRefAccess<ZSyncTransform, ZNetView>("m_nview");
			CsBody = AccessTools.FieldRefAccess<ZSyncTransform, Rigidbody>("m_body");
		}

		private static void BindNetTime()
		{
			RpcNetTimeMi = Need(typeof(ZNet).GetMethod("RPC_NetTime", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
			{
				typeof(ZRpc),
				typeof(double)
			}, null), "ZNet.RPC_NetTime(ZRpc,double)");
			NeedParams(RpcNetTimeMi, "ZNet.RPC_NetTime", "rpc", "time");
			NetTimeValue = AccessTools.FieldRefAccess<ZNet, double>("m_netTime");
		}

		private static void BindInstantiate()
		{
			CreateObjectsSortedMi = Need(typeof(ZNetScene).GetMethod("CreateObjectsSorted", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3]
			{
				typeof(List<ZDO>),
				typeof(int),
				typeof(int).MakeByRefType()
			}, null), "ZNetScene.CreateObjectsSorted(List<ZDO>,int,ref int)");
			NeedParams(CreateObjectsSortedMi, "ZNetScene.CreateObjectsSorted", "currentNearObjects", "maxCreatedPerFrame", "created");
			CreateObjectMi = Need(typeof(ZNetScene).GetMethod("CreateObject", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(ZDO) }, null), "ZNetScene.CreateObject(ZDO)");
			if (CreateObjectMi.ReturnType != typeof(GameObject))
			{
				throw new MissingMemberException("ZNetScene.CreateObject(ZDO) no longer returns GameObject");
			}
			TempCurrentObjects2 = AccessTools.FieldRefAccess<ZNetScene, List<ZDO>>("m_tempCurrentObjects2");
		}

		private static void BindZdoData()
		{
			RpcZdoDataMi = Need(typeof(ZDOMan).GetMethod("RPC_ZDOData", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
			{
				typeof(ZRpc),
				typeof(ZPackage)
			}, null), "ZDOMan.RPC_ZDOData(ZRpc,ZPackage)");
			NeedParams(RpcZdoDataMi, "ZDOMan.RPC_ZDOData", "rpc", "pkg");
		}

		private static void BindProfTargets()
		{
			(Type, string, Type[])[] obj = new(Type, string, Type[])[10]
			{
				(typeof(ZNet), "Update", Type.EmptyTypes),
				(typeof(ZDOMan), "Update", new Type[1] { typeof(float) }),
				(typeof(ZoneSystem), "Update", Type.EmptyTypes),
				(typeof(ZNetScene), "Update", Type.EmptyTypes),
				(typeof(SteamManager), "Update", Type.EmptyTypes),
				(typeof(MonoUpdaters), "Update", Type.EmptyTypes),
				(typeof(MonoUpdaters), "FixedUpdate", Type.EmptyTypes),
				(typeof(MonoUpdaters), "LateUpdate", Type.EmptyTypes),
				(typeof(Game), "Update", Type.EmptyTypes),
				(typeof(RandEventSystem), "FixedUpdate", Type.EmptyTypes)
			};
			ProfTargets.Clear();
			List<string> list = new List<string>();
			(Type, string, Type[])[] array = obj;
			for (int i = 0; i < array.Length; i++)
			{
				(Type, string, Type[]) tuple = array[i];
				Type item = tuple.Item1;
				string item2 = tuple.Item2;
				Type[] item3 = tuple.Item3;
				MethodInfo method = item.GetMethod(item2, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, item3, null);
				if (method == null || method.IsAbstract)
				{
					list.Add(item.Name + "." + item2);
				}
				else
				{
					ProfTargets.Add((method, item.Name + "." + item2));
				}
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (var profTarget in ProfTargets)
			{
				string item4 = profTarget.Label;
				hashSet.Add(item4);
			}
			foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
			{
				string key = pluginInfo.Key;
				if (key.StartsWith("helix.beanheim.net.", StringComparison.Ordinal))
				{
					continue;
				}
				try
				{
					BaseUnityPlugin instance = pluginInfo.Value.Instance;
					if ((Object)(object)instance == (Object)null)
					{
						continue;
					}
					Type type = ((object)instance).GetType();
					string text = PluginLabel(pluginInfo.Value.Metadata.Name, key);
					string[] pluginUpdateNames = PluginUpdateNames;
					foreach (string text2 in pluginUpdateNames)
					{
						MethodInfo method2 = type.GetMethod(text2, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
						if (!(method2 == null) && !method2.IsAbstract && !method2.ContainsGenericParameters)
						{
							string text3 = text + "." + text2;
							int num = 2;
							while (!hashSet.Add(text3))
							{
								text3 = text + "_" + num + "." + text2;
								num++;
							}
							ProfTargets.Add((method2, text3));
						}
					}
				}
				catch
				{
				}
			}
			if (list.Count > 0)
			{
				throw new MissingMemberException("vanilla update method(s) not found: " + string.Join(", ", list.ToArray()));
			}
		}

		private static string PluginLabel(string name, string guid)
		{
			StringBuilder stringBuilder = new StringBuilder();
			string text = name ?? "";
			foreach (char c in text)
			{
				bool flag;
				switch (c)
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case 'A':
				case 'B':
				case 'C':
				case 'D':
				case 'E':
				case 'F':
				case 'G':
				case 'H':
				case 'I':
				case 'J':
				case 'K':
				case 'L':
				case 'M':
				case 'N':
				case 'O':
				case 'P':
				case 'Q':
				case 'R':
				case 'S':
				case 'T':
				case 'U':
				case 'V':
				case 'W':
				case 'X':
				case 'Y':
				case 'Z':
				case '_':
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
				case 'g':
				case 'h':
				case 'i':
				case 'j':
				case 'k':
				case 'l':
				case 'm':
				case 'n':
				case 'o':
				case 'p':
				case 'q':
				case 'r':
				case 's':
				case 't':
				case 'u':
				case 'v':
				case 'w':
				case 'x':
				case 'y':
				case 'z':
					flag = true;
					break;
				default:
					flag = false;
					break;
				}
				if (flag)
				{
					stringBuilder.Append(c);
				}
			}
			if (stringBuilder.Length == 0)
			{
				text = guid;
				foreach (char c2 in text)
				{
					bool flag;
					switch (c2)
					{
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
					case 'A':
					case 'B':
					case 'C':
					case 'D':
					case 'E':
					case 'F':
					case 'G':
					case 'H':
					case 'I':
					case 'J':
					case 'K':
					case 'L':
					case 'M':
					case 'N':
					case 'O':
					case 'P':
					case 'Q':
					case 'R':
					case 'S':
					case 'T':
					case 'U':
					case 'V':
					case 'W':
					case 'X':
					case 'Y':
					case 'Z':
					case '_':
					case 'a':
					case 'b':
					case 'c':
					case 'd':
					case 'e':
					case 'f':
					case 'g':
					case 'h':
					case 'i':
					case 'j':
					case 'k':
					case 'l':
					case 'm':
					case 'n':
					case 'o':
					case 'p':
					case 'q':
					case 'r':
					case 's':
					case 't':
					case 'u':
					case 'v':
					case 'w':
					case 'x':
					case 'y':
					case 'z':
						flag = true;
						break;
					default:
						flag = false;
						break;
					}
					if (flag)
					{
						stringBuilder.Append(c2);
					}
					else if (c2 == '.')
					{
						stringBuilder.Append('_');
					}
				}
			}
			if (stringBuilder.Length <= 0)
			{
				return "plugin";
			}
			return stringBuilder.ToString();
		}

		private static void BindReflTargets()
		{
			ReflTargets.Clear();
			Assembly assembly = typeof(object).Assembly;
			List<string> list = new List<string>();
			(string, string)[] reflEntryPoints = ReflEntryPoints;
			for (int i = 0; i < reflEntryPoints.Length; i++)
			{
				(string, string) tuple = reflEntryPoints[i];
				string item = tuple.Item1;
				string item2 = tuple.Item2;
				Type type = assembly.GetType(item);
				int num = 0;
				if (type != null)
				{
					MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (MethodInfo methodInfo in methods)
					{
						if (!(methodInfo.Name != item2) && !methodInfo.IsAbstract && (methodInfo.GetMethodImplementationFlags() & MethodImplAttributes.InternalCall) == 0)
						{
							ReflTargets.Add((methodInfo, type.Name + "." + item2));
							num++;
						}
					}
				}
				if (num == 0)
				{
					list.Add(item + "." + item2);
				}
			}
			if (list.Count > 0)
			{
				throw new MissingMemberException("reflection entry point(s) not found: " + string.Join(", ", list.ToArray()));
			}
		}

		private static string Labels(List<(MethodBase Method, string Label)> list)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (var item2 in list)
			{
				string item = item2.Label;
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(item);
			}
			return stringBuilder.ToString();
		}

		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 sealed class Migration
		{
			public readonly string Target;

			public readonly List<string> Kept = new List<string>();

			public readonly List<string> Conflicts = new List<string>();

			public int Default;

			public int Superseded;

			public int Count => Kept.Count + Default + Superseded;

			public Migration(string target)
			{
				Target = target;
			}
		}

		private readonly struct Moved
		{
			public readonly ConfigDefinition Old;

			public readonly ConfigDefinition New;

			public Moved(ConfigDefinition old, ConfigDefinition now)
			{
				Old = old;
				New = now;
			}
		}

		private static ConfigFile _cfg = null;

		public const string Advanced = "99 Advanced";

		public const string General = "01 General";

		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> PeerExtras = null;

		public static ConfigEntry<bool> ClientExtras = null;

		public static ConfigEntry<bool> DesyncCounters = 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 ConfigEntry<bool> Profiler = null;

		public static ConfigEntry<bool> ReflectionSampler = null;

		public static ConfigEntry<int> GcTimeSliceMs = 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;

		private static Dictionary<ConfigDefinition, string>? _orphans;

		private static readonly List<Moved> MovedKeys = new List<Moved>();

		private static readonly Migration ToAdvanced = new Migration("99 Advanced");

		private static readonly Migration ToGeneral = new Migration("01 General");

		public static event Action? Changed;

		public static event Action? Triggers;

		public static void Init(ConfigFile cfg)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Expected O, but got Unknown
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Expected O, but got Unknown
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Expected O, but got Unknown
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Expected O, but got Unknown
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Expected O, but got Unknown
			//IL_0224: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Expected O, but got Unknown
			//IL_0260: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: Expected O, but got Unknown
			//IL_02d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02de: Expected O, but got Unknown
			//IL_0310: Unknown result type (might be due to invalid IL or missing references)
			//IL_031a: Expected O, but got Unknown
			//IL_033e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0348: Expected O, but got Unknown
			//IL_039b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a5: Expected O, but got Unknown
			//IL_03cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d5: Expected O, but got Unknown
			//IL_0443: Unknown result type (might be due to invalid IL or missing references)
			//IL_044d: Expected O, but got Unknown
			//IL_047f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0489: Expected O, but got Unknown
			//IL_05c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_05cf: Expected O, but got Unknown
			//IL_0601: Unknown result type (might be due to invalid IL or missing references)
			//IL_060b: Expected O, but got Unknown
			//IL_063d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0647: Expected O, but got Unknown
			//IL_069a: Unknown result type (might be due to invalid IL or missing references)
			//IL_06a4: Expected O, but got Unknown
			//IL_06d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e0: Expected O, but got Unknown
			//IL_0712: Unknown result type (might be due to invalid IL or missing references)
			//IL_071c: Expected O, but got Unknown
			//IL_074e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0758: Expected O, but got Unknown
			//IL_078a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0794: Expected O, but got Unknown
			//IL_07c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_07d0: Expected O, but got Unknown
			//IL_0802: Unknown result type (might be due to invalid IL or missing references)
			//IL_080c: Expected O, but got Unknown
			//IL_083e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0848: Expected O, but got Unknown
			//IL_087a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0884: Expected O, but got Unknown
			//IL_08d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_08e1: Expected O, but got Unknown
			//IL_091f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0929: Expected O, but got Unknown
			//IL_0972: Unknown result type (might be due to invalid IL or missing references)
			//IL_097c: Expected O, but got Unknown
			//IL_0a6e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a78: Expected O, but got Unknown
			//IL_0acb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ad5: Expected O, but got Unknown
			//IL_0b07: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b11: Expected O, but got Unknown
			//IL_0ba4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bae: Expected O, but got Unknown
			//IL_0bdc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0be6: Expected O, but got Unknown
			//IL_0c39: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c43: Expected O, but got Unknown
			//IL_0c75: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c7f: Expected O, but got Unknown
			//IL_0d5c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d66: Expected O, but got Unknown
			_cfg = cfg;
			_orphans = Orphans(cfg);
			bool isHeadless = Env.IsHeadless;
			float def = (isHeadless ? 0.033f : 0.05f);
			string def2 = (isHeadless ? "0,5,3" : "0,0,0");
			Enabled = cfg.Bind<bool>("01 General", "Enabled", true, new ConfigDescription("Master switch. false = every patch body behaves exactly like vanilla on its next call (telemetry included). Live.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 100
				}
			}));
			ConfigReloadSeconds = Adv("01 General", "ConfigReloadSeconds", 5f, 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 = Gen("02 SendWindow", "Enabled", "SendWindow", def: true, 95, "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 = Adv("02 SendWindow", "TargetRateKBps", 300, 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 = Adv("02 SendWindow", "BdpFactor", 1.25f, 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 = Adv("02 SendWindow", "MaxWindowBytes", 65536, 65536, new ConfigDescription("Upper bound of the computed window. The lower bound is MinWindowBytes (a queue drain can still clamp it to DrainFloorBytes).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10240, 262144), Array.Empty<object>()));
			MinWindowBytes = Adv("02 SendWindow", "MinWindowBytes", 32768, 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 = Adv("02 SendWindow", "HeadroomBytes", 2048, 2048, new ConfigDescription("Minimum free window before a round sends anything (vanilla 2048).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(512, 16384), Array.Empty<object>()));
			DrainIntervalSeconds = Adv("02 SendWindow", "DrainIntervalSeconds", 2f, 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 = Adv("02 SendWindow", "DrainFloorBytes", 8192, 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 = Adv("02 SendWindow", "RaiseJotunnQueueLimit", def: true, was: 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 = Gen("03 Scheduler", "Enabled", "Scheduler", def: true, 93, "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 = Adv("03 Scheduler", "SendIntervalSeconds", def, 0.05f, new ConfigDescription("Seconds between send rounds per peer (vanilla 0.05). Default 0.033 on a dedicated server: about 30 rounds per second per peer instead of 20, so an update waits less for its round; measured on a busy server at ~28 rounds/s per peer with no change in frame time. Default 0.05 on a client, for its own sends to the server (the client value was not part of the measurement).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.02f, 0.2f), Array.Empty<object>()));
			FrameBudgetMs = Adv("03 Scheduler", "FrameBudgetMs", 4f, 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 = Adv("03 Scheduler", "ServerTargetFrameRate", 60, 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. Default 60 halves both compared with vanilla's 30; measured with no extra CPU on a busy server. 0 = leave vanilla's 30. Follows the master switch ([01 General] Enabled), not [01 General] Scheduler. Live.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 120), Array.Empty<object>()));
			F3Enabled = Gen("04 SteamTransport", "Enabled", "SteamTransport", def: true, 94, "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 = Adv("04 SteamTransport", "SendRateKBps", 1024, 1024, new ConfigDescription("Send rate clamp per connection (KB/s). 150 = vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(150, 8192), Array.Empty<object>()));
			SendBufferBytes = Adv("04 SteamTransport", "SendBufferBytes", 0, 0, new ConfigDescription("Steam SendBufferSize (bytes). 0 leaves Steam's default 524288.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16777216), Array.Empty<object>()));
			F4Enabled = Gen("05 RelayThrottle", "Enabled", "RelayThrottle", def: true, 92, "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 = Adv("05 RelayThrottle", "ApplyToClientUplink", def: true, was: 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 an object next to any player gets the Near rate. By default that is unlimited except for fish (FishHz, 6 Hz) and effects (EffectHz, 10 Hz).");
			NearMeters = Adv("05 RelayThrottle", "NearMeters", 40f, 40f, new ConfigDescription("Inside this distance the Near rate applies.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 200f), Array.Empty<object>()));
			FarMeters = Adv("05 RelayThrottle", "FarMeters", 100f, 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 = Adv("05 RelayThrottle", "FishHz", "6,3,2", "6,3,2", "Max updates/s for Fish prefabs as near,far,beyond. 0 = unlimited.");
			CreatureHz = Adv("05 RelayThrottle", "CreatureHz", "0,10,5", "0,10,5", "Max updates/s for other Character prefabs as near,far,beyond. 0 = unlimited.");
			ItemHz = Adv("05 RelayThrottle", "ItemHz", "0,5,3", "0,5,3", "Max updates/s for ItemDrop prefabs as near,far,beyond. 0 = unlimited.");
			OtherHz = Adv("05 RelayThrottle", "OtherHz", def2, "0,0,0", "Max updates/s for every other Default-type prefab as near,far,beyond. 0 = unlimited. Default 0,5,3 on a dedicated server (0,0,0 before 0.3): nothing within NearMeters is slowed, farther objects get the item rates; live-tested on a busy server, where it cut distant relay traffic with no visible change. Default 0,0,0 on a client, which applies this table to its own uploads (the client value was not part of the measurement).");
			FloatingHz = Adv("05 RelayThrottle", "FloatingHz", "0,5,3", "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 = Adv("05 RelayThrottle", "EffectHz", "10,5,3", "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 = Gen("06 Priority", "Enabled", "Priority", def: true, 91, "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 = Adv("06 Priority", "LiveRefPos", def: true, was: 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 = Adv("06 Priority", "AgeWeight", 1.5f, 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 = Adv("06 Priority", "SolidBiasMeters", 64f, 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 = Adv("06 Priority", "NewNearBonusMeters", 200f, 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 = Gen("07 Deadband", "Enabled", "Deadband", def: true, 90, "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 = Adv("07 Deadband", "ItemPosMeters", 0.02f, 0.02f, new ConfigDescription("Position deadband for ItemDrop prefabs (m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>()));
			ItemRotDegrees = Adv("07 Deadband", "ItemRotDegrees", 2f, 2f, new ConfigDescription("Rotation deadband for ItemDrop prefabs (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			CreaturePosMeters = Adv("07 Deadband", "CreaturePosMeters", 0.01f, 0.01f, new ConfigDescription("Position deadband for creatures and fish (m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>()));
			CreatureRotDegrees = Adv("07 Deadband", "CreatureRotDegrees", 1f, 1f, new ConfigDescription("Rotation deadband for creatures and fish (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			OtherPosMeters = Adv("07 Deadband", "OtherPosMeters", 0.01f, 0.01f, new ConfigDescription("Position deadband for every other synced object (m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>()));
			OtherRotDegrees = Adv("07 Deadband", "OtherRotDegrees", 1f, 1f, new ConfigDescription("Rotation deadband for every other synced object (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			VelocityMps = Adv("07 Deadband", "VelocityMps", 0.05f, 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 = Adv("07 Deadband", "MooredShipPosMeters", 0.05f, 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 = Adv("07 Deadband", "MooredShipRotDegrees", 2f, 2f, new ConfigDescription("Rotation deadband for a ship with nobody aboard (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			TelemetryEnabled = Gen("08 Telemetry", "Enabled", "Telemetry", def: true, 80, "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 = Adv("08 Telemetry", "IntervalSeconds", 10f, 10f, new ConfigDescription("Seconds between stats lines.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 300f), Array.Empty<object>()));
			PerPrefab = Adv("08 Telemetry", "PerPrefab", def: true, was: true, "Tally sent/received ZDO updates by prefab (a dictionary increment per update).");
			TopN = Adv("08 Telemetry", "TopN", 8, 8, new ConfigDescription("How many prefabs to list per line.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 30), Array.Empty<object>()));
			StatsFile = Gen("08 Telemetry", "StatsFile", "StatsFile", def: true, 79, "Also append every stats/census/item line to BepInEx/BeanheimNet-stats.log, which survives restarts (LogOutput.log does not).");
			StatsLogMaxMB = Adv("08 Telemetry", "StatsLogMaxMB", 16, 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 = Gen("08 Telemetry", "CensusOnBoot", "CensusOnBoot", def: true, 75, "Server: count every ZDO by prefab and type once the world is loaded (sliced over frames). Also available as 'bnet census'.");
			OwnerWriteStats = Adv("08 Telemetry", "OwnerWriteStats", def: false, was: 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.");
			PeerExtras = Gen("08 Telemetry", "PeerExtras", "PeerExtras", def: true, 78, "Server: one more line per player after each peer line ('peerx'): Steam queue time and packet loss, how long a moving object near that player waited before it was relayed, who owns the creatures near them, ownership changes, helm holder vs ship owner, and the longest silence. Reads only; nothing is sent or changed. Live.");
			ClientExtras = Gen("08 Telemetry", "ClientExtras", "ClientExtras", def: true, 77, "Client: one more line after the stats line ('client'): arrival gaps of moving objects near you, position corrections (jumps and snaps), clock corrections from the server, objects waiting to be created, and received object data. Reads only; nothing is sent or changed. Live.");
			DesyncCounters = Gen("08 Telemetry", "DesyncCounters", "DesyncCounters", def: true, 76, "Server: one 'desync' line per interval: copies left out of date after an object crossed a zone border, damage requests sent to a player who does not own the target, and routed RPCs by name with their recipient counts. Reads only; nothing is sent or changed. Live.");
			ItemReport = Gen("09 Items", "ReportEnabled", "ItemReport", def: true, 70, "Server: periodic report of ItemDrop ZDOs (count by prefab, age, in water, far from players). Live.");
			SweepMinutes = Adv("09 Items", "SweepMinutes", 15f, 15f, new ConfigDescription("Minutes between sweeps (sliced over frames).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 1440f), Array.Empty<object>()));
			ItemCleanup = Gen("09 Items", "Cleanup", "ItemCleanup", def: false, 69, "DESTROY items that match every Items.* condition under [99 Advanced]. 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 = Adv("09 Items", "MaxAgeHours", 6f, 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 = Adv("09 Items", "ProtectRadiusMeters", 200f, 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 = Adv("09 Items", "OnlyInWater", def: true, was: true, "Only delete items at or below sea level (floating or sunk).");
			ProtectedPatterns = Adv("09 Items", "ProtectedPatterns", "Trophy,Wishbone,DragonEgg,CryptKey,Totem", "Trophy,Wishbone,DragonEgg,CryptKey,Totem", "Comma-separated substrings; a prefab name containing any of them is never deleted (case-insensitive).");
			BaseProxyPrefabs = Adv("09 Items", "BaseProxyPrefabs", "piece_workbench,bed,guard_stone,piece_bed02,fire_pit,forge,piece_chest_wood,piece_chest", "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 = Adv("09 Items", "BaseProxyRadiusMeters", 32f, 32f, new ConfigDescription("Radius for the base proxy check.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(8f, 128f), Array.Empty<object>()));
			MaxDeletionsPerSweep = Adv("09 Items", "MaxDeletionsPerSweep", 200, 200, new ConfigDescription("Safety cap per sweep.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 5000), Array.Empty<object>()));
			F9Enabled = Gen("10 ItemMerge", "Enabled", "ItemMerge", def: false, 68, "Client side (the machine that owns the items). Merge same-name, same-quality, same-variant stackable drops within ItemMerge.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 ItemMerge.MergeIntervalSeconds regardless. Live.");
			MergeRadiusMeters = Adv("10 ItemMerge", "MergeRadiusMeters", 5f, 5f, new ConfigDescription("Merge radius (vanilla auto-stack uses 4).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 16f), Array.Empty<object>()));
			MergeIntervalSeconds = Adv("10 ItemMerge", "MergeIntervalSeconds", 30f, 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 = Gen("11 Triggers", "Census", "RunCensus", def: false, 60, "One-shot: set true (hand-edit the server cfg; it is re-read within General.ConfigReloadSeconds) to run a world census now. Resets to false by itself. Same as 'bnet census'.");
			TriggerItemReport = Gen("11 Triggers", "ItemReport", "RunItemReport", def: false, 59, "One-shot: run the item report now (no deletions). Same as 'bnet items'.");
			TriggerItemCleanupOnce = Gen("11 Triggers", "ItemCleanupOnce", "RunItemCleanupOnce", def: false, 58, "One-shot: run ONE item cleanup sweep with the configured criteria even if ItemCleanup is false. Every deletion is logged. Same as 'bnet cleanup run'.");
			TriggerStats = Gen("11 Triggers", "Stats", "RunStats", def: false, 57, "One-shot: write a stats report now without waiting for the interval. Same as 'bnet stats'.");
			Profiler = cfg.Bind<bool>("12 Diagnostics", "Profiler", false, "Both sides: time each part of the frame and the busiest update methods, and write a 'prof' line per interval. Its timing hooks are installed when this turns on and removed when it turns off, a few per frame, so toggling never stalls a frame. Off costs nothing. Live.");
			ReflectionSampler = cfg.Bind<bool>("12 Diagnostics", "ReflectionSampler", false, "Both sides: count reflection calls on the main thread and name the code making them, in a 'refl' line per interval. It captures a call stack for a small sample of calls, so leave it off unless you are chasing long frames. Installed on toggle and removed when off. Live.");
			GcTimeSliceMs = cfg.Bind<int>("12 Diagnostics", "GcTimeSliceMs", 0, new ConfigDescription("Dedicated server: Unity's incremental garbage collector time slice in milliseconds, for measuring long frames. 0 leaves Unity's value alone (and puts back the value read at startup if it was changed). Every change is logged. Live.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 50), Array.Empty<object>()));
			if (ToAdvanced.Count > 0 || ToGeneral.Count > 0)
			{
				cfg.Save();
				LogMigration(ToAdvanced, "settings");
				LogMigration(ToGeneral, "switches");
			}
			cfg.SettingChanged += delegate
			{
				if (!_suppress)
				{
					Refresh();
				}
			};
			RememberWriteTime();
			Refresh();
		}

		private static ConfigEntry<T> Adv<T>(string oldSection, string key, T def, T was, ConfigDescription desc)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			string text = oldSection.Substring(oldSection.IndexOf(' ') + 1) + "." + key;
			ConfigDefinition old = new ConfigDefinition(oldSection, key);
			bool flag = false;
			try
			{
				flag = Carry(old, new ConfigDefinition("99 Advanced", text), was, ToAdvanced);
			}
			catch (Exception ex)
			{
				Stats.LogOnce("config.migrate", ex);
			}
			ConfigEntry<T> val = _cfg.Bind<T>("99 Advanced", text, def, new ConfigDescription(desc.Description + "\nFormerly [" + oldSection + "] " + key + ".", desc.AcceptableValues, desc.Tags));
			MovedKeys.Add(new Moved(old, new ConfigDefinition("99 Advanced", text)));
			if (flag)
			{
				ToAdvanced.Kept.Add(text + "=" + ((ConfigEntryBase)val).GetSerializedValue());
			}
			return val;
		}

		private static ConfigEntry<T> Adv<T>(string oldSection, string key, T def, T was, string desc)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			return Adv(oldSection, key, def, was, new ConfigDescription(desc, (AcceptableValueBase)null, Array.Empty<object>()));
		}

		private static ConfigEntry<T> Gen<T>(string oldSection, string oldKey, string key, T def, int order, string desc)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Expected O, but got Unknown
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			ConfigDefinition old = new ConfigDefinition(oldSection, oldKey);
			ConfigDefinition now = new ConfigDefinition("01 General", key);
			bool flag = false;
			try
			{
				flag = Carry(old, now, def, ToGeneral);
			}
			catch (Exception ex)
			{
				Stats.LogOnce("config.migrate", ex);
			}
			ConfigEntry<T> val = _cfg.Bind<T>("01 General", key, def, new ConfigDescription(desc + "\nFormerly [" + oldSection + "] " + oldKey + ".", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = order
				}
			}));
			MovedKeys.Add(new Moved(old, now));
			if (flag)
			{
				ToGeneral.Kept.Add(key + "=" + ((ConfigEntryBase)val).GetSerializedValue());
			}
			return val;
		}

		private static void LogMigration(Migration m, string what)
		{
			if (m.Count != 0)
			{
				Plugin.Log.LogInfo((object)($"config: moved {m.Count} {what} to [{m.Target}]: kept {m.Kept.Count} changed values" + string.Format("{0}, {1} at defaults", (m.Kept.Count > 0) ? (" (" + string.Join(", ", m.Kept.ToArray()) + ")") : "", m.Default) + ((m.Superseded > 0) ? $", {m.Superseded} already set there (old copy dropped)" : "")));
				if (m.Conflicts.Count > 0)
				{
					Plugin.Log.LogWarning((object)($"config: {m.Conflicts.Count} old-layout setting(s) were not carried over because the [{m.Target}] key is also set: " + string.Join("; ", m.Conflicts.ToArray()) + "."));
				}
			}
		}

		private static bool Carry<T>(ConfigDefinition old, ConfigDefinition now, T was, Migration mig)
		{
			if (_orphans == null || !_orphans.TryGetValue(old, out string value))
			{
				return false;
			}
			_orphans.Remove(old);
			if (_orphans.ContainsKey(now))
			{
				mig.Superseded++;
				string text = _orphans[now];
				if (!TryRead<T>(value, out var value2) || !TryRead<T>(text, out var value3) || !EqualityComparer<T>.Default.Equals(value2, value3))
				{
					mig.Conflicts.Add("[" + old.Section + "] " + old.Key + " = " + value + " (kept [" + now.Section + "] " + now.Key + " = " + text + ")");
				}
				return false;
			}
			if (!TryRead<T>(value, out var value4))
			{
				_orphans[now] = value;
				mig.Default++;
				return false;
			}
			if (EqualityComparer<T>.Default.Equals(value4, was))
			{
				mig.Default++;
				return false;
			}
			_orphans[now] = value;
			return true;
		}

		private static bool TryRead<T>(string raw, out T value)
		{
			try
			{
				value = (T)TomlTypeConverter.ConvertToValue(raw, typeof(T));
				return true;
			}
			catch
			{
				value = default(T);
				return false;
			}
		}

		private static Dictionary<ConfigDefinition, string>? Orphans(ConfigFile cfg)
		{
			string text;
			try
			{
				PropertyInfo property = typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.NonPublic);
				if (property?.GetValue(cfg, null) is Dictionary<ConfigDefinition, string> result)
				{
					return result;
				}
				text = ((property == null) ? "no such property" : "unexpected type");
			}
			catch (Exception ex)
			{
				text = ex.GetType().Name + ": " + ex.Message;
			}
			Plugin.Log.LogWarning((object)("config: cannot read BepInEx's ConfigFile.OrphanedEntries (" + text + "). Values changed in an older layout were NOT carried over (tuning values to [99 Advanced], switches to [01 General]) and apply their defaults: set them again there. Any old lines stay in the file, unread."));
			return null;
		}

		private static void WarnStale()
		{
			if (_orphans == null)
			{
				return;
			}
			List<string> list = null;
			foreach (Moved movedKey in MovedKeys)
			{
				if (_orphans.ContainsKey(movedKey.Old))
				{
					(list ?? (list = new List<string>())).Add("[" + movedKey.Old.Section + "] " + movedKey.Old.Key + " -> [" + movedKey.New.Section + "] " + movedKey.New.Key);
				}
			}
			if (list != null)
			{
				Plugin.Log.LogWarning((object)string.Format("config: {0} setting(s) under their old section are not read; they moved: {1}.", list.Count, string.Join(", ", list.ToArray())));
			}
		}

		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()));
			WarnStale();
		}

		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()));
			WarnStale();
		}

		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.3.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "helix.beanheim.net";

		public const string PluginName = "BeanheimNet";

		public const string PluginVersion = "0.3.1";

		internal static ManualLogSource Log = null;

		internal static Plugin Instance = null;

		internal static Harmony HarmonyInstance = null;

		internal static string Conflicts = "";

		private static readonly string[] IncompatibleGuids = new string[11]
		{
			"org.bepinex.plugins.network", "akoozie.valheimtune", "MidnightsFX.NetworkPerformanceSystem", "redseiko.valheim.returntosender", "CW_Jesse.BetterNetworking", "VitByr.VBNetTweaks", "DIT.BetterNetworking10", "com.Fire.FiresGhettoNetworkMod", "sighsorry.SkadiNet", "dzk.warheimnetwork",
			"Searica.Valheim.NetworkTweaks"
		};

		private static readonly IFeature[] AllFeatures = new IFeature[14]
		{
			new TelemetryFeature(),
			new PeerExtrasFeature(),
			new ClientExtrasFeature(),
			new DesyncFeature(),
			new ProfilerFeature(),
			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.3.1 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))
				{
					Conflicts = Conflicts + ((Conflicts.Length > 0) ? ", " : "") + text;
				}
			}
			if (Conflicts.Length > 0)
			{
				Log.LogWarning((object)(Conflicts + " rewrites the same network send path as BeanheimNet. The two are not compatible and BeanheimNet does not switch itself off: remove one of them."));
			}
			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.3.1", 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();
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public int? Order { get; set; }
	}
}
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>();

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

		private const int MissedCap = 1024;

		private const float RetrySeconds = 30f;

		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;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (Missed.TryGetValue(prefabHash, out var value2) && realtimeSinceStartup - value2 < 30f)
			{
				return Cls.Other;
			}
			GameObject prefab = instance.GetPrefab(prefabHash);
			if ((Object)(object)prefab == (Object)null)
			{
				if (Missed.Count < 1024 || Missed.ContainsKey(prefabHash))
				{
					Missed[prefabHash] = realtimeSinceStartup;
				}
				return Cls.Other;
			}
			Missed.Remove(prefabHash);
			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();
			Missed.Clear();
		}
	}
}
namespace BeanheimNet.Telemetry
{
	internal static class Census
	{
		private struct Slot
		{
			public int Prefab;

			public int Count;

			public PrefabClass.Cls Cls;

			public bool Planned;
		}

		private const int SliceSize = 20000;

		private const int InitialSlots = 4096;

		private const int GhostZoneCap = 4096;

		private static readonly PrefabClass.Cls[] TypesOrder = new PrefabClass.Cls[9]
		{
			PrefabClass.Cls.Other,
			PrefabClass.Cls.Exempt,
			PrefabClass.Cls.Fish,
			PrefabClass.Cls.Creature,
			PrefabClass.Cls.Item,
			PrefabClass.Cls.Terrain,
			PrefabClass.Cls.Floating,
			PrefabClass.Cls.Effect,
			PrefabClass.Cls.Unknown
		};

		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> SlotOf = new Dictionary<int, int>();

		private static Slot[] _slots = Array.Empty<Slot>();

		private static int _slotCount;

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

		private static readonly int[] ByClassType = new int[1024];

		private static GhostTally? _ghosts;

		private static bool _haveNow;

		private static long _nowTicks;

		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.ObjectsOk || 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;
			SlotOf.Clear();
			if (_slots.Length == 0)
			{
				_slots = new Slot[4096];
			}
			_slotCount = 0;
			Array.Clear(ByType, 0, ByType.Length);
			Array.Clear(ByClassType, 0, ByClassType.Length);
			(_ghosts ?? (_ghosts = new GhostTally(4096))).Reset();
			_haveNow = (Object)(object)ZNet.instance != (Object)null;
			_nowTicks = (_haveNow ? ZNet.instance.GetTime().Ticks : 0);
			_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
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			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)
						{