Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BeanheimNet v0.2.2
BeanheimNet.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using BeanheimNet.Features; using BeanheimNet.Telemetry; using BeanheimNet.Util; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Steamworks; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("BeanheimNet")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BeanheimNet")] [assembly: AssemblyTitle("BeanheimNet")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BeanheimNet { internal readonly struct PeerSnap { public readonly bool Found; public readonly uint DataRevision; public readonly ushort OwnerRevision; public readonly float SyncTime; public PeerSnap(uint dataRevision, ushort ownerRevision, float syncTime) { Found = true; DataRevision = dataRevision; OwnerRevision = ownerRevision; SyncTime = syncTime; } } internal static class Bindings { public static bool ZdoManOk; public static bool PeerInfoOk; public static bool SyncTransformOk; public static bool RpcOk; public static bool SteamSocketOk; public static string ZdoManReason = ""; public static string PeerInfoReason = ""; public static string SyncTransformReason = ""; public static string RpcReason = ""; public static string SteamSocketReason = ""; public static Type ZdoPeerType = null; public static Type PeerInfoType = null; public static MethodInfo SendZDOsMi = null; public static MethodInfo SendZDOToPeers2Mi = null; public static MethodInfo CreateSyncListMi = null; public static MethodInfo ServerSortMi = null; public static FieldRef<object, ZNetPeer> PeerNetPeer = null; public static FieldRef<object, HashSet<ZDOID>> PeerForceSend = null; public static FieldRef<object, IList> Peers = null; public static FieldRef<ZDOMan, List<ZDO>> TempToSync = null; public static FieldRef<ZDOMan, int> ZdosSent = null; public static FieldRef<ZDOMan, float> SendTimer = null; public static FieldRef<ZDOMan, int> NextSendPeer = null; public static FieldRef<ZDOMan, Dictionary<ZDOID, ZDO>> ObjectsById = null; public static Func<ZDOMan, object, bool, bool> SendZDOs = null; public static Func<object, ZDOID, PeerSnap> PeerInfo = null; public static MethodInfo DeserializeMi = null; public static MethodInfo? IncreaseDataRevisionMi; public static MethodInfo OwnerSyncMi = null; public static FieldRef<ZSyncTransform, Vector3> StPositionCached = null; public static FieldRef<ZSyncTransform, Vector3> StVelocityCached = null; public static FieldRef<ZSyncTransform, Vector3> StLastScale = null; public static FieldRef<ZSyncTransform, bool> StWasOwner = null; public static FieldRef<ZSyncTransform, ZNetView> StNview = null; public static FieldRef<ZSyncTransform, Rigidbody> StBody = null; public static FieldRef<ZSyncTransform, ZDOID>? StParentCached; public static Func<ZSyncTransform, Vector3> StGetPosition = null; public static Func<ZSyncTransform, Vector3> StGetVelocity = null; public static MethodInfo UpdatePingMi = null; public static MethodInfo ReceivePingMi = null; public static FieldRef<ZRpc, int> RpcSentData = null; public static FieldRef<ZRpc, int> RpcRecvData = null; public static FieldRef<ZRpc, float> RpcPingTimer = null; public static float PingInterval = 1f; public static MethodInfo RegisterGlobalCallbacksMi = null; public static FieldRef<ZSteamSocket, HSteamNetConnection> SteamCon = null; public static FieldRef<ZSteamSocket, Queue<byte[]>> SteamSendQueue = null; public static MethodInfo? InitTerminalMi; public static bool ShipOk; public static string ShipReason = ""; public static Func<Ship, bool> ShipHasPlayerOnboard = null; public static bool ItemDropOk; public static string ItemDropReason = ""; public static MethodInfo SlowUpdateMi = null; public static FieldRef<ItemDrop, ZNetView> ItemNview = null; public static Action<ItemDrop> ItemSave = null; public static readonly List<MethodBase> PatchedMethods = new List<MethodBase>(); public static void Init(ManualLogSource log) { ZdoManOk = Try(BindZdoMan, out ZdoManReason); PeerInfoOk = ZdoManOk && Try(BindPeerInfo, out PeerInfoReason); SyncTransformOk = Try(BindSyncTransform, out SyncTransformReason); RpcOk = Try(BindRpc, out RpcReason); SteamSocketOk = Try(BindSteamSocket, out SteamSocketReason); InitTerminalMi = AccessTools.Method(typeof(Terminal), "InitTerminal", (Type[])null, (Type[])null); ItemDropOk = Try(BindItemDrop, out ItemDropReason); ShipOk = Try(BindShip, out ShipReason); Report(log, "ZDOMan", ZdoManOk, ZdoManReason); Report(log, "ZDOMan.ZDOPeer.PeerZDOInfo", PeerInfoOk, PeerInfoReason); Report(log, "ZSyncTransform", SyncTransformOk, SyncTransformReason); Report(log, "ZRpc", RpcOk, RpcReason); Report(log, "ZSteamSocket", SteamSocketOk, SteamSocketReason); Report(log, "ItemDrop", ItemDropOk, ItemDropReason); Report(log, "Ship", ShipOk, ShipReason); } private static void Report(ManualLogSource log, string group, bool ok, string reason) { if (ok) { log.LogInfo((object)("[bind] " + group + ": ok")); return; } log.LogError((object)("[bind] " + group + ": FAILED - " + reason + ". Features needing it are skipped; the game changed shape (AUTO_UPDATE?) or another mod rewrote it.")); } private static bool Try(Action bind, out string reason) { try { bind(); reason = ""; return true; } catch (Exception ex) { reason = ex.GetType().Name + ": " + ex.Message; return false; } } private static T Need<T>(T? value, string what) where T : class { if (value == null) { throw new MissingMemberException(what + " not found"); } return value; } private static void BindZdoMan() { ZdoPeerType = Need(AccessTools.Inner(typeof(ZDOMan), "ZDOPeer"), "ZDOMan.ZDOPeer"); SendZDOsMi = Need(AccessTools.Method(typeof(ZDOMan), "SendZDOs", new Type[2] { ZdoPeerType, typeof(bool) }, (Type[])null), "ZDOMan.SendZDOs(ZDOPeer,bool)"); SendZDOToPeers2Mi = Need(AccessTools.Method(typeof(ZDOMan), "SendZDOToPeers2", new Type[1] { typeof(float) }, (Type[])null), "ZDOMan.SendZDOToPeers2(float)"); CreateSyncListMi = Need(AccessTools.Method(typeof(ZDOMan), "CreateSyncList", new Type[2] { ZdoPeerType, typeof(List<ZDO>) }, (Type[])null), "ZDOMan.CreateSyncList(ZDOPeer,List<ZDO>)"); ServerSortMi = Need(AccessTools.Method(typeof(ZDOMan), "ServerSortSendZDOS", new Type[3] { typeof(List<ZDO>), typeof(Vector3), ZdoPeerType }, (Type[])null), "ZDOMan.ServerSortSendZDOS"); DeserializeMi = Need(AccessTools.Method(typeof(ZDO), "Deserialize", new Type[1] { typeof(ZPackage) }, (Type[])null), "ZDO.Deserialize(ZPackage)"); IncreaseDataRevisionMi = AccessTools.Method(typeof(ZDO), "IncreaseDataRevision", (Type[])null, (Type[])null); PeerNetPeer = AccessTools.FieldRefAccess<ZNetPeer>(ZdoPeerType, "m_peer"); PeerForceSend = AccessTools.FieldRefAccess<HashSet<ZDOID>>(ZdoPeerType, "m_forceSend"); Peers = AccessTools.FieldRefAccess<IList>(typeof(ZDOMan), "m_peers"); TempToSync = AccessTools.FieldRefAccess<ZDOMan, List<ZDO>>("m_tempToSync"); ZdosSent = AccessTools.FieldRefAccess<ZDOMan, int>("m_zdosSent"); SendTimer = AccessTools.FieldRefAccess<ZDOMan, float>("m_sendTimer"); NextSendPeer = AccessTools.FieldRefAccess<ZDOMan, int>("m_nextSendPeer"); ObjectsById = AccessTools.FieldRefAccess<ZDOMan, Dictionary<ZDOID, ZDO>>("m_objectsByID"); ParameterExpression parameterExpression = Expression.Parameter(typeof(ZDOMan), "man"); ParameterExpression parameterExpression2 = Expression.Parameter(typeof(object), "peer"); ParameterExpression parameterExpression3 = Expression.Parameter(typeof(bool), "flush"); SendZDOs = Expression.Lambda<Func<ZDOMan, object, bool, bool>>(Expression.Call(parameterExpression, SendZDOsMi, Expression.Convert(parameterExpression2, ZdoPeerType), parameterExpression3), new ParameterExpression[3] { parameterExpression, parameterExpression2, parameterExpression3 }).Compile(); } private static void BindPeerInfo() { PeerInfoType = Need(AccessTools.Inner(ZdoPeerType, "PeerZDOInfo"), "ZDOMan.ZDOPeer.PeerZDOInfo"); FieldInfo fieldInfo = Need(AccessTools.Field(ZdoPeerType, "m_zdos"), "ZDOPeer.m_zdos"); MethodInfo method = Need(fieldInfo.FieldType.GetMethod("TryGetValue"), "Dictionary.TryGetValue"); FieldInfo fieldInfo2 = Need(AccessTools.Field(PeerInfoType, "m_dataRevision"), "PeerZDOInfo.m_dataRevision"); FieldInfo fieldInfo3 = Need(AccessTools.Field(PeerInfoType, "m_ownerRevision"), "PeerZDOInfo.m_ownerRevision"); FieldInfo fieldInfo4 = Need(AccessTools.Field(PeerInfoType, "m_syncTime"), "PeerZDOInfo.m_syncTime"); if (fieldInfo2.FieldType != typeof(uint) || fieldInfo3.FieldType != typeof(ushort) || fieldInfo4.FieldType != typeof(float)) { throw new MissingMemberException("PeerZDOInfo field types changed"); } ConstructorInfo? constructor = typeof(PeerSnap).GetConstructor(new Type[3] { typeof(uint), typeof(ushort), typeof(float) }); ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "peer"); ParameterExpression parameterExpression2 = Expression.Parameter(typeof(ZDOID), "id"); ParameterExpression parameterExpression3 = Expression.Variable(PeerInfoType, "info"); Expression test = Expression.Call(Expression.Field(Expression.Convert(parameterExpression, ZdoPeerType), fieldInfo), method, parameterExpression2, parameterExpression3); Expression ifTrue = Expression.New(constructor, Expression.Field(parameterExpression3, fieldInfo2), Expression.Field(parameterExpression3, fieldInfo3), Expression.Field(parameterExpression3, fieldInfo4)); PeerInfo = Expression.Lambda<Func<object, ZDOID, PeerSnap>>(Expression.Block(new ParameterExpression[1] { parameterExpression3 }, Expression.Condition(test, ifTrue, Expression.Default(typeof(PeerSnap)))), new ParameterExpression[2] { parameterExpression, parameterExpression2 }).Compile(); } private static void BindSyncTransform() { OwnerSyncMi = Need(AccessTools.Method(typeof(ZSyncTransform), "OwnerSync", (Type[])null, (Type[])null), "ZSyncTransform.OwnerSync"); StPositionCached = AccessTools.FieldRefAccess<ZSyncTransform, Vector3>("m_positionCached"); StVelocityCached = AccessTools.FieldRefAccess<ZSyncTransform, Vector3>("m_velocityCached"); StLastScale = AccessTools.FieldRefAccess<ZSyncTransform, Vector3>("m_lastLocalScale"); StWasOwner = AccessTools.FieldRefAccess<ZSyncTransform, bool>("m_wasOwner"); StNview = AccessTools.FieldRefAccess<ZSyncTransform, ZNetView>("m_nview"); StBody = AccessTools.FieldRefAccess<ZSyncTransform, Rigidbody>("m_body"); try { StParentCached = AccessTools.FieldRefAccess<ZSyncTransform, ZDOID>("m_tempParentCached"); } catch { StParentCached = null; } MethodInfo method = Need(AccessTools.Method(typeof(ZSyncTransform), "GetPosition", (Type[])null, (Type[])null), "ZSyncTransform.GetPosition"); MethodInfo method2 = Need(AccessTools.Method(typeof(ZSyncTransform), "GetVelocity", (Type[])null, (Type[])null), "ZSyncTransform.GetVelocity"); ParameterExpression parameterExpression = Expression.Parameter(typeof(ZSyncTransform), "st"); StGetPosition = Expression.Lambda<Func<ZSyncTransform, Vector3>>(Expression.Call(parameterExpression, method), new ParameterExpression[1] { parameterExpression }).Compile(); StGetVelocity = Expression.Lambda<Func<ZSyncTransform, Vector3>>(Expression.Call(parameterExpression, method2), new ParameterExpression[1] { parameterExpression }).Compile(); } private static void BindRpc() { UpdatePingMi = Need(AccessTools.Method(typeof(ZRpc), "UpdatePing", new Type[1] { typeof(float) }, (Type[])null), "ZRpc.UpdatePing(float)"); ReceivePingMi = Need(AccessTools.Method(typeof(ZRpc), "ReceivePing", new Type[1] { typeof(ZPackage) }, (Type[])null), "ZRpc.ReceivePing(ZPackage)"); RpcSentData = AccessTools.FieldRefAccess<ZRpc, int>("m_sentData"); RpcRecvData = AccessTools.FieldRefAccess<ZRpc, int>("m_recvData"); RpcPingTimer = AccessTools.FieldRefAccess<ZRpc, float>("m_pingTimer"); PingInterval = (float)Need(AccessTools.Field(typeof(ZRpc), "m_pingInterval"), "ZRpc.m_pingInterval").GetValue(null); } private static void BindItemDrop() { SlowUpdateMi = Need(AccessTools.Method(typeof(ItemDrop), "SlowUpdate", (Type[])null, (Type[])null), "ItemDrop.SlowUpdate"); ItemNview = AccessTools.FieldRefAccess<ItemDrop, ZNetView>("m_nview"); MethodInfo method = Need(AccessTools.Method(typeof(ItemDrop), "Save", Type.EmptyTypes, (Type[])null), "ItemDrop.Save()"); ParameterExpression parameterExpression = Expression.Parameter(typeof(ItemDrop), "item"); ItemSave = Expression.Lambda<Action<ItemDrop>>(Expression.Call(parameterExpression, method), new ParameterExpression[1] { parameterExpression }).Compile(); } private static void BindShip() { MethodInfo methodInfo = Need(AccessTools.Method(typeof(Ship), "HasPlayerOnboard", Type.EmptyTypes, (Type[])null), "Ship.HasPlayerOnboard()"); if (methodInfo.ReturnType != typeof(bool)) { throw new MissingMemberException("Ship.HasPlayerOnboard() no longer returns bool"); } ParameterExpression parameterExpression = Expression.Parameter(typeof(Ship), "ship"); ShipHasPlayerOnboard = Expression.Lambda<Func<Ship, bool>>(Expression.Call(parameterExpression, methodInfo), new ParameterExpression[1] { parameterExpression }).Compile(); } private static void BindSteamSocket() { RegisterGlobalCallbacksMi = Need(AccessTools.Method(typeof(ZSteamSocket), "RegisterGlobalCallbacks", (Type[])null, (Type[])null), "ZSteamSocket.RegisterGlobalCallbacks"); SteamCon = AccessTools.FieldRefAccess<ZSteamSocket, HSteamNetConnection>("m_con"); SteamSendQueue = AccessTools.FieldRefAccess<ZSteamSocket, Queue<byte[]>>("m_sendQueue"); } public static int UnsentBytes(ISocket socket) { if (!SteamSocketOk) { return 0; } ZSteamSocket val = Env.Unwrap(socket); if (val == null) { return 0; } Queue<byte[]> queue = SteamSendQueue.Invoke(val); if (queue == null || queue.Count == 0) { return 0; } int num = 0; foreach (byte[] item in queue) { num += item.Length; } return num; } } internal static class Cfg { private static ConfigFile _cfg = null; public static ConfigEntry<bool> Enabled = null; public static ConfigEntry<float> ConfigReloadSeconds = null; public static ConfigEntry<bool> F1Enabled = null; public static ConfigEntry<int> TargetRateKBps = null; public static ConfigEntry<float> BdpFactor = null; public static ConfigEntry<int> MaxWindowBytes = null; public static ConfigEntry<int> MinWindowBytes = null; public static ConfigEntry<int> HeadroomBytes = null; public static ConfigEntry<float> DrainIntervalSeconds = null; public static ConfigEntry<int> DrainFloorBytes = null; public static ConfigEntry<bool> RaiseJotunnQueueLimit = null; public static ConfigEntry<bool> F2Enabled = null; public static ConfigEntry<float> SendIntervalSeconds = null; public static ConfigEntry<float> FrameBudgetMs = null; public static ConfigEntry<int> ServerTargetFrameRate = null; public static ConfigEntry<bool> F3Enabled = null; public static ConfigEntry<int> SendRateKBps = null; public static ConfigEntry<int> SendBufferBytes = null; public static ConfigEntry<bool> F4Enabled = null; public static ConfigEntry<bool> F4ClientUplink = null; public static ConfigEntry<float> NearMeters = null; public static ConfigEntry<float> FarMeters = null; public static ConfigEntry<string> FishHz = null; public static ConfigEntry<string> CreatureHz = null; public static ConfigEntry<string> ItemHz = null; public static ConfigEntry<string> OtherHz = null; public static ConfigEntry<string> FloatingHz = null; public static ConfigEntry<string> EffectHz = null; public static ConfigEntry<bool> F5Enabled = null; public static ConfigEntry<bool> LiveRefPos = null; public static ConfigEntry<float> AgeWeight = null; public static ConfigEntry<float> SolidBiasMeters = null; public static ConfigEntry<float> NewNearBonusMeters = null; public static ConfigEntry<bool> F6Enabled = null; public static ConfigEntry<float> ItemPosMeters = null; public static ConfigEntry<float> ItemRotDegrees = null; public static ConfigEntry<float> CreaturePosMeters = null; public static ConfigEntry<float> CreatureRotDegrees = null; public static ConfigEntry<float> OtherPosMeters = null; public static ConfigEntry<float> OtherRotDegrees = null; public static ConfigEntry<float> VelocityMps = null; public static ConfigEntry<float> MooredShipPosMeters = null; public static ConfigEntry<float> MooredShipRotDegrees = null; public static ConfigEntry<bool> TelemetryEnabled = null; public static ConfigEntry<float> IntervalSeconds = null; public static ConfigEntry<bool> PerPrefab = null; public static ConfigEntry<int> TopN = null; public static ConfigEntry<bool> StatsFile = null; public static ConfigEntry<int> StatsLogMaxMB = null; public static ConfigEntry<bool> CensusOnBoot = null; public static ConfigEntry<bool> OwnerWriteStats = null; public static ConfigEntry<bool> ItemReport = null; public static ConfigEntry<float> SweepMinutes = null; public static ConfigEntry<bool> ItemCleanup = null; public static ConfigEntry<float> MaxAgeHours = null; public static ConfigEntry<float> ProtectRadiusMeters = null; public static ConfigEntry<bool> OnlyInWater = null; public static ConfigEntry<string> ProtectedPatterns = null; public static ConfigEntry<string> BaseProxyPrefabs = null; public static ConfigEntry<float> BaseProxyRadiusMeters = null; public static ConfigEntry<int> MaxDeletionsPerSweep = null; public static ConfigEntry<bool> F9Enabled = null; public static ConfigEntry<float> MergeRadiusMeters = null; public static ConfigEntry<float> MergeIntervalSeconds = null; public static ConfigEntry<bool> TriggerCensus = null; public static ConfigEntry<bool> TriggerItemReport = null; public static ConfigEntry<bool> TriggerItemCleanupOnce = null; public static ConfigEntry<bool> TriggerStats = null; public static HzTable Fish; public static HzTable Creature; public static HzTable Item; public static HzTable Other; public static HzTable Floating; public static HzTable Effect; public static string[] ProtectedPatternList = Array.Empty<string>(); public static HashSet<int> BaseProxyHashes = new HashSet<int>(); private static DateTime _lastWrite; private static float _pollTimer; private static bool _suppress; public static event Action? Changed; public static event Action? Triggers; public static void Init(ConfigFile cfg) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Expected O, but got Unknown //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Expected O, but got Unknown //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Expected O, but got Unknown //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Expected O, but got Unknown //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Expected O, but got Unknown //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Expected O, but got Unknown //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Expected O, but got Unknown //IL_0310: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Expected O, but got Unknown //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_034a: Expected O, but got Unknown //IL_03ae: Unknown result type (might be due to invalid IL or missing references) //IL_03b8: Expected O, but got Unknown //IL_03e6: Unknown result type (might be due to invalid IL or missing references) //IL_03f0: Expected O, but got Unknown //IL_050e: Unknown result type (might be due to invalid IL or missing references) //IL_0518: Expected O, but got Unknown //IL_0546: Unknown result type (might be due to invalid IL or missing references) //IL_0550: Expected O, but got Unknown //IL_057e: Unknown result type (might be due to invalid IL or missing references) //IL_0588: Expected O, but got Unknown //IL_05d1: Unknown result type (might be due to invalid IL or missing references) //IL_05db: Expected O, but got Unknown //IL_0609: Unknown result type (might be due to invalid IL or missing references) //IL_0613: Expected O, but got Unknown //IL_0641: Unknown result type (might be due to invalid IL or missing references) //IL_064b: Expected O, but got Unknown //IL_0679: Unknown result type (might be due to invalid IL or missing references) //IL_0683: Expected O, but got Unknown //IL_06b1: Unknown result type (might be due to invalid IL or missing references) //IL_06bb: Expected O, but got Unknown //IL_06e9: Unknown result type (might be due to invalid IL or missing references) //IL_06f3: Expected O, but got Unknown //IL_0721: Unknown result type (might be due to invalid IL or missing references) //IL_072b: Expected O, but got Unknown //IL_0759: Unknown result type (might be due to invalid IL or missing references) //IL_0763: Expected O, but got Unknown //IL_0791: Unknown result type (might be due to invalid IL or missing references) //IL_079b: Expected O, but got Unknown //IL_07e4: Unknown result type (might be due to invalid IL or missing references) //IL_07ee: Expected O, but got Unknown //IL_082c: Unknown result type (might be due to invalid IL or missing references) //IL_0836: Expected O, but got Unknown //IL_0878: Unknown result type (might be due to invalid IL or missing references) //IL_0882: Expected O, but got Unknown //IL_0901: Unknown result type (might be due to invalid IL or missing references) //IL_090b: Expected O, but got Unknown //IL_0954: Unknown result type (might be due to invalid IL or missing references) //IL_095e: Expected O, but got Unknown //IL_098c: Unknown result type (might be due to invalid IL or missing references) //IL_0996: Expected O, but got Unknown //IL_0a1d: Unknown result type (might be due to invalid IL or missing references) //IL_0a27: Expected O, but got Unknown //IL_0a51: Unknown result type (might be due to invalid IL or missing references) //IL_0a5b: Expected O, but got Unknown //IL_0aa4: Unknown result type (might be due to invalid IL or missing references) //IL_0aae: Expected O, but got Unknown //IL_0adc: Unknown result type (might be due to invalid IL or missing references) //IL_0ae6: Expected O, but got Unknown _cfg = cfg; Enabled = cfg.Bind<bool>("01 General", "Enabled", true, "Master switch. false = every patch body behaves exactly like vanilla on its next call (telemetry included). Live."); ConfigReloadSeconds = cfg.Bind<float>("01 General", "ConfigReloadSeconds", 5f, new ConfigDescription("How often the cfg file is checked for hand edits and reloaded. 0 disables polling.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>())); F1Enabled = cfg.Bind<bool>("02 SendWindow", "Enabled", true, "Size each peer's in-flight ZDO window from its measured round-trip time instead of vanilla's fixed 10240 bytes. Throughput through a fixed window is window/RTT, so vanilla starves anyone past ~70 ms. Needs SteamTransport to matter above 150 KB/s. Live."); TargetRateKBps = cfg.Bind<int>("02 SendWindow", "TargetRateKBps", 300, new ConfigDescription("ZDO throughput per peer the window is sized for (KB/s). Clamped to the Steam send rate actually in effect (SteamTransport).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(32, 2048), Array.Empty<object>())); BdpFactor = cfg.Bind<float>("02 SendWindow", "BdpFactor", 1.25f, new ConfigDescription("Multiplier on RTT x rate. 1.0 = one round trip of data in flight; more adds headroom at the cost of standing queue.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 3f), Array.Empty<object>())); MaxWindowBytes = cfg.Bind<int>("02 SendWindow", "MaxWindowBytes", 65536, new ConfigDescription("Upper bound of the computed window. The lower bound is always vanilla's 10240.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10240, 262144), Array.Empty<object>())); MinWindowBytes = cfg.Bind<int>("02 SendWindow", "MinWindowBytes", 32768, new ConfigDescription("Lower bound of the computed window (vanilla 10240). A LAN peer with a ~0 ms ping gets no benefit from RTT sizing, yet on a dedicated server whose send lap runs at ~10 rounds/s 10240 x 10 = ~100 KB/s is its hard cap; 32768 lifts that to ~320 KB/s. Raise together with SteamTransport.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10240, 65536), Array.Empty<object>())); HeadroomBytes = cfg.Bind<int>("02 SendWindow", "HeadroomBytes", 2048, new ConfigDescription("Minimum free window before a round sends anything (vanilla 2048).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(512, 16384), Array.Empty<object>())); DrainIntervalSeconds = cfg.Bind<float>("02 SendWindow", "DrainIntervalSeconds", 2f, new ConfigDescription("Compatibility with mods that wait for the socket queue to drop under a fixed size (ServerSync, Jotunn, ConditionalConfigSync) before sending. If a peer's queue stays above DrainFloorBytes for this long, that peer's window is clamped to the floor until one round sees it below (max 3 s). 0 disables.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); DrainFloorBytes = cfg.Bind<int>("02 SendWindow", "DrainFloorBytes", 8192, new ConfigDescription("Must sit below the smallest threshold any installed mod waits for (older ServerSync 10000, current ones 20000) with room for one ZDO of overshoot.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2048, 20000), Array.Empty<object>())); RaiseJotunnQueueLimit = cfg.Bind<bool>("02 SendWindow", "RaiseJotunnQueueLimit", true, "Raise Jotunn.Entities.CustomRPC.MaximumSendQueueSize to MaxWindowBytes + 20000 while SendWindow is on (restored when off). Jotunn otherwise blocks its senders behind the widened window."); F2Enabled = cfg.Bind<bool>("03 Scheduler", "Enabled", true, "Serve every connected peer each send interval instead of vanilla's one-peer-per-frame lap with an idle arming frame. Costs one CreateSyncList scan per peer per round; the frame budget bounds it. Live."); SendIntervalSeconds = cfg.Bind<float>("03 Scheduler", "SendIntervalSeconds", 0.05f, new ConfigDescription("Seconds between send rounds per peer (vanilla 0.05).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.02f, 0.2f), Array.Empty<object>())); FrameBudgetMs = cfg.Bind<float>("03 Scheduler", "FrameBudgetMs", 4f, new ConfigDescription("Max milliseconds per frame spent sending; the remainder is owed to the next frame so nobody starves.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 16f), Array.Empty<object>())); ServerTargetFrameRate = cfg.Bind<int>("03 Scheduler", "ServerTargetFrameRate", 0, new ConfigDescription("Dedicated server only. The server build requests a hard 30 fps (GraphicsSettingsManager.RequestTargetFrameRateFromPreset), so vanilla's one-peer-per-frame lap runs at ~10 Hz per peer with two players and every relayed RPC waits up to 33 ms. 60 halves both at roughly double the main-thread CPU. 0 = leave vanilla's 30. Live.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 120), Array.Empty<object>())); F3Enabled = cfg.Bind<bool>("04 SteamTransport", "Enabled", true, "Raise Steam's per-connection send rate clamp from vanilla's 153600 B/s. SendRateMin and SendRateMax are set to the SAME value (the Steam header requires it; there is no estimator). Without this a bigger SendWindow only builds standing queue. Live (re-applied globally and per existing connection)."); SendRateKBps = cfg.Bind<int>("04 SteamTransport", "SendRateKBps", 1024, new ConfigDescription("Send rate clamp per connection (KB/s). 150 = vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(150, 8192), Array.Empty<object>())); SendBufferBytes = cfg.Bind<int>("04 SteamTransport", "SendBufferBytes", 0, new ConfigDescription("Steam SendBufferSize (bytes). 0 leaves Steam's default 524288.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16777216), Array.Empty<object>())); F4Enabled = cfg.Bind<bool>("05 RelayThrottle", "Enabled", true, "Cap how often Default-type objects are relayed to a peer, by class and distance. Never drops anything: a held object stays dirty and goes next round. Never throttles: Prioritized/Solid/Terrain types, players, ships, carts, never-sent objects, ownership changes, force-sends. Live."); F4ClientUplink = cfg.Bind<bool>("05 RelayThrottle", "ApplyToClientUplink", true, "Also throttle the client's own uploads to the server with the same tables. Distance is measured from the NEAREST loaded player (yours or anyone else's), so nothing a player is looking at is ever slowed down."); NearMeters = cfg.Bind<float>("05 RelayThrottle", "NearMeters", 40f, new ConfigDescription("Inside this distance the Near rate applies.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 200f), Array.Empty<object>())); FarMeters = cfg.Bind<float>("05 RelayThrottle", "FarMeters", 100f, new ConfigDescription("Between Near and Far the Far rate applies; beyond it the Beyond rate.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 400f), Array.Empty<object>())); FishHz = cfg.Bind<string>("05 RelayThrottle", "FishHz", "6,3,2", "Max updates/s for Fish prefabs as near,far,beyond. 0 = unlimited."); CreatureHz = cfg.Bind<string>("05 RelayThrottle", "CreatureHz", "0,10,5", "Max updates/s for other Character prefabs as near,far,beyond. 0 = unlimited."); ItemHz = cfg.Bind<string>("05 RelayThrottle", "ItemHz", "0,5,3", "Max updates/s for ItemDrop prefabs as near,far,beyond. 0 = unlimited."); OtherHz = cfg.Bind<string>("05 RelayThrottle", "OtherHz", "0,0,0", "Max updates/s for every other Default-type prefab. 0 = unlimited (default: untouched)."); FloatingHz = cfg.Bind<string>("05 RelayThrottle", "FloatingHz", "0,5,3", "Max updates/s for floating objects that are neither items nor creatures (prefabs with a Floating component: logs, debris) as near,far,beyond. A log bobbing in the water otherwise talks every frame. 0 = unlimited."); EffectHz = cfg.Bind<string>("05 RelayThrottle", "EffectHz", "10,5,3", "Max updates/s for networked effect objects (prefab names starting with sfx_ or vfx_, e.g. a sound that follows a bird) as near,far,beyond. 0 = unlimited."); F5Enabled = cfg.Bind<bool>("06 Priority", "Enabled", true, "Server only. Replace vanilla's strict Terrain > Solid > Prioritized > Default send order with an age-fair score so a starving creature, cart or drop near the player can outrank a distant building backlog. Foreign-owned Prioritized objects and Terrain keep their top tiers. Live."); LiveRefPos = cfg.Bind<bool>("06 Priority", "LiveRefPos", true, "Use the peer's live character position (x/z) for interest sorting and the RelayThrottle distance bands instead of the position the client reports every 2 s. Active while Priority is on."); AgeWeight = cfg.Bind<float>("06 Priority", "AgeWeight", 1.5f, new ConfigDescription("Metres of priority per second an object has waited since its last sync to this peer (vanilla 1.5).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>())); SolidBiasMeters = cfg.Bind<float>("06 Priority", "SolidBiasMeters", 64f, new ConfigDescription("Solid (building) objects sort as if this many metres closer. Vanilla is effectively infinite.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f), Array.Empty<object>())); NewNearBonusMeters = cfg.Bind<float>("06 Priority", "NewNearBonusMeters", 200f, new ConfigDescription("Objects never sent to this peer that are inside NearMeters sort as if this many metres closer (drops and spawns next to the player pop in first).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f), Array.Empty<object>())); F6Enabled = cfg.Bind<bool>("07 Deadband", "Enabled", true, "Owner-side: skip position/rotation/velocity ZDO writes that moved less than a deadband since the LAST WRITE (not since last frame). Stops physics jitter and wave bobbing from producing per-frame updates. Slow movers still write every deadband of travel. Never applies to Prioritized objects, players, ships or carts. Live."); ItemPosMeters = cfg.Bind<float>("07 Deadband", "ItemPosMeters", 0.02f, new ConfigDescription("Position deadband for ItemDrop prefabs (m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>())); ItemRotDegrees = cfg.Bind<float>("07 Deadband", "ItemRotDegrees", 2f, new ConfigDescription("Rotation deadband for ItemDrop prefabs (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); CreaturePosMeters = cfg.Bind<float>("07 Deadband", "CreaturePosMeters", 0.01f, new ConfigDescription("Position deadband for creatures and fish (m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>())); CreatureRotDegrees = cfg.Bind<float>("07 Deadband", "CreatureRotDegrees", 1f, new ConfigDescription("Rotation deadband for creatures and fish (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); OtherPosMeters = cfg.Bind<float>("07 Deadband", "OtherPosMeters", 0.01f, new ConfigDescription("Position deadband for every other synced object (m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>())); OtherRotDegrees = cfg.Bind<float>("07 Deadband", "OtherRotDegrees", 1f, new ConfigDescription("Rotation deadband for every other synced object (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); VelocityMps = cfg.Bind<float>("07 Deadband", "VelocityMps", 0.05f, new ConfigDescription("Velocity deadband (m/s). When an object comes to rest its velocity is written as exactly zero once so receivers stop extrapolating.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); MooredShipPosMeters = cfg.Bind<float>("07 Deadband", "MooredShipPosMeters", 0.05f, new ConfigDescription("Position deadband for a ship with nobody aboard (m); a moored ship only bobs. A ship with a player aboard is never touched.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>())); MooredShipRotDegrees = cfg.Bind<float>("07 Deadband", "MooredShipRotDegrees", 2f, new ConfigDescription("Rotation deadband for a ship with nobody aboard (degrees).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); TelemetryEnabled = cfg.Bind<bool>("08 Telemetry", "Enabled", true, "Periodic per-peer stats: Steam RTT vs app RTT, window, queue, in-flight, rounds, refused rounds, candidates, sent, bytes, DEFERRED (candidates - sent), throttled, top prefabs. Live."); IntervalSeconds = cfg.Bind<float>("08 Telemetry", "IntervalSeconds", 10f, new ConfigDescription("Seconds between stats lines.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 300f), Array.Empty<object>())); PerPrefab = cfg.Bind<bool>("08 Telemetry", "PerPrefab", true, "Tally sent/received ZDO updates by prefab (a dictionary increment per update)."); TopN = cfg.Bind<int>("08 Telemetry", "TopN", 8, new ConfigDescription("How many prefabs to list per line.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 30), Array.Empty<object>())); StatsFile = cfg.Bind<bool>("08 Telemetry", "StatsFile", true, "Also append every stats/census/item line to BepInEx/BeanheimNet-stats.log, which survives restarts (LogOutput.log does not)."); StatsLogMaxMB = cfg.Bind<int>("08 Telemetry", "StatsLogMaxMB", 16, new ConfigDescription("Rotate the stats file to .1 when it exceeds this size.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 512), Array.Empty<object>())); CensusOnBoot = cfg.Bind<bool>("08 Telemetry", "CensusOnBoot", true, "Server: count every ZDO by prefab and type once the world is loaded (sliced over frames). Also available as 'bnet census'."); OwnerWriteStats = cfg.Bind<bool>("08 Telemetry", "OwnerWriteStats", false, "Tally owner-side ZDO revision bumps by prefab (which objects talk the most). Patches a hot path, so it is installed only when true at startup: needs a restart to turn on."); ItemReport = cfg.Bind<bool>("09 Items", "ReportEnabled", true, "Server: periodic report of ItemDrop ZDOs (count by prefab, age, in water, far from players). Live."); SweepMinutes = cfg.Bind<float>("09 Items", "SweepMinutes", 15f, new ConfigDescription("Minutes between sweeps (sliced over frames).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 1440f), Array.Empty<object>())); ItemCleanup = cfg.Bind<bool>("09 Items", "Cleanup", false, "DESTROY items that match every condition below. Each deletion is logged with prefab, position and age. Off = report only. Live. Hard rule that no setting can relax: an item with custom data (EpicLoot enchantments and other mod data), a quality above 1, or a max stack size of 1 (all gear) is never deleted."); MaxAgeHours = cfg.Bind<float>("09 Items", "MaxAgeHours", 6f, new ConfigDescription("Older than this many hours of played time (world time only advances with players online). Items never instantiated have no age and are never deleted.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1000f), Array.Empty<object>())); ProtectRadiusMeters = cfg.Bind<float>("09 Items", "ProtectRadiusMeters", 200f, new ConfigDescription("Never delete within this distance of any connected player (a player's active area reaches ~181 m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(50f, 2000f), Array.Empty<object>())); OnlyInWater = cfg.Bind<bool>("09 Items", "OnlyInWater", true, "Only delete items at or below sea level (floating or sunk)."); ProtectedPatterns = cfg.Bind<string>("09 Items", "ProtectedPatterns", "Trophy,Wishbone,DragonEgg,CryptKey,Totem", "Comma-separated substrings; a prefab name containing any of them is never deleted (case-insensitive)."); BaseProxyPrefabs = cfg.Bind<string>("09 Items", "BaseProxyPrefabs", "piece_workbench,bed,guard_stone,piece_bed02,fire_pit,forge,piece_chest_wood,piece_chest", "Server has no 'inside base' test; an item within BaseProxyRadiusMeters of any of these prefabs counts as in a base and is never deleted."); BaseProxyRadiusMeters = cfg.Bind<float>("09 Items", "BaseProxyRadiusMeters", 32f, new ConfigDescription("Radius for the base proxy check.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(8f, 128f), Array.Empty<object>())); MaxDeletionsPerSweep = cfg.Bind<int>("09 Items", "MaxDeletionsPerSweep", 200, new ConfigDescription("Safety cap per sweep.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 5000), Array.Empty<object>())); F9Enabled = cfg.Bind<bool>("10 ItemMerge", "Enabled", false, "Client side (the machine that owns the items). Merge same-name, same-quality, same-variant stackable drops within MergeRadiusMeters into one stack, up to the item's max stack size; the merged-away drops are destroyed. NEVER touches an item that carries custom data (every EpicLoot enchantment lives there), a quality above 1, or a max stack of 1 (all gear). Vanilla does the same at 4 m, once per item, and only while more than 200 drops are loaded (ItemDrop.AutoStackItems); this runs every MergeIntervalSeconds regardless. Live."); MergeRadiusMeters = cfg.Bind<float>("10 ItemMerge", "MergeRadiusMeters", 5f, new ConfigDescription("Merge radius (vanilla auto-stack uses 4).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 16f), Array.Empty<object>())); MergeIntervalSeconds = cfg.Bind<float>("10 ItemMerge", "MergeIntervalSeconds", 30f, new ConfigDescription("Seconds between merge attempts per item (checked from the item's 10 s slow update).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 600f), Array.Empty<object>())); TriggerCensus = cfg.Bind<bool>("11 Triggers", "Census", false, "One-shot: set true (hand-edit the server cfg; it is re-read within ConfigReloadSeconds) to run a world census now. Resets to false by itself. Same as 'bnet census'."); TriggerItemReport = cfg.Bind<bool>("11 Triggers", "ItemReport", false, "One-shot: run the item report now (no deletions). Same as 'bnet items'."); TriggerItemCleanupOnce = cfg.Bind<bool>("11 Triggers", "ItemCleanupOnce", false, "One-shot: run ONE item cleanup sweep with the configured criteria even if Cleanup is false. Every deletion is logged. Same as 'bnet cleanup run'."); TriggerStats = cfg.Bind<bool>("11 Triggers", "Stats", false, "One-shot: write a stats report now without waiting for the interval. Same as 'bnet stats'."); cfg.SettingChanged += delegate { if (!_suppress) { Refresh(); } }; RememberWriteTime(); Refresh(); } public static void Refresh() { Fish = HzTable.Parse(FishHz.Value); Creature = HzTable.Parse(CreatureHz.Value); Item = HzTable.Parse(ItemHz.Value); Other = HzTable.Parse(OtherHz.Value); Floating = HzTable.Parse(FloatingHz.Value); Effect = HzTable.Parse(EffectHz.Value); ProtectedPatternList = Split(ProtectedPatterns.Value); BaseProxyHashes = new HashSet<int>(); string[] array = Split(BaseProxyPrefabs.Value); foreach (string text in array) { BaseProxyHashes.Add(StringExtensionMethods.GetStableHashCode(text)); } try { Cfg.Changed?.Invoke(); } catch (Exception arg) { Plugin.Log.LogError((object)$"config change handler threw: {arg}"); } try { Cfg.Triggers?.Invoke(); } catch (Exception arg2) { Plugin.Log.LogError((object)$"trigger handler threw: {arg2}"); } } public static void ResetTrigger(ConfigEntry<bool> entry) { if (!entry.Value) { return; } try { _suppress = true; entry.Value = false; _cfg.Save(); RememberWriteTime(); } finally { _suppress = false; } } private static string[] Split(string csv) { List<string> list = new List<string>(); string[] array = csv.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { list.Add(text); } } return list.ToArray(); } public static HzTable? HzFor(PrefabClass.Cls cls) { HzTable value = cls switch { PrefabClass.Cls.Fish => Fish, PrefabClass.Cls.Creature => Creature, PrefabClass.Cls.Item => Item, PrefabClass.Cls.Other => Other, PrefabClass.Cls.Floating => Floating, PrefabClass.Cls.Effect => Effect, _ => default(HzTable), }; if (!value.IsUnlimited) { return value; } return null; } public static void DeadbandFor(PrefabClass.Cls cls, out float posMeters, out float rotDegrees) { switch (cls) { case PrefabClass.Cls.Item: case PrefabClass.Cls.Floating: posMeters = ItemPosMeters.Value; rotDegrees = ItemRotDegrees.Value; break; case PrefabClass.Cls.Fish: case PrefabClass.Cls.Creature: posMeters = CreaturePosMeters.Value; rotDegrees = CreatureRotDegrees.Value; break; default: posMeters = OtherPosMeters.Value; rotDegrees = OtherRotDegrees.Value; break; } } private static void RememberWriteTime() { try { _lastWrite = File.GetLastWriteTimeUtc(_cfg.ConfigFilePath); } catch { _lastWrite = DateTime.MinValue; } } public static void PollReload(float dt) { float value = ConfigReloadSeconds.Value; if (value <= 0f) { return; } _pollTimer += dt; if (_pollTimer < value) { return; } _pollTimer = 0f; DateTime lastWriteTimeUtc; try { lastWriteTimeUtc = File.GetLastWriteTimeUtc(_cfg.ConfigFilePath); } catch { return; } if (lastWriteTimeUtc == _lastWrite) { return; } _lastWrite = lastWriteTimeUtc; try { _suppress = true; _cfg.Reload(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("config reload failed: " + ex.Message)); return; } finally { _suppress = false; } Refresh(); Plugin.Log.LogInfo((object)("config reloaded from disk: " + Summary())); } public static void ForceReload() { try { _suppress = true; _cfg.Reload(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("config reload failed: " + ex.Message)); return; } finally { _suppress = false; } RememberWriteTime(); Refresh(); Plugin.Log.LogInfo((object)("config reloaded on request: " + Summary())); } public static string Summary() { return "window " + (F1Enabled.Value ? "on" : "off") + ", scheduler " + (F2Enabled.Value ? "on" : "off") + " fps " + ((ServerTargetFrameRate.Value > 0) ? ServerTargetFrameRate.Value.ToString() : "vanilla") + ", steam " + (F3Enabled.Value ? "on" : "off") + ", throttle " + (F4Enabled.Value ? "on" : "off") + ", priority " + (F5Enabled.Value ? "on" : "off") + ", deadband " + (F6Enabled.Value ? "on" : "off") + ", telemetry " + (TelemetryEnabled.Value ? "on" : "off") + ", items report " + (ItemReport.Value ? "on" : "off") + " cleanup " + (ItemCleanup.Value ? "ON" : "off") + ", merge " + (F9Enabled.Value ? "on" : "off"); } } internal static class Env { public static readonly bool IsHeadless = (int)SystemInfo.graphicsDeviceType == 4; private static readonly Dictionary<Type, FieldInfo?> OriginalField = new Dictionary<Type, FieldInfo>(); private static int _statusFailures; private static bool _statusDisabled; private static bool _statusLogged; public static int StatusNotOk; public static int StatusOk; public static string LastStatusResult = ""; public static bool IsServer { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } public static bool IsPluginLoaded(string guid) { return Chainloader.PluginInfos.ContainsKey(guid); } public static ZSteamSocket? Unwrap(ISocket? socket) { int num = 0; while (socket != null && num < 32) { ZSteamSocket val = (ZSteamSocket)(object)((socket is ZSteamSocket) ? socket : null); if (val != null) { return val; } Type type = ((object)socket).GetType(); if (!OriginalField.TryGetValue(type, out FieldInfo value)) { value = null; FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.Name == "Original" && typeof(ISocket).IsAssignableFrom(fieldInfo.FieldType)) { value = fieldInfo; break; } } if (value == null) { fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo2 in fields) { if (typeof(ISocket).IsAssignableFrom(fieldInfo2.FieldType)) { value = fieldInfo2; break; } } } OriginalField[type] = value; } if (value == null) { return null; } object? value2 = value.GetValue(socket); socket = (ISocket?)((value2 is ISocket) ? value2 : null); num++; } return null; } public unsafe static bool TryGetStatus(ISocket socket, out SteamNetConnectionRealTimeStatus_t status) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Invalid comparison between Unknown and I4 status = default(SteamNetConnectionRealTimeStatus_t); if (_statusDisabled || !Bindings.SteamSocketOk) { return false; } ZSteamSocket val = Unwrap(socket); if (val == null) { return false; } try { HSteamNetConnection val2 = Bindings.SteamCon.Invoke(val); if (val2 == HSteamNetConnection.Invalid) { return false; } SteamNetConnectionRealTimeLaneStatus_t val3 = default(SteamNetConnectionRealTimeLaneStatus_t); EResult val4 = (IsHeadless ? SteamGameServerNetworkingSockets.GetConnectionRealTimeStatus(val2, ref status, 0, ref val3) : SteamNetworkingSockets.GetConnectionRealTimeStatus(val2, ref status, 0, ref val3)); if ((int)val4 == 1) { _statusFailures = 0; StatusOk++; return true; } StatusNotOk++; LastStatusResult = ((object)(*(EResult*)(&val4))/*cast due to .constrained prefix*/).ToString(); return false; } catch (Exception ex) { if (++_statusFailures >= 5 && !_statusLogged) { _statusLogged = true; _statusDisabled = true; Plugin.Log.LogWarning((object)("Steam connection status unavailable (" + ex.GetType().Name + ": " + ex.Message + "); per-peer RTT and queue readings stay off for this process, send window stays vanilla.")); } return false; } } public static bool TrySetSteamInt(ESteamNetworkingConfigValue key, int value, ESteamNetworkingConfigScope scope, IntPtr scopeObj) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) GCHandle gCHandle = GCHandle.Alloc(value, GCHandleType.Pinned); try { return IsHeadless ? SteamGameServerNetworkingUtils.SetConfigValue(key, scope, scopeObj, (ESteamNetworkingConfigDataType)1, gCHandle.AddrOfPinnedObject()) : SteamNetworkingUtils.SetConfigValue(key, scope, scopeObj, (ESteamNetworkingConfigDataType)1, gCHandle.AddrOfPinnedObject()); } finally { gCHandle.Free(); } } public static bool TryGetSteamInt(ESteamNetworkingConfigValue key, out int value) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Invalid comparison between Unknown and I4 //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Invalid comparison between Unknown and I4 value = 0; int[] array = new int[1]; GCHandle gCHandle = GCHandle.Alloc(array, GCHandleType.Pinned); try { ulong num = 4uL; ESteamNetworkingConfigDataType val2 = default(ESteamNetworkingConfigDataType); ESteamNetworkingGetConfigValueResult val = (IsHeadless ? SteamGameServerNetworkingUtils.GetConfigValue(key, (ESteamNetworkingConfigScope)1, IntPtr.Zero, ref val2, gCHandle.AddrOfPinnedObject(), ref num) : SteamNetworkingUtils.GetConfigValue(key, (ESteamNetworkingConfigScope)1, IntPtr.Zero, ref val2, gCHandle.AddrOfPinnedObject(), ref num)); if ((int)val == 1 || (int)val == 2) { value = array[0]; return true; } return false; } finally { gCHandle.Free(); } } } [BepInPlugin("helix.beanheim.net", "BeanheimNet", "0.2.2")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "helix.beanheim.net"; public const string PluginName = "BeanheimNet"; public const string PluginVersion = "0.2.2"; internal static ManualLogSource Log = null; internal static Plugin Instance = null; internal static Harmony HarmonyInstance = null; internal static bool Refused; internal static string RefusedBy = ""; private static readonly string[] IncompatibleGuids = new string[6] { "org.bepinex.plugins.network", "akoozie.valheimtune", "MidnightsFX.NetworkPerformanceSystem", "redseiko.valheim.returntosender", "CW_Jesse.BetterNetworking", "VitByr.VBNetTweaks" }; private static readonly IFeature[] AllFeatures = new IFeature[10] { new TelemetryFeature(), new SteamTransportFeature(), new SendWindowFeature(), new SchedulerFeature(), new RelayThrottleFeature(), new PriorityFeature(), new DeadbandFeature(), new ItemSweepFeature(), new ItemMergeFeature(), new CommandsFeature() }; private static readonly List<string> Applied = new List<string>(); private static readonly List<string> Skipped = new List<string>(); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; Instance = this; Cfg.Init(((BaseUnityPlugin)this).Config); Log.LogInfo((object)("BeanheimNet 0.2.2 on " + (Env.IsHeadless ? "dedicated server (headless)" : "client") + ": " + Cfg.Summary())); } private void Start() { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown string[] incompatibleGuids = IncompatibleGuids; foreach (string text in incompatibleGuids) { if (Env.IsPluginLoaded(text)) { Refused = true; RefusedBy = RefusedBy + ((RefusedBy.Length > 0) ? ", " : "") + text; } } if (Refused) { Log.LogError((object)(RefusedBy + " rewrites the same send path. BeanheimNet installs TELEMETRY ONLY; every improvement feature is skipped. Remove one of the two mods; do not run both.")); } Bindings.Init(Log); HarmonyInstance = new Harmony("helix.beanheim.net"); IFeature[] allFeatures = AllFeatures; foreach (IFeature feature in allFeatures) { try { if (feature.Apply(HarmonyInstance, Log)) { Applied.Add(feature.Id); } else { Skipped.Add(feature.Id); } } catch (Exception arg) { Log.LogError((object)$"[{feature.Id}] threw while applying, skipping it: {arg}"); Skipped.Add(feature.Id); } } LogOtherOwners(); Log.LogInfo((object)(string.Format("{0} {1}: {2} applied ({3}), ", "BeanheimNet", "0.2.2", Applied.Count, string.Join(", ", Applied.ToArray())) + string.Format("{0} skipped ({1}).", Skipped.Count, string.Join(", ", Skipped.ToArray())))); StatsFile.WriteBootMarker(); } private static void LogOtherOwners() { foreach (MethodBase patchedMethod in Bindings.PatchedMethods) { try { Patches patchInfo = Harmony.GetPatchInfo(patchedMethod); if (patchInfo == null) { continue; } List<string> list = new List<string>(); foreach (string owner in patchInfo.Owners) { if (owner != "helix.beanheim.net") { list.Add(owner); } } if (list.Count > 0) { Log.LogInfo((object)("[owners] " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name + " is also patched by: " + string.Join(", ", list.ToArray()))); } } catch (Exception ex) { Log.LogDebug((object)("owner listing failed for " + patchedMethod.Name + ": " + ex.Message)); } } } private void Update() { float unscaledDeltaTime = Time.unscaledDeltaTime; try { Cfg.PollReload(unscaledDeltaTime); FrameCap.Tick(); Stats.Tick(unscaledDeltaTime); Census.Tick(); ItemSweepFeature.Tick(unscaledDeltaTime); } catch (Exception ex) { Stats.LogOnce("update", ex); } } private void OnDestroy() { StatsFile.Flush(); } } } namespace BeanheimNet.Util { internal static class FrameCap { private const int VanillaServerFps = 30; private static int _applied; public static void Tick() { if (!Env.IsHeadless) { return; } int num = (Cfg.Enabled.Value ? Cfg.ServerTargetFrameRate.Value : 0); if (num <= 0) { if (_applied > 0) { Application.targetFrameRate = 30; Plugin.Log.LogInfo((object)$"[Scheduler] server target frame rate restored to vanilla {30}."); _applied = 0; } } else if (Application.targetFrameRate != num) { Application.targetFrameRate = num; if (_applied != num) { Plugin.Log.LogInfo((object)$"[Scheduler] server target frame rate {30} -> {num} (vanilla requests 30; re-asserted while the setting is on)."); _applied = num; } } } } internal readonly struct HzTable { public readonly float Near; public readonly float Far; public readonly float Beyond; public bool IsUnlimited { get { if (Near <= 0f && Far <= 0f) { return Beyond <= 0f; } return false; } } public HzTable(float near, float far, float beyond) { Near = near; Far = far; Beyond = beyond; } public static HzTable Parse(string text) { try { string[] array = text.Split(new char[1] { ',' }); if (array.Length != 3) { return default(HzTable); } return new HzTable(P(array[0]), P(array[1]), P(array[2])); } catch { return default(HzTable); } static float P(string s) { return Math.Max(0f, float.Parse(s.Trim(), CultureInfo.InvariantCulture)); } } public float For(float distance, float nearM, float farM) { if (!(distance < nearM)) { if (!(distance < farM)) { return Beyond; } return Far; } return Near; } } internal static class PrefabClass { internal enum Cls : byte { Unknown, Other, Exempt, Fish, Creature, Item, Terrain, Floating, Effect } private static readonly Dictionary<int, Cls> Cache = new Dictionary<int, Cls>(); private static readonly Dictionary<int, string> Names = new Dictionary<int, string>(); public static Cls Get(int prefabHash) { if (Cache.TryGetValue(prefabHash, out var value)) { return value; } ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return Cls.Unknown; } GameObject prefab = instance.GetPrefab(prefabHash); if ((Object)(object)prefab == (Object)null) { Cache[prefabHash] = Cls.Other; return Cls.Other; } Names[prefabHash] = ((Object)prefab).name; value = Cls.Other; if ((Object)(object)prefab.GetComponent<Player>() != (Object)null || (Object)(object)prefab.GetComponent<Ship>() != (Object)null || (Object)(object)prefab.GetComponent<Vagon>() != (Object)null) { value = Cls.Exempt; } else if ((Object)(object)prefab.GetComponent<Fish>() != (Object)null) { value = Cls.Fish; } else if ((Object)(object)prefab.GetComponent<Character>() != (Object)null) { value = Cls.Creature; } else if ((Object)(object)prefab.GetComponent<ItemDrop>() != (Object)null) { value = Cls.Item; } else if ((Object)(object)prefab.GetComponent<TerrainComp>() != (Object)null) { value = Cls.Terrain; } else if ((Object)(object)prefab.GetComponent<Floating>() != (Object)null) { value = Cls.Floating; } else if (((Object)prefab).name.StartsWith("sfx_", StringComparison.Ordinal) || ((Object)prefab).name.StartsWith("vfx_", StringComparison.Ordinal)) { value = Cls.Effect; } Cache[prefabHash] = value; return value; } public static string Name(int prefabHash) { if (Names.TryGetValue(prefabHash, out string value)) { return value; } Get(prefabHash); if (Names.TryGetValue(prefabHash, out value)) { return value; } return "hash:" + prefabHash; } public static void Clear() { Cache.Clear(); Names.Clear(); } } } namespace BeanheimNet.Telemetry { internal static class Census { private const int SliceSize = 20000; private static bool _bootDone; private static float _armTimer; private static List<ZDO>? _work; private static int _idx; private static string _reason = ""; private static readonly Dictionary<int, int> ByPrefab = new Dictionary<int, int>(); private static readonly int[] ByType = new int[4]; private static int _total; private static int _items; private static int _creatures; private static int _fish; private static int _terrain; private static int _persistent; private static int _distant; private static int _owned; private static long _terrainBytes; private static int _terrainMax; public static bool Running => _work != null; public static void Tick() { if (_work != null) { Slice(); } else if (!_bootDone && Cfg.Enabled.Value && Cfg.CensusOnBoot.Value && Env.IsServer && ZDOMan.instance != null && !((Object)(object)ZNetScene.instance == (Object)null) && ZDOMan.instance.NrOfObjects() != 0) { _armTimer += Time.unscaledDeltaTime; if (!(_armTimer < 30f)) { _bootDone = true; Start("boot"); } } } public static bool Start(string reason) { if (_work != null || !Bindings.ZdoManOk || ZDOMan.instance == null) { return false; } try { _work = new List<ZDO>(Bindings.ObjectsById.Invoke(ZDOMan.instance).Values); } catch (Exception ex) { Stats.LogOnce("census", ex); _work = null; return false; } _idx = 0; _reason = reason; ByPrefab.Clear(); Array.Clear(ByType, 0, ByType.Length); _total = (_items = (_creatures = (_fish = (_terrain = (_persistent = (_distant = (_owned = 0))))))); _terrainBytes = 0L; _terrainMax = 0; Plugin.Log.LogInfo((object)$"census ({reason}): scanning {_work.Count:N0} ZDOs, {20000:N0} per frame."); return true; } private static void Slice() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected I4, but got Unknown List<ZDO> work = _work; int num = Math.Min(_idx + 20000, work.Count); try { while (_idx < num) { ZDO val = work[_idx]; if (val != null && val.IsValid()) { _total++; int num2 = (int)val.Type; if (num2 >= 0 && num2 < 4) { ByType[num2]++; } if (val.Persistent) { _persistent++; } if (val.Distant) { _distant++; } if (val.HasOwner()) { _owned++; } int prefab = val.GetPrefab(); Stats.Inc(ByPrefab, prefab); switch (PrefabClass.Get(prefab)) { case PrefabClass.Cls.Item: _items++; break; case PrefabClass.Cls.Fish: _fish++; _creatures++; break; case PrefabClass.Cls.Creature: _creatures++; break; case PrefabClass.Cls.Terrain: { _terrain++; byte[] byteArray = val.GetByteArray(ZDOVars.s_TCData, (byte[])null); if (byteArray != null) { _terrainBytes += byteArray.Length; if (byteArray.Length > _terrainMax) { _terrainMax = byteArray.Length; } } break; } } } _idx++; } } catch (Exception ex) { Stats.LogOnce("census.slice", ex); _work = null; return; } if (_idx >= work.Count) { Finish(); } } private static void Finish() { _work = null; int num = 0; int num2 = 0; List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>(ByPrefab); foreach (KeyValuePair<int, int> item in list) { if (PrefabClass.Name(item.Key).EndsWith("_planned", StringComparison.Ordinal)) { num += item.Value; num2++; } } list.Sort((KeyValuePair<int, int> a, KeyValuePair<int, int> b) => b.Value.CompareTo(a.Value)); List<string> list2 = new List<string>(); list2.Add($"census ({_reason}): {_total:N0} ZDOs | types Default {ByType[0]:N0} Prioritized {ByType[1]:N0} Solid {ByType[2]:N0} Terrain {ByType[3]:N0} | " + $"persistent {_persistent:N0} distant {_distant:N0} owned {_owned:N0} | items {_items:N0} | creatures {_creatures:N0} (fish {_fish:N0}) | " + $"planned ghosts {num:N0} ({num2} prefabs) | terrain blobs {_terrain:N0} total {(float)_terrainBytes / 1024f:F1}KB max {(float)_terrainMax / 1024f:F1}KB"); List<string> list3 = list2; StringBuilder stringBuilder = new StringBuilder("census top: "); for (int num3 = 0; num3 < list.Count && num3 < 40; num3++) { if (num3 > 0) { stringBuilder.Append(", "); } stringBuilder.Append(PrefabClass.Name(list[num3].Key)).Append(' ').Append(list[num3].Value.ToString("N0")); } list3.Add(stringBuilder.ToString()); foreach (string item2 in list3) { Plugin.Log.LogInfo((object)item2); StatsFile.Append(item2); } } } internal sealed class PeerStats { public long Uid; public string Name = "?"; public ZRpc? Rpc; public int SteamPing; public int SteamRate; public int Inflight; public int Window = 10240; public int LastQueue = -1; public int AppRttMs; public float PingSentAt; public int CtxSent0; public int CtxData0; public long CtxT0; public bool CtxFlush; public long QueueSum; public int QueueN; public int QueueMax; public int InflightMax; public int UnsentMax; public int Rounds; public int Refused; public long CandSum; public int CandMax; public int Sent; public long Bytes; public int Deferred; public int Throttled; public double SendMsSum; public double SendMsMax; public readonly Dictionary<int, int> SentByPrefab = new Dictionary<int, int>(); public float AboveFloorSince = -1f; public bool Draining; public float DrainStart; public int Drains; public int DrainAborts; public void ResetInterval() { QueueSum = 0L; QueueN = 0; QueueMax = 0; InflightMax = 0; UnsentMax = 0; Rounds = 0; Refused = 0; CandSum = 0L; CandMax = 0; Sent = 0; Bytes = 0L; Deferred = 0; Throttled = 0; SendMsSum = 0.0; SendMsMax = 0.0; Drains = 0; DrainAborts = 0; SentByPrefab.Clear(); } } internal static class Stats { private static readonly Dictionary<long, PeerStats> Peers = new Dictionary<long, PeerStats>(); private static readonly Dictionary<ZRpc, PeerStats> ByRpc = new Dictionary<ZRpc, PeerStats>(); public static readonly Dictionary<int, int> RecvByPrefab = new Dictionary<int, int>(); public static readonly Dictionary<int, int> OwnerWritesByPrefab = new Dictionary<int, int>(); public static int RecvCount; public static int SchedulerFrames; public static int SchedulerServed; public static int SchedulerBudgetBreaks; public static int DbPos; public static int DbRot; public static int DbVel; public static int DbZero; public static int DbBody; private static float _acc; private static int _frames; private static float _frameSum; private static float _frameMax; private static int _frames50; private static int _frames100; private static int _gcBase = -1; private static TimeSpan _cpuBase; private static float _cpuWallBase = -1f; private static bool _cpuUnavailable; private static Process? _proc; private static readonly Dictionary<string, float> OnceAt = new Dictionary<string, float>(); public static IEnumerable<PeerStats> All => Peers.Values; public static PeerStats PeerFor(ZNetPeer np) { if (!Peers.TryGetValue(np.m_uid, out PeerStats value)) { value = new PeerStats { Uid = np.m_uid }; Peers[np.m_uid] = value; } if (!string.IsNullOrEmpty(np.m_playerName)) { value.Name = np.m_playerName; } if (np.m_rpc != null && value.Rpc != np.m_rpc) { value.Rpc = np.m_rpc; ByRpc[np.m_rpc] = value; } return value; } public static PeerStats? ByRpcOrNull(ZRpc rpc) { if (!ByRpc.TryGetValue(rpc, out PeerStats value)) { return null; } return value; } public static void Inc(Dictionary<int, int> d, int key) { d.TryGetValue(key, out var value); d[key] = value + 1; } public static void LogOnce(string key, Exception ex) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!OnceAt.TryGetValue(key, out var value) || !(realtimeSinceStartup - value < 60f)) { OnceAt[key] = realtimeSinceStartup; Plugin.Log.LogError((object)$"[{key}] threw (vanilla behaviour used for that call): {ex}"); } } public static void Tick(float dt) { if (_cpuWallBase < 0f) { ResetBases(); } _frames++; _frameSum += dt; if (dt > _frameMax) { _frameMax = dt; } if (dt > 0.05f) { _frames50++; } if (dt > 0.1f) { _frames100++; } _acc += dt; float value = Cfg.IntervalSeconds.Value; if (!(_acc < value)) { _acc = 0f; if (Cfg.Enabled.Value && Cfg.TelemetryEnabled.Value && (Object)(object)ZNet.instance != (Object)null) { Report(); } else { ResetAll(); } } } public static void Report() { foreach (string item in BuildReport(reset: true)) { Plugin.Log.LogInfo((object)item); StatsFile.Append(item); } } private static void ResetAll() { foreach (PeerStats value in Peers.Values) { value.ResetInterval(); } RecvByPrefab.Clear(); OwnerWritesByPrefab.Clear(); RecvCount = 0; SchedulerFrames = (SchedulerServed = (SchedulerBudgetBreaks = 0)); DbPos = (DbRot = (DbVel = (DbZero = (DbBody = 0)))); _frames = 0; _frameSum = 0f; _frameMax = 0f; _frames50 = 0; _frames100 = 0; ResetBases(); } private static void ResetBases() { _gcBase = GC.CollectionCount(0); _cpuBase = ReadCpu(); _cpuWallBase = Time.realtimeSinceStartup; } private static TimeSpan ReadCpu() { if (_cpuUnavailable) { return TimeSpan.Zero; } try { if (_proc == null) { _proc = Process.GetCurrentProcess(); } _proc.Refresh(); return _proc.TotalProcessorTime; } catch { _cpuUnavailable = true; return TimeSpan.Zero; } } private static string ProcessLine() { int num = ((_gcBase >= 0) ? (GC.CollectionCount(0) - _gcBase) : 0); long num2 = GC.GetTotalMemory(forceFullCollection: false) / 1048576; string arg = "?"; if (!_cpuUnavailable && _cpuWallBase >= 0f) { TimeSpan timeSpan = ReadCpu(); float num3 = Time.realtimeSinceStartup - _cpuWallBase; if (!_cpuUnavailable && num3 > 0.5f) { arg = ((timeSpan - _cpuBase).TotalSeconds / (double)num3 * 100.0).ToString("F0"); } } return $"gc {num} heap {num2}MB cpu {arg}% (process, all threads)"; } public static List<string> BuildReport(bool reset) { List<string> list = new List<string>(); float num = Math.Max(0.001f, _frameSum); int num2 = 0; if ((Object)(object)ZNet.instance != (Object)null) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady()) { num2++; } } } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"stats {num:F0}s | peers {num2} | "); if (_frames > 0) { stringBuilder.Append($"frame avg {_frameSum / (float)_frames * 1000f:F1}ms max {_frameMax * 1000f:F0}ms frames >50ms {_frames50} >100ms {_frames100} | "); } stringBuilder.Append(ProcessLine()).Append(" | "); stringBuilder.Append($"recv zdo {(float)RecvCount / num:F0}/s"); if (Cfg.PerPrefab.Value && RecvByPrefab.Count > 0) { stringBuilder.Append(" top: ").Append(TopPrefabs(RecvByPrefab, Cfg.TopN.Value)); } if (Env.StatusNotOk > 0) { stringBuilder.Append($" | steam-status ok {Env.StatusOk} not-ok {Env.StatusNotOk} last {Env.LastStatusResult}"); } Env.StatusOk = 0; Env.StatusNotOk = 0; if (SchedulerFrames > 0) { stringBuilder.Append($" | sched frames {SchedulerFrames} served {SchedulerServed} budget-breaks {SchedulerBudgetBreaks}"); } if (DbPos + DbRot + DbVel + DbBody > 0) { stringBuilder.Append($" | deadband pos {DbPos} rot {DbRot} vel {DbVel} zero-writes {DbZero} bodyvel {DbBody}"); } if (ItemMergeFeature.Merges > 0) { stringBuilder.Append($" | merges {ItemMergeFeature.Merges} ({ItemMergeFeature.Absorbed} items)"); ItemMergeFeature.Merges = 0; ItemMergeFeature.Absorbed = 0; } list.Add(stringBuilder.ToString()); if (Cfg.OwnerWriteStats.Value && OwnerWritesByPrefab.Count > 0) { int num3 = 0; foreach (int value2 in OwnerWritesByPrefab.Values) { num3 += value2; } list.Add($"owner writes {(float)num3 / num:F0}/s top: {TopPrefabs(OwnerWritesByPrefab, Cfg.TopN.Value)}"); } List<long> list2 = new List<long>(); foreach (PeerStats value3 in Peers.Values) { if (!((Object)(object)ZNet.instance != (Object)null) || ZNet.instance.GetPeer(value3.Uid) == null) { if (value3.Rounds == 0) { list2.Add(value3.Uid); } if (value3.Rounds == 0) { continue; } } stringBuilder.Clear(); stringBuilder.Append($" peer {value3.Name}: rtt {value3.SteamPing}ms app {value3.AppRttMs}ms steam-rate {value3.SteamRate / 1024}KB/s win {value3.Window}"); if (value3.QueueN > 0) { stringBuilder.Append($" q avg {(float)(value3.QueueSum / value3.QueueN) / 1024f:F1}K max {(float)value3.QueueMax / 1024f:F1}K inflight max {(float)value3.InflightMax / 1024f:F1}K"); } if (value3.UnsentMax > 0) { stringBuilder.Append($" unsent max {(float)value3.UnsentMax / 1024f:F1}K"); } stringBuilder.Append($" | rounds {value3.Rounds} refused {value3.Refused}"); int num4 = value3.Rounds - value3.Refused; if (num4 > 0) { stringBuilder.Append($" cand avg {value3.CandSum / num4} max {value3.CandMax}"); } stringBuilder.Append($" sent {value3.Sent} ({(float)value3.Sent / num:F0}/s) {(float)value3.Bytes / num / 1024f:F1}KB/s deferred {value3.Deferred} throttled {value3.Throttled}"); if (value3.Rounds > 0) { stringBuilder.Append($" | send {value3.SendMsSum / (double)value3.Rounds:F2}ms/call max {value3.SendMsMax:F1}ms"); } if (value3.Drains > 0 || value3.DrainAborts > 0) { stringBuilder.Append($" | drains {value3.Drains} aborted {value3.DrainAborts}"); } if (Cfg.PerPrefab.Value && value3.SentByPrefab.Count > 0) { stringBuilder.Append(" | top: ").Append(TopPrefabs(value3.SentByPrefab, Cfg.TopN.Value)); } list.Add(stringBuilder.ToString()); } foreach (long item in list2) { if (Peers.TryGetValue(item, out PeerStats value) && value.Rpc != null) { ByRpc.Remove(value.Rpc); } Peers.Remove(item); } if (reset) { ResetAll(); } return list; } public static string TopPrefabs(Dictionary<int, int> d, int n) { int num = 0; foreach (int value in d.Values) { num += value; } if (num == 0) { return "-"; } List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>(d); list.Sort((KeyValuePair<int, int> a, KeyValuePair<int, int> b) => b.Value.CompareTo(a.Value)); StringBuilder stringBuilder = new StringBuilder(); for (int num2 = 0; num2 < list.Count && num2 < n; num2++) { if (num2 > 0) { stringBuilder.Append(' '); } stringBuilder.Append(PrefabClass.Name(list[num2].Key)).Append(' ').Append((100f * (float)list[num2].Value / (float)num).ToString("F0")) .Append('%'); } return stringBuilder.ToString(); } } internal static class StatsFile { private static int _sinceSizeCheck; private static bool _failed; private static string Path => System.IO.Path.Combine(Paths.BepInExRootPath, "BeanheimNet-stats.log"); public static void Append(string line) { if (_failed || !Cfg.StatsFile.Value) { return; } try { if (++_sinceSizeCheck >= 50) { _sinceSizeCheck = 0; FileInfo fileInfo = new FileInfo(Path); if (fileInfo.Exists && fileInfo.Length > (long)Cfg.StatsLogMaxMB.Value * 1024L * 1024) { string text = Path + ".1"; if (File.Exists(text)) { File.Delete(text); } File.Move(Path, text); } } File.AppendAllText(Path, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + " " + line + Environment.NewLine); } catch (Exception ex) { _failed = true; Plugin.Log.LogWarning((object)("stats file disabled: " + ex.Message)); } } public static void WriteBootMarker() { Append("=== boot BeanheimNet 0.2.2 " + (Env.IsHeadless ? "server" : "client") + " | " + Cfg.Summary() + " | refused=" + (Plugin.Refused ? Plugin.RefusedBy : "no")); } public static void Flush() { } } internal sealed class TelemetryFeature : IFeature { public string Id => "Telemetry"; public string Description => "per-peer send-path statistics, receive tally, app RTT"; public bool Apply(Harmony harmony, ManualLogSource log) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown //IL_006d: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Expected O, but got Unknown //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Expected O, but got Unknown if (!Bindings.ZdoManOk) { log.LogWarning((object)("[" + Id + "] skipped: " + Bindings.ZdoManReason)); return false; } harmony.Patch((MethodBase)Bindings.SendZDOsMi, new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "SendPrefix", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "SendPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Bindings.PatchedMethods.Add(Bindings.SendZDOsMi); harmony.Patch((MethodBase)Bindings.DeserializeMi, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "DeserializePostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Bindings.PatchedMethods.Add(Bindings.DeserializeMi); if (Bindings.RpcOk) { harmony.Patch((MethodBase)Bindings.UpdatePingMi, new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "UpdatePingPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)Bindings.ReceivePingMi, new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "ReceivePingPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Bindings.PatchedMethods.Add(Bindings.UpdatePingMi); Bindings.PatchedMethods.Add(Bindings.ReceivePingMi); } else { log.LogWarning((object)("[" + Id + "] app RTT off: " + Bindings.RpcReason)); } if (Cfg.OwnerWriteStats.Value) { if (Bindings.IncreaseDataRevisionMi != null) { harmony.Patch((MethodBase)Bindings.IncreaseDataRevisionMi, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(TelemetryFeature), "IncreaseRevisionPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Bindings.PatchedMethods.Add(Bindings.IncreaseDataRevisionMi); log.LogInfo((object)("[" + Id + "] owner-write tally installed (hot path; OwnerWriteStats=true at startup).")); } else { log.LogWarning((object)("[" + Id + "] OwnerWriteStats requested but ZDO.IncreaseDataRevision not found.")); } } log.LogInfo((object)string.Format("[{0}] applying: stats every {1:F0}s, file {2}.", Id, Cfg.IntervalSeconds.Value, Cfg.StatsFile.Value ? "on" : "off")); return true; } private static void SendPrefix(ZDOMan __instance, object peer, bool flush, out PeerStats? __state) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) __state = null; if (!Cfg.Enabled.Value) { return; } try { ZNetPeer val = Bindings.PeerNetPeer.Invoke(peer); if (val == null || val.m_socket == null) { return; } PeerStats peerStats = Stats.PeerFor(val); float time = Time.time; int num = -1; int num2 = Bindings.UnsentBytes(val.m_socket); if (num2 > peerStats.UnsentMax) { peerStats.UnsentMax = num2; } if (Env.TryGetStatus(val.m_socket, out var status)) { peerStats.SteamPing = status.m_nPing; peerStats.SteamRate = status.m_nSendRateBytesPerSecond; peerStats.Inflight = status.m_cbSentUnackedReliable; num = status.m_cbPendingReliable + status.m_cbPendingUnreliable + status.m_cbSentUnackedReliable + num2; } else { peerStats.SteamPing = 0; } peerStats.LastQueue = num; if (num >= 0) { peerStats.QueueSum += num; peerStats.QueueN++; if (num > peerStats.QueueMax) { peerStats.QueueMax = num; } if (peerStats.Inflight > peerStats.InflightMax) { peerStats.InflightMax = peerStats.Inflight; } SendWindowFeature.ObserveQueue(peerStats, num, time); } if (!SendWindowFeature.Active) { peerStats.Window = 10240; } Bindings.TempToSync.Invoke(__instance).Clear(); peerStats.CtxSent0 = Bindings.ZdosSent.Invoke(__instance); peerStats.CtxData0 = ((val.m_rpc != null) ? Bindings.RpcSentData.Invoke(val.m_rpc) : 0); peerStats.CtxT0 = Stopwatch.GetTimestamp(); peerStats.CtxFlush = flush; __state = peerStats; } catch (Exception ex) { Stats.LogOnce("telemetry.send-prefix", ex); } } private static void SendPostfix(ZDOMan __instance, bool __result, PeerStats? __state) { if (__state == null) { return; } try { __state.Rounds++; double num = (double)(Stopwatch.GetTimestamp() - __state.CtxT0) * 1000.0 / (double)Stopwatch.Frequency; __state.SendMsSum += num; if (num > __state.SendMsMax) { __state.SendMsMax = num; } int window = __state.Window; int currentHeadroom = SendWindowFeature.CurrentHeadroom; if (__state.LastQueue >= 0 && ((!__state.CtxFlush && __state.LastQueue > window) || window - __state.LastQueue < currentHeadroom)) { __state.Refused++; return; } int num2 = Bindings.ZdosSent.Invoke(__instance) - __state.CtxSent0; int num3 = ((__state.Rpc != null) ? (Bindings.RpcSentData.Invoke(__state.Rpc) - __state.CtxData0) : 0); List<ZDO> list = Bindings.TempToSync.Invoke(__instance); int count = list.Count; __state.CandSum += count; if (count > __state.CandMax) { __state.CandMax = count; } __state.Sent += num2; __state.Bytes += num3; if (count > num2) { __state.Deferred += count - num2; } if (Cfg.PerPrefab.Value) { int num4 = Math.Min(num2, count); for (int i = 0; i < num4; i++) { Stats.Inc(__state.SentByPrefab, list[i].GetPrefab()); } } } catch (Exception ex) { Stats.LogOnce("telemetry.send-postfix", ex); } } private static void DeserializePostfix(ZDO __instance) { if (Cfg.Enabled.Value && Cfg.TelemetryEnabled.Value) { Stats.RecvCount++; if (Cfg.PerPrefab.Value) { Stats.Inc(Stats.RecvByPrefab, __instance.GetPrefab()); } } } private static void UpdatePingPrefix(ZRpc __instance, float dt) { try { if (!(Bindings.RpcPingTimer.Invoke(__instance) + dt <= Bindings.PingInterval)) { PeerStats peerStats = Stats.ByRpcOrNull(__instance); if (peerStats != null) { peerStats.PingSentAt = Time.realtimeSinceStartup; } } } catch (Exception ex) { Stats.LogOnce("telemetry.ping", ex); } } private static void ReceivePingPrefix(ZRpc __instance, ZPackage package) { try { int pos = package.GetPos(); bool flag; try { flag = package.ReadBool(); } finally { package.SetPos(pos); } if (!flag) { PeerStats peerStats = Stats.ByRpcOrNull(__instance); if (peerStats != null && peerStats.PingSentAt > 0f) { peerStats.AppRttMs = (int)((Time.realtimeSinceStartup - peerStats.PingSentAt) * 1000f); peerStats.PingSentAt = 0f; } } } catch (Exception ex) { Stats.LogOnce("telemetry.pong", ex); } } private static void IncreaseRevisionPostfix(ZDO __instance) { if (Cfg.Enabled.Value && Cfg.OwnerWriteStats.Value) { Stats.Inc(Stats.OwnerWritesByPrefab, __instance.GetPrefab()); } } } } namespace BeanheimNet.Features { internal sealed class CommandsFeature : IFeature { [CompilerGenerated] private static class <>O { public static Action <0>__RunTriggers; public static Action<string> <1>__Say; public static ConsoleEvent <2>__RunConsole; } public string Id => "Commands"; public string Description => "bnet console command and cfg triggers"; public bool Apply(Harmony harmony, ManualLogSource log) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown Cfg.Triggers += RunTriggers; if (Bindings.InitTerminalMi == null) { log.LogWarning((object)("[" + Id + "] cfg triggers only: Terminal.InitTerminal not found.")); return true; } harmony.Patch((MethodBase)Bindings.InitTerminalMi, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CommandsFeature), "RegisterCommands", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); try { FieldInfo fieldInfo = AccessTools.Field(typeof(Terminal), "m_terminalInitialized"); bool flag = default(bool); int num; if (fieldInfo != null) { object value = fieldInfo.GetValue(null); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { RegisterCommands(); } } catch (Exception ex) { log.LogDebug((object)("[" + Id + "] terminal state check failed: " + ex.Message)); } log.LogInfo((object)("[" + Id + "] applying: 'bnet help' in the in-game console; [11 Triggers] in the cfg for the dedicated server.")); return true; } private static void RunTriggers() { if (Cfg.TriggerCensus.Value) { Cfg.ResetTrigger(Cfg.TriggerCensus); Run("census", "", Say); } if (Cfg.TriggerItemReport.Value) { Cfg.ResetTrigger(Cfg.TriggerItemReport); Run("items", "", Say); } if (Cfg.TriggerItemCleanupOnce.Value) { Cfg.ResetTrigger(Cfg.TriggerItemCleanupOnce); Run("cleanup", "run", Say); } if (Cfg.TriggerStats.Value) { Cfg.ResetTrigger(Cfg.TriggerStats); Run("stats", "", Say); } } private static void Say(string line) { Plugin.Log.LogInfo((object)line); } private static void RegisterCommands() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown try { object obj = <>O.<2>__RunConsole; if (obj == null) { ConsoleEvent val = RunConsole; <>O.<2>__RunConsole = val; obj = (object)val; } new ConsoleCommand("bnet", "BeanheimNet: bnet stats | census | items | cleanup dryrun|run | reload | config", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } catch (Exception ex) { Stats.LogOnce("commands", ex); } } private static void RunConsole(ConsoleEventArgs args) { string sub = ((args.Args != null && args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "help"); string arg = ((args.Args != null && args.Args.Length > 2) ? args.Args[2].ToLowerInvariant() : ""); Run(sub, arg, delegate(string line) { Plugin.Log.LogInfo((object)line); try { Terminal context = args.Context; if (context != null) { context.AddString(line); } } catch { } }); } public static void Run(string sub, string arg, Action<string> say) { switch (sub) { case "stats": { foreach (string item in Stats.BuildReport(reset: false)) { say(item); } break; } case "census": say(Census.Start("command") ? "census started; results follow in the log within a few seconds." : "census not started (already running, or ZDOMan/bindings unavailable)."); break; case "items": say(ItemSweepFeature.RunNow(cleanup: false, "command") ? "item sweep started (report only); results follow in the log." : "sweep not started (already running, or not the server)."); break; case "cleanup": if (arg == "run") { say(ItemSweepFeature.RunNow(cleanup: true, "command") ? "item CLEANUP sweep started with the configured criteria; every deletion is logged." : "sweep not started (already running, or not the server)."); } else { say(ItemSweepFeature.RunNow(cleanup: false, "command dryrun") ? "dry run started; candidates are counted, nothing is deleted." : "sweep not started (already running, or not the server)."); } break; case "reload": Cfg.ForceReload(); say("config: " + Cfg.Summary()); break; case "config": say("config: " + Cfg.Summary()); break; default: say("bnet stats | census | items | cleanup dryrun|run | reload | config"); break; } } } internal sealed class DeadbandFeature : IFeature { private sealed class State { public bool HasPos; public bool HasRot; public bool HasVel; public bool HasBody; public bool RestoreBodyFlag; public bool ShipChecked; public Ship? Ship; public Vector3 LastPos; public Vector3 LastVel; public Vector3 LastBodyVel; public Vector3 LastBodyAVel; public Quaternion LastRot; public void Reset() { HasPos = (HasRot = (HasVel = (HasBody = false))); } } private static readonly ConditionalWeakTable<ZSyncTransform, State> Table = new ConditionalWeakTable<ZSyncTransform, State>(); private static readonly ConditionalWeakTable<ZSyncTransform, State>.CreateValueCallback Create = (ZSyncTransform _) => new State(); public string Id => "Deadband"; public string Description => "owner-side position/rotation/velocity write deadbands"; public bool Apply(Harmony harmony, ManualLogSource log) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00b0: Expected O, but got Unknown if (Plugin.Refused) { log.LogWarning((object)("[" + Id + "] skipped: refused (" + Plugin.RefusedBy + ").")); return false; } if (!Bindings.SyncTransformOk) { log.LogWarning((object)("[" + Id + "] skipped: " + Bindings.SyncTransformReason)); return false; } harmony.Patch((MethodBase)Bindings.OwnerSyncMi, new HarmonyMethod(AccessTools.Method(typeof(DeadbandFeature), "Prefix", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.Method(typeof(DeadbandFeature), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Bindings.PatchedMethods.Add(Bindings.OwnerSyncMi); log.LogInfo((object)("[" + Id + "] applying: " + (Cfg.F6Enabled.Value ? $"item {Cfg.ItemPosMeters.Value}m/{Cfg.ItemRotDegrees.Value}deg, creature {Cfg.CreaturePosMeters.Value}m/{Cfg.CreatureRotDegrees.Value}deg, vel {Cfg.VelocityMps.Value}m/s" : "off until enabled") + (Bindings.ShipOk ? $"; moored ship {Cfg.MooredShipPosMeters.Value}m/{Cfg.MooredShipRotDegrees.Value}deg" : ("; moored-ship deadband off (" + Bindings.ShipReason + ")")) + ".")); return true; } private static void Prefix(ZSyncTransform __instance, out State? __state) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Invalid comparison between Unknown and I4 //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_0392: Unknown result type (might be due to invalid IL or missing references) //IL_0397: Unknown result type (might be due to invalid IL or missing references) //IL_039b: Unknown result type (might be due to invalid IL or missing references) //IL_03a0: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_03fb: Unknown result type (might be due to invalid IL or missing references) //IL_03fd: Unknown result type (might be due to invalid IL or missing references) //IL_0403: Unknown result type (might be due to invalid IL or missing references) //IL_0405: Unknown result type (might be due to invalid IL or missing references) //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03ad: Unknown result type (might be due to invalid IL or missing references) //IL_03b2: Unknown result type (might be due to invalid IL or missing references) //IL_03b7: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_03c4: Unknown result type (might be due to invalid IL or missing references) //IL_03c7: Unknown result type (might be due to invalid IL or missing references) //IL_03cc: Unknown result type (might be due to invalid IL or missing references) //IL_03d1: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_036a: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_033e: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Unknown result type (might be due to invalid IL or missing references) __state = null; if (!Cfg.Enabled.Value || !Cfg.F6Enabled.Value) { return; } try { ZNetView val = Bindings.StNview.Invoke(__instance); ZDO val2 = (((Object)(object)val != (Object)null) ? val.GetZDO() : null); if (val2 == null || !val2.IsOwner()) { return; } State value = Table.GetValue(__instance, Create); if (value.RestoreBodyFlag) { __instance.m_syncBodyVelocity = true; value.RestoreBodyFlag = false; } if (!Bindings.StWasOwner.Invoke(__instance)) {