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 ValheimAnticheat v0.0.1
ValheimAnticheat.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ValheimAnticheat")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.0.1.0")] [assembly: AssemblyInformationalVersion("0.0.1+e111e6d15ed6538121e919cae93adee737664390")] [assembly: AssemblyProduct("ValheimAnticheat")] [assembly: AssemblyTitle("ValheimAnticheat")] [assembly: AssemblyVersion("0.0.1.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 ValheimAnticheat { [BepInPlugin("dev.monokaijs.valheim.anticheat", "Valheim Anticheat", "0.0.1")] public sealed class AnticheatPlugin : BaseUnityPlugin { private sealed class PeerState { internal readonly TrafficWindow Traffic = new TrafficWindow(); internal DateTime FirstRateStrike; internal DateTime LastRateStrike; internal int RateStrikes; internal bool PendingKick; internal string? LastItemReason; internal DateTime NextItemLog; internal DateTime ReadyAt; internal DateTime NextScanAt; internal DateTime ScanRequestedAt; internal string? PendingNonce; internal bool ScannerAvailable; internal string? LastHealthReason; internal DateTime NextHealthLog; internal byte LastReportedModes; internal DateTime NextModeLog; internal bool InventoryBaselineChecked; } private Harmony? _harmony; private readonly Dictionary<ZRpc, PeerState> _states = new Dictionary<ZRpc, PeerState>(); private DateTime _nextCleanup; private ZRpc? _clientHelloRpc; private string? _clientPendingNonce; private byte _clientObservedModes; private InventoryOriginTracker _inventoryOrigins; internal static AnticheatPlugin? Instance { get; private set; } internal GuardSettings Settings { get; private set; } private void Awake() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown Instance = this; Settings = new GuardSettings(((BaseUnityPlugin)this).Config); _inventoryOrigins = new InventoryOriginTracker(Path.Combine(Paths.ConfigPath, "ValheimAnticheat", "inventories")); _harmony = new Harmony("dev.monokaijs.valheim.anticheat"); _harmony.PatchAll(typeof(AnticheatPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Server protocol guard loaded. Enforce=" + Settings.Enforce.Value)); } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _states.Clear(); Instance = null; } private void Update() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return; } if (!instance.IsServer()) { UpdateClientScanner(instance); } else { if (_states.Count == 0) { return; } DateTime utcNow = DateTime.UtcNow; if (utcNow < _nextCleanup) { return; } _nextCleanup = utcNow.AddSeconds(2.0); List<ZRpc> list = new List<ZRpc>(); List<ZNetPeer> list2 = new List<ZNetPeer>(); foreach (KeyValuePair<ZRpc, PeerState> state in _states) { ZNetPeer val = FindPeer(instance, state.Key); if (val == null) { list.Add(state.Key); } else if (state.Value.PendingKick) { list2.Add(val); } else { UpdateServerScanner(val, state.Value, utcNow); } } foreach (ZRpc item in list) { _states.Remove(item); } foreach (ZNetPeer item2 in list2) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Disconnecting " + Describe(item2) + " after protocol violation")); instance.Disconnect(item2); _states.Remove(item2.m_rpc); } } } internal void RegisterConnection(ZNet net, ZNetPeer peer) { peer.m_rpc.Register<ZPackage>("AC_ItemHello", (Action<ZRpc, ZPackage>)ReceiveItemHello); peer.m_rpc.Register<ZPackage>("AC_ItemScanRequest", (Action<ZRpc, ZPackage>)ReceiveItemScanRequest); peer.m_rpc.Register<ZPackage>("AC_ItemReport", (Action<ZRpc, ZPackage>)ReceiveItemReport); if (net.IsServer()) { GetState(peer); return; } _clientHelloRpc = null; _clientPendingNonce = null; _clientObservedModes = 0; } private void UpdateClientScanner(ZNet net) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown if (!Settings.EnableClientInventoryScanner.Value || (Object)(object)Player.m_localPlayer == (Object)null) { return; } ZRpc serverRPC = net.GetServerRPC(); if (serverRPC == null || !serverRPC.IsConnected()) { return; } if (_clientHelloRpc != serverRPC) { _clientHelloRpc = serverRPC; _clientObservedModes = 0; serverRPC.Invoke("AC_ItemHello", new object[1] { (object)new ZPackage() }); } Player localPlayer = Player.m_localPlayer; if (((Character)localPlayer).InGodMode()) { _clientObservedModes |= 1; } if (((Character)localPlayer).InGhostMode()) { _clientObservedModes |= 2; } if (localPlayer.InDebugFlyMode()) { _clientObservedModes |= 4; } if (_clientPendingNonce == null) { return; } try { ZPackage val = new ZPackage(); ((Humanoid)localPlayer).GetInventory().Save(val); ZPackage val2 = new ZPackage(); val2.Write(_clientPendingNonce); val2.Write(val.GetArray()); val2.Write(_clientObservedModes); serverRPC.Invoke("AC_ItemReport", new object[1] { val2 }); _clientPendingNonce = null; _clientObservedModes = 0; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not serialize local inventory for scanner: " + ex.GetType().Name)); } } private void UpdateServerScanner(ZNetPeer peer, PeerState state, DateTime now) { //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Expected O, but got Unknown if (peer.m_uid == 0L || ((ZDOID)(ref peer.m_characterID)).IsNone()) { return; } if (state.ReadyAt == default(DateTime)) { state.ReadyAt = now; } if (!state.ScannerAvailable) { if (Settings.RequireClientInventoryScanner.Value && now - state.ReadyAt > TimeSpan.FromSeconds(Math.Max(5, Settings.ClientScannerJoinTimeoutSeconds.Value))) { Violation(peer, "client inventory scanner did not connect"); } } else if (state.PendingNonce != null) { if (now - state.ScanRequestedAt > TimeSpan.FromSeconds(Math.Max(5, Settings.ClientScannerReplyTimeoutSeconds.Value))) { state.PendingNonce = null; state.NextScanAt = now.AddSeconds(Math.Max(5, Settings.ClientScannerIntervalSeconds.Value)); if (Settings.RequireClientInventoryScanner.Value) { Violation(peer, "client inventory scanner did not answer"); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Client inventory scanner timed out for " + Describe(peer))); } } } else if (!(now < state.NextScanAt) && peer.m_rpc.IsConnected()) { state.PendingNonce = Guid.NewGuid().ToString("N"); state.ScanRequestedAt = now; ZPackage val = new ZPackage(); val.Write(state.PendingNonce); peer.m_rpc.Invoke("AC_ItemScanRequest", new object[1] { val }); } } private static void ReceiveItemHello(ZRpc rpc, ZPackage package) { AnticheatPlugin instance = Instance; if ((Object)(object)instance == (Object)null || !IsActiveServer()) { return; } ZNetPeer val = FindPeer(ZNet.instance, rpc); if (val != null) { PeerState state = instance.GetState(val); if (!state.ScannerAvailable) { state.ScannerAvailable = true; state.NextScanAt = DateTime.UtcNow; ((BaseUnityPlugin)instance).Logger.LogInfo((object)("Client inventory scanner available for " + Describe(val))); } } } private static void ReceiveItemScanRequest(ZRpc rpc, ZPackage package) { if ((Object)(object)Instance == (Object)null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || ZNet.instance.GetServerRPC() != rpc || package == null || package.Size() > 128) { return; } try { string text = package.ReadString(); if (text.Length == 32 && package.GetPos() == package.Size()) { Instance._clientPendingNonce = text; } } catch { } } private static void ReceiveItemReport(ZRpc rpc, ZPackage package) { AnticheatPlugin instance = Instance; if ((Object)(object)instance == (Object)null || !IsActiveServer()) { return; } ZNetPeer val = FindPeer(ZNet.instance, rpc); if (val == null) { return; } PeerState state = instance.GetState(val); if (package == null || package.Size() > 524288) { instance.Violation(val, "oversized client inventory report"); return; } byte b = 0; byte[] array; try { string text = package.ReadString(); array = package.ReadByteArray(); if (package.GetPos() < package.Size()) { b = package.ReadByte(); } if (text != state.PendingNonce || package.GetPos() != package.Size()) { instance.Violation(val, "unexpected client inventory report"); return; } if ((b & -8) != 0) { instance.Violation(val, "invalid client mode flags"); return; } } catch (Exception ex) { instance.Violation(val, "unreadable client inventory report: " + ex.GetType().Name); return; } state.PendingNonce = null; state.NextScanAt = DateTime.UtcNow.AddSeconds(Math.Max(5, instance.Settings.ClientScannerIntervalSeconds.Value)); if (b != 0) { instance.ReportedModeFinding(val, b); } bool flag = true; if (instance.Settings.InspectItemValues.Value) { try { ItemInspection itemInspection = new ItemInspection(); ItemPacketInspector.InspectInventory(array, "reported inventory", instance.Settings, itemInspection); if (itemInspection.WorldValueIssue != null) { flag = false; instance.ItemFinding(val, itemInspection.WorldValueIssue, instance.Settings.KickForReportedInventoryValues.Value, instance.Settings.KickForReportedInventoryValues.Value); } if (itemInspection.CheatMarkedIssue != null && instance.Settings.ReportCheatMarkedItems.Value) { instance.ItemFinding(val, itemInspection.CheatMarkedIssue, reject: false, kick: false); } } catch (Exception ex2) { instance.Violation(val, "invalid client inventory report: " + ex2.GetType().Name); return; } } if (!(instance.Settings.TrackInventoryBetweenSessions.Value && flag) || val.m_playerID == 0L) { return; } try { string hostName = val.m_socket.GetHostName(); if (!string.IsNullOrWhiteSpace(hostName)) { string text2 = instance._inventoryOrigins.CompareAndStore(hostName, val.m_playerID, array, !state.InventoryBaselineChecked); state.InventoryBaselineChecked = true; if (text2 != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)("Possible outside inventory " + Describe(val) + ": " + text2)); } } } catch (Exception ex3) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)("Could not compare inventory history for " + Describe(val) + ": " + ex3.GetType().Name)); } } internal static bool IsActiveServer() { if ((Object)(object)Instance != (Object)null && (Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } internal static ZNetPeer? FindPeer(ZNet net, ZRpc rpc) { foreach (ZNetPeer peer in net.GetPeers()) { if (peer.m_rpc == rpc) { return peer; } } return null; } internal bool Record(ZNetPeer peer, int rpcs, int zdos, int bytes) { PeerState state = GetState(peer); if (state.PendingKick) { return false; } if (state.Traffic.Record(rpcs, zdos, bytes, Settings.MaxRoutedPerSecond.Value, Settings.MaxZdoUpdatesPerSecond.Value, Settings.MaxBytesPerSecond.Value)) { return true; } DateTime utcNow = DateTime.UtcNow; if (utcNow - state.LastRateStrike >= TimeSpan.FromSeconds(1.0)) { if (utcNow - state.FirstRateStrike > TimeSpan.FromSeconds(30.0)) { state.FirstRateStrike = utcNow; state.RateStrikes = 0; } state.LastRateStrike = utcNow; state.RateStrikes++; ((BaseUnityPlugin)this).Logger.LogWarning((object)("Rate limit: " + Describe(peer) + " (strike " + state.RateStrikes + ")")); if (state.RateStrikes >= Math.Max(1, Settings.RateStrikesToKick.Value) && Settings.Enforce.Value) { state.PendingKick = true; } } return !Settings.Enforce.Value; } internal bool Violation(ZNetPeer? peer, string reason) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Protocol violation " + Describe(peer) + ": " + reason)); if (peer != null && Settings.Enforce.Value) { GetState(peer).PendingKick = true; } return !Settings.Enforce.Value; } internal bool ItemFinding(ZNetPeer peer, string reason, bool reject, bool kick) { PeerState state = GetState(peer); DateTime utcNow = DateTime.UtcNow; if (reason != state.LastItemReason || utcNow >= state.NextItemLog) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Suspicious item " + Describe(peer) + ": " + reason)); state.LastItemReason = reason; state.NextItemLog = utcNow.AddSeconds(10.0); } if (kick && Settings.Enforce.Value) { state.PendingKick = true; } if (Settings.Enforce.Value) { return !reject; } return true; } internal bool HealthFinding(ZNetPeer peer, string reason) { PeerState state = GetState(peer); DateTime utcNow = DateTime.UtcNow; if (reason != state.LastHealthReason || utcNow >= state.NextHealthLog) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Invalid player health " + Describe(peer) + ": " + reason)); state.LastHealthReason = reason; state.NextHealthLog = utcNow.AddSeconds(10.0); } if (Settings.Enforce.Value && Settings.KickForInvalidPlayerHealth.Value) { state.PendingKick = true; } return !Settings.Enforce.Value; } private void ReportedModeFinding(ZNetPeer peer, byte modes) { PeerState state = GetState(peer); string text = (((modes & 1) != 0) ? "god mode " : "") + (((modes & 2) != 0) ? "ghost mode " : "") + (((modes & 4) != 0) ? "debug flight" : ""); DateTime utcNow = DateTime.UtcNow; if (modes != state.LastReportedModes || utcNow >= state.NextModeLog) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Client reported invulnerability mode " + Describe(peer) + ": " + text.Trim())); state.LastReportedModes = modes; state.NextModeLog = utcNow.AddSeconds(10.0); } if (Settings.Enforce.Value && Settings.KickForReportedInvulnerability.Value) { state.PendingKick = true; } } private PeerState GetState(ZNetPeer peer) { if (!_states.TryGetValue(peer.m_rpc, out PeerState value)) { value = new PeerState(); _states.Add(peer.m_rpc, value); } return value; } private static string Describe(ZNetPeer? peer) { if (peer == null) { return "unknown peer"; } string text = (peer.m_playerName ?? "<joining>").Replace('\r', ' ').Replace('\n', ' '); if (text.Length > 80) { text = text.Substring(0, 80); } ISocket socket = peer.m_socket; string text2 = (((socket != null) ? socket.GetHostName() : null) ?? "<unknown>").Replace('\r', ' ').Replace('\n', ' '); if (text2.Length > 100) { text2 = text2.Substring(0, 100); } return "peer=" + peer.m_uid + " account=" + text2 + " name=" + text; } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] internal static class ConnectionPatch { private static void Postfix(ZNet __instance, ZNetPeer __0) { AnticheatPlugin.Instance?.RegisterConnection(__instance, __0); } } [HarmonyPatch(typeof(ZRoutedRpc), "RPC_RoutedRPC")] internal static class RoutedRpcPatch { private static bool Prefix(ZRpc __0, ZPackage __1) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown if (!AnticheatPlugin.IsActiveServer()) { return true; } AnticheatPlugin instance = AnticheatPlugin.Instance; ZNetPeer val = AnticheatPlugin.FindPeer(ZNet.instance, __0); if (val == null) { return instance.Violation(null, "routed RPC from an unknown connection"); } if (__1 == null || __1.Size() > Math.Max(1024, instance.Settings.MaxRoutedBytes.Value)) { return instance.Violation(val, "routed RPC exceeds packet limit"); } try { ZPackage val2 = new ZPackage(__1.GetArray()); RoutedRPCData val3 = new RoutedRPCData(); val3.Deserialize(val2); if (val3.m_senderPeerID != val.m_uid || val.m_uid == 0L) { return instance.Violation(val, "forged routed RPC sender " + val3.m_senderPeerID); } if (val3.m_parameters == null || val2.GetPos() != val2.Size()) { return instance.Violation(val, "malformed routed RPC payload"); } if (instance.Settings.InspectDamageRpcs.Value && val3.m_methodHash == DamagePacketInspector.DamageHash && !((ZDOID)(ref val3.m_targetZDO)).IsNone()) { string text = DamagePacketInspector.Inspect(val3.m_parameters, instance.Settings.MaxRoutedDamage.Value); if (text != null) { return instance.Violation(val, text); } } } catch (Exception ex) { return instance.Violation(val, "unreadable routed RPC: " + ex.GetType().Name); } return instance.Record(val, 1, 0, __1.Size()); } } [HarmonyPatch(typeof(ZDOMan), "RPC_ZDOData")] internal static class ZdoDataPatch { private static bool Prefix(ZDOMan __instance, ZRpc __0, ZPackage __1) { if (!AnticheatPlugin.IsActiveServer()) { return true; } AnticheatPlugin instance = AnticheatPlugin.Instance; ZNetPeer val = AnticheatPlugin.FindPeer(ZNet.instance, __0); if (val == null) { return instance.Violation(null, "ZDO data from an unknown connection"); } if (__1 == null || __1.Size() > Math.Max(1024, instance.Settings.MaxZdoBytes.Value)) { return instance.Violation(val, "ZDO packet exceeds packet limit"); } string text; int updates; ItemInspection itemInspection; string healthIssue; try { text = WorldPacketValidator.ValidateZdoData(__instance, val, __1, instance.Settings, out updates, out itemInspection, out healthIssue); } catch (Exception ex) { return instance.Violation(val, "unreadable ZDO packet: " + ex.GetType().Name); } if (text != null) { return instance.Violation(val, text); } if (healthIssue != null && !instance.HealthFinding(val, healthIssue)) { return false; } if (itemInspection.WorldValueIssue != null && !instance.ItemFinding(val, itemInspection.WorldValueIssue, instance.Settings.RejectSuspiciousWorldItems.Value, kick: false)) { return false; } if (itemInspection.CheatMarkedIssue != null && instance.Settings.ReportCheatMarkedItems.Value && !instance.ItemFinding(val, itemInspection.CheatMarkedIssue, instance.Settings.RejectCheatMarkedItems.Value, kick: false)) { return false; } if (itemInspection.EquipmentIssue != null && !instance.ItemFinding(val, itemInspection.EquipmentIssue, instance.Settings.KickForSuspiciousEquipment.Value, instance.Settings.KickForSuspiciousEquipment.Value)) { return false; } return instance.Record(val, 0, updates, __1.Size()); } } [HarmonyPatch(typeof(ZDOMan), "RPC_DestroyZDO")] internal static class DestroyZdoPatch { private static bool Prefix(ZDOMan __instance, long __0, ZPackage __1) { if (!AnticheatPlugin.IsActiveServer() || __0 == ZNet.GetUID()) { return true; } AnticheatPlugin instance = AnticheatPlugin.Instance; ZNetPeer peer = ZNet.instance.GetPeer(__0); if (peer == null) { return instance.Violation(null, "destroy request from an unknown sender"); } if (__1 == null || __1.Size() > Math.Max(1024, instance.Settings.MaxRoutedBytes.Value)) { return instance.Violation(peer, "destroy request exceeds packet limit"); } string text; try { text = WorldPacketValidator.ValidateDestroy(__instance, peer, __1, instance.Settings); } catch (Exception ex) { return instance.Violation(peer, "unreadable destroy request: " + ex.GetType().Name); } if (text != null) { return instance.Violation(peer, text); } return true; } } internal static class DamagePacketInspector { internal static readonly int DamageHash = StringExtensionMethods.GetStableHashCode("RPC_Damage"); internal static string? Inspect(ZPackage parameters, float maximum) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0089: 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_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: 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) ZPackage val = new ZPackage(parameters.GetArray()); HitData val2 = new HitData(); val2.Deserialize(ref val); if (val.GetPos() != val.Size()) { return "trailing data in damage RPC"; } float num = Math.Max(1f, maximum); DamageTypes damage = val2.m_damage; float[] obj = new float[12] { damage.m_damage, damage.m_blunt, damage.m_slash, damage.m_pierce, damage.m_chop, damage.m_pickaxe, damage.m_fire, damage.m_frost, damage.m_lightning, damage.m_poison, damage.m_spirit, damage.m_nonPlayer }; float num2 = 0f; float[] array = obj; for (int i = 0; i < array.Length; i++) { float num3 = array[i]; if (!Finite(num3) || num3 < 0f || num3 > num) { return "invalid damage component " + num3; } num2 += num3; } if (!Finite(num2) || num2 > num) { return "total damage " + num2 + " exceeds " + num; } if (!Finite(val2.m_pushForce) || Math.Abs(val2.m_pushForce) > 10000f) { return "invalid damage push force " + val2.m_pushForce; } if (!Finite(val2.m_staggerMultiplier) || Math.Abs(val2.m_staggerMultiplier) > 10000f) { return "invalid stagger multiplier " + val2.m_staggerMultiplier; } return null; } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } internal sealed class GuardSettings { internal readonly ConfigEntry<bool> Enforce; internal readonly ConfigEntry<int> MaxRoutedBytes; internal readonly ConfigEntry<int> MaxZdoBytes; internal readonly ConfigEntry<int> MaxDestroyCount; internal readonly ConfigEntry<int> MaxZdoUpdates; internal readonly ConfigEntry<int> MaxRoutedPerSecond; internal readonly ConfigEntry<int> MaxZdoUpdatesPerSecond; internal readonly ConfigEntry<int> MaxBytesPerSecond; internal readonly ConfigEntry<float> MaxObjectDistance; internal readonly ConfigEntry<float> MaxObjectMoveDistance; internal readonly ConfigEntry<float> MaxWorldCoordinate; internal readonly ConfigEntry<int> RateStrikesToKick; internal readonly ConfigEntry<bool> InspectItemValues; internal readonly ConfigEntry<bool> RejectSuspiciousWorldItems; internal readonly ConfigEntry<bool> ReportCheatMarkedItems; internal readonly ConfigEntry<bool> RejectCheatMarkedItems; internal readonly ConfigEntry<bool> KickForSuspiciousEquipment; internal readonly ConfigEntry<int> MaxAbsoluteItemQuality; internal readonly ConfigEntry<int> MaxWorldLevel; internal readonly ConfigEntry<float> MaxItemDamage; internal readonly ConfigEntry<float> MaxItemArmor; internal readonly ConfigEntry<bool> EnableClientInventoryScanner; internal readonly ConfigEntry<bool> RequireClientInventoryScanner; internal readonly ConfigEntry<bool> KickForReportedInventoryValues; internal readonly ConfigEntry<int> ClientScannerIntervalSeconds; internal readonly ConfigEntry<int> ClientScannerJoinTimeoutSeconds; internal readonly ConfigEntry<int> ClientScannerReplyTimeoutSeconds; internal readonly ConfigEntry<bool> InspectPlayerHealth; internal readonly ConfigEntry<float> MaxPlayerHealth; internal readonly ConfigEntry<bool> KickForInvalidPlayerHealth; internal readonly ConfigEntry<bool> KickForReportedInvulnerability; internal readonly ConfigEntry<bool> InspectDamageRpcs; internal readonly ConfigEntry<float> MaxRoutedDamage; internal readonly ConfigEntry<bool> TrackInventoryBetweenSessions; internal GuardSettings(ConfigFile config) { Enforce = config.Bind<bool>("Enforcement", "Enforce", true, "Reject invalid traffic and disconnect offenders. Set false to log decisions while tuning thresholds."); MaxRoutedBytes = config.Bind<int>("Limits", "MaxRoutedPacketBytes", 262144, "Largest accepted routed RPC packet in bytes."); MaxZdoBytes = config.Bind<int>("Limits", "MaxZdoPacketBytes", 262144, "Largest accepted ZDO synchronization packet in bytes."); MaxDestroyCount = config.Bind<int>("Limits", "MaxDestroyCount", 128, "Most ZDOs a client may destroy in one request."); MaxZdoUpdates = config.Bind<int>("Limits", "MaxZdoUpdatesPerPacket", 256, "Most ZDO updates in one synchronization packet."); MaxRoutedPerSecond = config.Bind<int>("Limits", "MaxRoutedRpcsPerSecond", 300, "Maximum routed RPCs from one peer in a rolling second before a rate strike."); MaxZdoUpdatesPerSecond = config.Bind<int>("Limits", "MaxZdoUpdatesPerSecond", 500, "Maximum ZDO updates from one peer in a rolling second before a rate strike."); MaxBytesPerSecond = config.Bind<int>("Limits", "MaxTrafficBytesPerSecond", 1048576, "Combined routed RPC and ZDO bytes from one peer in a rolling second."); MaxObjectDistance = config.Bind<float>("World", "MaxObjectDistanceFromPlayer", 512f, "Maximum distance of a new ZDO or ownership claim from the peer's reported position. Set 0 to disable."); MaxObjectMoveDistance = config.Bind<float>("World", "MaxObjectMoveDistancePerUpdate", 256f, "Maximum position jump for an existing ZDO other than the peer's character. Set 0 to disable."); MaxWorldCoordinate = config.Bind<float>("World", "MaxWorldCoordinate", 20000f, "Maximum absolute coordinate for a client-supplied ZDO position."); RateStrikesToKick = config.Bind<int>("Enforcement", "RateStrikesToKick", 3, "Rate limit breaches within 30 seconds before disconnection."); InspectItemValues = config.Bind<bool>("Items", "InspectItemValues", true, "Inspect item data in incoming world drops and containers, and visible equipped quality in player ZDOs."); RejectSuspiciousWorldItems = config.Bind<bool>("Items", "RejectSuspiciousWorldItems", true, "Reject client world updates containing invalid item quality, stack, durability, or extreme equipment stats. Does not kick."); ReportCheatMarkedItems = config.Bind<bool>("Items", "ReportCheatMarkedItems", true, "Log items carrying Valheim's built-in cheated marker."); RejectCheatMarkedItems = config.Bind<bool>("Items", "RejectCheatMarkedItems", false, "Reject world updates containing items marked cheated. Useful on servers that forbid admin-spawned or imported items."); KickForSuspiciousEquipment = config.Bind<bool>("Items", "KickForSuspiciousEquipment", false, "Disconnect peers that advertise impossible quality for equipped weapons or shoulder gear. Usually leave off until thresholds are tuned."); MaxAbsoluteItemQuality = config.Bind<int>("Items", "MaxAbsoluteItemQuality", 100, "Hard quality ceiling even when the item prefab is unknown."); MaxWorldLevel = config.Bind<int>("Items", "MaxItemWorldLevel", 100, "Largest accepted world level encoded on an item."); MaxItemDamage = config.Bind<float>("Items", "MaxItemDamage", 2000f, "Largest computed total weapon damage accepted for an item using the server's prefab data."); MaxItemArmor = config.Bind<float>("Items", "MaxItemArmor", 1000f, "Largest computed armor accepted for an item using the server's prefab data."); EnableClientInventoryScanner = config.Bind<bool>("Items", "EnableClientInventoryScanner", true, "When installed on a client, answer server inventory scan requests."); RequireClientInventoryScanner = config.Bind<bool>("Items", "RequireClientInventoryScanner", false, "Require this plugin on every client and disconnect peers that do not answer inventory scans."); KickForReportedInventoryValues = config.Bind<bool>("Items", "KickForReportedInventoryValues", false, "Disconnect when an optional client inventory scan reports suspicious values. Client reports can be falsified."); ClientScannerIntervalSeconds = config.Bind<int>("Items", "ClientScannerIntervalSeconds", 15, "Time between inventory scan requests to a client running this plugin."); ClientScannerJoinTimeoutSeconds = config.Bind<int>("Items", "ClientScannerJoinTimeoutSeconds", 30, "Seconds after a character appears to wait for the client scanner hello when required."); ClientScannerReplyTimeoutSeconds = config.Bind<int>("Items", "ClientScannerReplyTimeoutSeconds", 15, "Seconds to wait for a requested inventory report when the scanner is required."); InspectPlayerHealth = config.Bind<bool>("Health", "InspectPlayerHealth", true, "Inspect player health and maximum health in client ZDO updates."); MaxPlayerHealth = config.Bind<float>("Health", "MaxPlayerHealth", 1000f, "Hard ceiling for a player's synchronized health and maximum health. Raise for health overhaul mods."); KickForInvalidPlayerHealth = config.Bind<bool>("Health", "KickForInvalidPlayerHealth", true, "Disconnect a client that synchronizes non-finite, negative, over-maximum, or excessive player health."); KickForReportedInvulnerability = config.Bind<bool>("Health", "KickForReportedInvulnerability", true, "Disconnect a client scanner that reports god mode, ghost mode, or debug flight. Disable for servers that permit these modes."); InspectDamageRpcs = config.Bind<bool>("Combat", "InspectDamageRpcs", true, "Validate damage RPC values sent by clients before routing them to a target."); MaxRoutedDamage = config.Bind<float>("Combat", "MaxRoutedDamage", 10000f, "Maximum sum of raw damage components in one client-origin damage RPC. Raise for combat overhaul mods."); TrackInventoryBetweenSessions = config.Bind<bool>("Items", "TrackInventoryBetweenSessions", true, "Log items added to a client's reported inventory between sessions. This is only an audit clue; crashes and save rollbacks can cause false alarms."); } } internal static class HealthValueRules { internal static string? Check(float? health, float? maxHealth, float ceiling) { float num = Math.Max(1f, ceiling); if (maxHealth.HasValue && (!Finite(maxHealth.Value) || maxHealth.Value < 1f || maxHealth.Value > num)) { return "invalid maximum health " + maxHealth.Value; } if (health.HasValue && (!Finite(health.Value) || health.Value < 0f || health.Value > num)) { return "invalid health " + health.Value; } if (health.HasValue && maxHealth.HasValue && health.Value > maxHealth.Value + 5f) { return "health " + health.Value + " exceeds maximum " + maxHealth.Value; } return null; } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } internal sealed class InventoryOriginTracker { private readonly struct ItemKey : IEquatable<ItemKey> { internal readonly int Prefab; internal readonly int Quality; internal readonly int Variant; internal readonly int WorldLevel; internal readonly long Crafter; internal ItemKey(int prefab, ItemData item) : this(prefab, item.m_quality, item.m_variant, item.m_worldLevel, item.m_crafterID) { } internal ItemKey(int prefab, int quality, int variant, int worldLevel, long crafter) { Prefab = prefab; Quality = quality; Variant = variant; WorldLevel = worldLevel; Crafter = crafter; } public bool Equals(ItemKey other) { if (Prefab == other.Prefab && Quality == other.Quality && Variant == other.Variant && WorldLevel == other.WorldLevel) { return Crafter == other.Crafter; } return false; } public override bool Equals(object? obj) { if (obj is ItemKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (((Prefab * 31 + Quality) * 31 + Variant) * 31 + WorldLevel) * 31 + Crafter.GetHashCode(); } internal string Describe() { GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(Prefab) : null); return (((Object)(object)val != (Object)null) ? ((Object)val).name : ("hash=" + Prefab)) + ((Quality > 1) ? (" q" + Quality) : "") + ((Crafter != 0L) ? (" crafter=" + Crafter) : ""); } } private sealed class Snapshot { internal readonly Dictionary<ItemKey, int> Items = new Dictionary<ItemKey, int>(); internal static Snapshot Parse(byte[] bytes) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(bytes); int num = val.ReadInt(); if (num < 108) { throw new InvalidDataException("unsupported inventory version " + num); } int num2 = val.ReadUShort(); if (num2 > 4096) { throw new InvalidDataException("inventory item count exceeds 4096"); } Snapshot snapshot = new Snapshot(); for (int i = 0; i < num2; i++) { var (prefab, val2) = ItemData.Load(val, (Item)num); if (val2.m_stack < 1 || val2.m_stack > 1000000) { throw new InvalidDataException("invalid item stack in inventory report"); } ItemKey key = new ItemKey(prefab, val2); snapshot.Items.TryGetValue(key, out var value); snapshot.Items[key] = checked(value + val2.m_stack); } if (val.GetPos() != val.Size()) { throw new InvalidDataException("trailing inventory data"); } return snapshot; } internal static Snapshot? Load(string path) { if (!File.Exists(path)) { return null; } using BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)); if (binaryReader.ReadInt32() != 1) { throw new InvalidDataException("unknown inventory snapshot version"); } int num = binaryReader.ReadInt32(); if (num < 0 || num > 4096) { throw new InvalidDataException("invalid inventory snapshot count"); } Snapshot snapshot = new Snapshot(); for (int i = 0; i < num; i++) { ItemKey key = new ItemKey(binaryReader.ReadInt32(), binaryReader.ReadInt32(), binaryReader.ReadInt32(), binaryReader.ReadInt32(), binaryReader.ReadInt64()); int num2 = binaryReader.ReadInt32(); if (num2 < 1) { throw new InvalidDataException("invalid inventory snapshot quantity"); } snapshot.Items.Add(key, num2); } if (binaryReader.BaseStream.Position != binaryReader.BaseStream.Length) { throw new InvalidDataException("trailing inventory snapshot data"); } return snapshot; } internal void Store(string path) { Directory.CreateDirectory(Path.GetDirectoryName(path)); using MemoryStream memoryStream = new MemoryStream(); using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true)) { binaryWriter.Write(1); binaryWriter.Write(Items.Count); foreach (KeyValuePair<ItemKey, int> item in Items) { binaryWriter.Write(item.Key.Prefab); binaryWriter.Write(item.Key.Quality); binaryWriter.Write(item.Key.Variant); binaryWriter.Write(item.Key.WorldLevel); binaryWriter.Write(item.Key.Crafter); binaryWriter.Write(item.Value); } } string text = path + "." + Guid.NewGuid().ToString("N") + ".tmp"; File.WriteAllBytes(text, memoryStream.ToArray()); if (File.Exists(path)) { File.Replace(text, path, null); } else { File.Move(text, path); } } } private readonly string _directory; internal InventoryOriginTracker(string directory) { _directory = directory; } internal string? CompareAndStore(string account, long characterId, byte[] inventory, bool compare) { if (string.IsNullOrWhiteSpace(account) || characterId == 0L) { return null; } Snapshot snapshot = Snapshot.Parse(inventory); string path = Path.Combine(_directory, KeyFor(account, characterId) + ".dat"); Snapshot snapshot2 = (compare ? Snapshot.Load(path) : null); snapshot.Store(path); if (snapshot2 != null) { return DescribeAdditions(snapshot2, snapshot); } return null; } private static string KeyFor(string account, long characterId) { using SHA256 sHA = SHA256.Create(); return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(account + "\n" + characterId))).Replace("-", "").ToLowerInvariant(); } private static string? DescribeAdditions(Snapshot previous, Snapshot current) { List<(ItemKey, int)> list = new List<(ItemKey, int)>(); foreach (KeyValuePair<ItemKey, int> item in current.Items) { previous.Items.TryGetValue(item.Key, out var value); if (item.Value > value) { list.Add((item.Key, item.Value - value)); } } if (list.Count == 0) { return null; } list.Sort(((ItemKey Key, int Count) a, (ItemKey Key, int Count) b) => b.Count.CompareTo(a.Count)); return "inventory gained items between sessions: " + string.Join(", ", from x in list.Take(6) select "+" + x.Count + " " + x.Key.Describe()) + ((list.Count > 6) ? (" (and " + (list.Count - 6) + " more types)") : ""); } } internal sealed class ItemInspection { internal string? WorldValueIssue; internal string? CheatMarkedIssue; internal string? EquipmentIssue; } internal static class ItemPacketInspector { private static readonly HashSet<int> IndexedItemDataKeys = MakeIndexedKeys(); internal unsafe static void Inspect(ZDOID id, ZDO? existing, ZNetPeer peer, ZPackage serializedZdo, GuardSettings settings, ItemInspection result) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: 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) ZPackage val = new ZPackage(serializedZdo.GetArray()); int num = val.ReadUShort(); int zdoPrefabHash = val.ReadInt(); if ((num & 0x1000) != 0) { val.ReadVector3(); } if ((num & 0xFF) == 0) { return; } if ((num & 1) != 0) { val.ReadByte(); val.ReadZDOID(); } if ((num & 2) != 0) { Skip(val, (ZPackage p) => p.ReadSingle()); } if ((num & 4) != 0) { Skip(val, (ZPackage p) => p.ReadVector3()); } if ((num & 8) != 0) { Skip(val, (ZPackage p) => p.ReadQuaternion()); } Dictionary<int, int> dictionary = ((id == peer.m_characterID) ? new Dictionary<int, int>() : null); if ((num & 0x10) != 0) { int num2 = Count(val); for (int num3 = 0; num3 < num2; num3++) { int key = val.ReadInt(); int value = val.ReadInt(); if (dictionary != null) { dictionary[key] = value; } } } if ((num & 0x20) != 0) { Skip(val, (ZPackage p) => p.ReadLong()); } if ((num & 0x40) != 0) { Skip(val, (ZPackage p) => p.ReadString()); } if ((num & 0x80) != 0) { int num4 = Count(val); for (int num5 = 0; num5 < num4; num5++) { int num6 = val.ReadInt(); byte[] array = val.ReadByteArray(); if (array != null && !SameAsExisting(existing, num6, array)) { if (num6 == ZDOVars.s_items) { ZDOID val2 = id; InspectInventory(array, "container " + ((object)(*(ZDOID*)(&val2))/*cast due to .constrained prefix*/).ToString(), settings, result); } else if (num6 == ZDOVars.s_itemData || IndexedItemDataKeys.Contains(num6)) { InspectItemData(array, zdoPrefabHash, id, settings, result); } } } } if (val.GetPos() != val.Size()) { throw new InvalidDataException("trailing data in serialized ZDO"); } if (dictionary != null) { InspectEquipped(dictionary, id, settings, result); } } internal static void InspectInventory(byte[] bytes, string location, GuardSettings settings, ItemInspection result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(bytes); int num = val.ReadInt(); if (num >= 108) { int num2 = val.ReadUShort(); if (num2 > 4096) { throw new InvalidDataException("inventory item count exceeds 4096"); } for (int i = 0; i < num2; i++) { ValueTuple<int, ItemData> valueTuple = ItemData.Load(val, (Item)num); var (prefabHash, _) = valueTuple; CheckItem(valueTuple.Item2, prefabHash, location, settings, result); } if (val.GetPos() != val.Size()) { throw new InvalidDataException("trailing inventory data"); } } } private unsafe static void InspectItemData(byte[] bytes, int zdoPrefabHash, ZDOID id, GuardSettings settings, ItemInspection result) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (bytes.Length < 2) { return; } ZPackage val = new ZPackage(bytes); int num = val.ReadByte(); if (num >= 107) { ValueTuple<int, ItemData> valueTuple = ItemData.Load(val, (Item)num); int item = valueTuple.Item1; ItemData item2 = valueTuple.Item2; int prefabHash = ((item != 0) ? item : zdoPrefabHash); ZDOID val2 = id; CheckItem(item2, prefabHash, "world object " + ((object)(*(ZDOID*)(&val2))/*cast due to .constrained prefix*/).ToString(), settings, result); if (val.GetPos() != val.Size()) { throw new InvalidDataException("trailing item data"); } } } private static void CheckItem(ItemData item, int prefabHash, string location, GuardSettings settings, ItemInspection result) { //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) ItemDrop val = FindItem(prefabHash); SharedData val2 = val?.m_itemData?.m_shared; string text = (((Object)(object)val != (Object)null) ? ((Object)val).name : ("hash=" + prefabHash)); float prefabMaxDurability = 0f; if (val2 != null) { item.m_shared = val2; try { prefabMaxDurability = item.GetMaxDurability(); } catch { } } string text2 = ItemValueRules.Check(item.m_quality, item.m_stack, item.m_durability, item.m_variant, item.m_worldLevel, val2?.m_maxQuality ?? 0, val2?.m_maxStackSize ?? 0, prefabMaxDurability, val2?.m_variants ?? 0, settings.MaxAbsoluteItemQuality.Value, settings.MaxWorldLevel.Value); if (text2 == null && val2 != null) { try { if (item.IsWeapon()) { DamageTypes damage = item.GetDamage(); text2 = ItemValueRules.CheckComputed(((DamageTypes)(ref damage)).GetTotalDamage(), settings.MaxItemDamage.Value, "damage"); } if (text2 == null && item.IsEquipable()) { text2 = ItemValueRules.CheckComputed(item.GetArmor(), settings.MaxItemArmor.Value, "armor"); } } catch { } } if (text2 != null && result.WorldValueIssue == null) { result.WorldValueIssue = location + " item " + text + ": " + text2; } if (item.m_cheated && result.CheatMarkedIssue == null) { result.CheatMarkedIssue = location + " item " + text + " carries the cheated marker"; } } private static void InspectEquipped(Dictionary<int, int> ints, ZDOID id, GuardSettings settings, ItemInspection result) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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) CheckEquipped(ints, ZDOVars.s_rightItem, ZDOVars.s_rightItemQuality, "right hand", id, settings, result); CheckEquipped(ints, ZDOVars.s_leftItem, ZDOVars.s_leftItemQuality, "left hand", id, settings, result); CheckEquipped(ints, ZDOVars.s_rightBackItem, ZDOVars.s_rightBackItemQuality, "right back", id, settings, result); CheckEquipped(ints, ZDOVars.s_leftBackItem, ZDOVars.s_leftBackItemQuality, "left back", id, settings, result); CheckEquipped(ints, ZDOVars.s_shoulderItem, ZDOVars.s_shoulderItemQuality, "shoulder", id, settings, result); } private unsafe static void CheckEquipped(Dictionary<int, int> ints, int itemKey, int qualityKey, string slot, ZDOID id, GuardSettings settings, ItemInspection result) { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) if (result.EquipmentIssue == null && ints.TryGetValue(itemKey, out var value) && value != 0 && ints.TryGetValue(qualityKey, out var value2)) { ItemDrop val = FindItem(value); int valueOrDefault = (val?.m_itemData?.m_shared?.m_maxQuality).GetValueOrDefault(); if (value2 < 1 || value2 > Math.Max(1, settings.MaxAbsoluteItemQuality.Value) || (valueOrDefault > 0 && value2 > valueOrDefault)) { string text = (((Object)(object)val != (Object)null) ? ((Object)val).name : ("hash=" + value)); string[] obj = new string[11] { "player ", null, null, null, null, null, null, null, null, null, null }; ZDOID val2 = id; obj[1] = ((object)(*(ZDOID*)(&val2))/*cast due to .constrained prefix*/).ToString(); obj[2] = " advertises "; obj[3] = slot; obj[4] = " "; obj[5] = text; obj[6] = " quality "; obj[7] = value2.ToString(); obj[8] = " (prefab maximum "; obj[9] = valueOrDefault.ToString(); obj[10] = ")"; result.EquipmentIssue = string.Concat(obj); } } } private static ItemDrop? FindItem(int hash) { if (hash == 0 || (Object)(object)ObjectDB.instance == (Object)null) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(hash); if (!((Object)(object)itemPrefab != (Object)null)) { return null; } return itemPrefab.GetComponent<ItemDrop>(); } private static bool SameAsExisting(ZDO? zdo, int key, byte[] value) { if (zdo == null) { return false; } byte[] byteArray = zdo.GetByteArray(key, (byte[])null); if (byteArray == null || byteArray.Length != value.Length) { return false; } for (int i = 0; i < byteArray.Length; i++) { if (byteArray[i] != value[i]) { return false; } } return true; } private static int Count(ZPackage pkg) { int num = pkg.ReadNumItems(); if (num < 0 || num > 4096) { throw new InvalidDataException("invalid ZDO field count " + num); } return num; } private static void Skip<T>(ZPackage pkg, Func<ZPackage, T> read) { int num = Count(pkg); for (int i = 0; i < num; i++) { pkg.ReadInt(); read(pkg); } } private static HashSet<int> MakeIndexedKeys() { HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < 64; i++) { hashSet.Add(StringExtensionMethods.GetStableHashCode(i + "_itemData")); } return hashSet; } } internal static class ItemValueRules { internal static string? Check(int quality, int stack, float durability, int variant, int worldLevel, int prefabMaxQuality, int prefabMaxStack, float prefabMaxDurability, int prefabVariants, int absoluteMaxQuality, int maxWorldLevel) { if (quality < 1 || quality > Math.Max(1, absoluteMaxQuality)) { return "quality " + quality + " exceeds absolute limit"; } if (prefabMaxQuality > 0 && quality > prefabMaxQuality) { return "quality " + quality + " exceeds prefab maximum " + prefabMaxQuality; } if (stack < 1 || (prefabMaxStack > 0 && stack > prefabMaxStack)) { return "stack " + stack + " exceeds prefab maximum " + prefabMaxStack; } if (float.IsNaN(durability) || float.IsInfinity(durability) || durability < 0f) { return "invalid durability " + durability; } if (prefabMaxDurability > 0f && durability > prefabMaxDurability * 1.25f + 10f) { return "durability " + durability + " exceeds prefab maximum " + prefabMaxDurability; } if (variant < 0 || (prefabVariants > 0 && variant >= prefabVariants)) { return "invalid variant " + variant; } if (worldLevel < 0 || worldLevel > Math.Max(1, maxWorldLevel)) { return "world level " + worldLevel + " exceeds limit"; } return null; } internal static string? CheckComputed(float value, float maximum, string label) { if (float.IsNaN(value) || float.IsInfinity(value) || value < 0f || value > Math.Max(1f, maximum)) { return label + " " + value + " exceeds limit " + maximum; } return null; } } internal static class PlayerHealthInspector { internal unsafe static string? Inspect(ZDOID id, ZDOID characterId, ZDO? existing, ZPackage serializedZdo, float ceiling) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) if (id != characterId) { return null; } ZPackage val = new ZPackage(serializedZdo.GetArray()); int num = val.ReadUShort(); val.ReadInt(); if ((num & 0x1000) != 0) { val.ReadVector3(); } if ((num & 1) != 0) { val.ReadByte(); val.ReadZDOID(); } float? health = null; float? maxHealth = null; if ((num & 2) != 0) { int num2 = val.ReadNumItems(); if (num2 < 0 || num2 > 4096) { throw new InvalidOperationException("invalid player float field count " + num2); } for (int i = 0; i < num2; i++) { int num3 = val.ReadInt(); float value = val.ReadSingle(); if (num3 == ZDOVars.s_health) { health = value; } else if (num3 == ZDOVars.s_maxHealth) { maxHealth = value; } } } if (!maxHealth.HasValue && health.HasValue && existing != null) { float num4 = existing.GetFloat(ZDOVars.s_maxHealth, float.NaN); if (!float.IsNaN(num4)) { maxHealth = num4; } } string text = HealthValueRules.Check(health, maxHealth, ceiling); if (text != null) { ZDOID val2 = id; return "player " + ((object)(*(ZDOID*)(&val2))/*cast due to .constrained prefix*/).ToString() + " sent " + text; } return null; } } internal sealed class TrafficWindow { private readonly long[] _rpcs = new long[10]; private readonly long[] _zdos = new long[10]; private readonly long[] _bytes = new long[10]; private readonly Func<long> _clock; private long _latestSlice = -1L; internal TrafficWindow(Func<long>? clock = null) { _clock = clock ?? ((Func<long>)(() => DateTime.UtcNow.Ticks / 10000)); } internal bool Record(int rpcCount, int zdoCount, int bytes, int maxRpcs, int maxZdos, int maxBytes) { long num = _clock() / 100; if (_latestSlice < 0 || num < _latestSlice || num - _latestSlice >= 10) { Array.Clear(_rpcs, 0, _rpcs.Length); Array.Clear(_zdos, 0, _zdos.Length); Array.Clear(_bytes, 0, _bytes.Length); } else { for (long num2 = _latestSlice + 1; num2 <= num; num2++) { int num3 = (int)(num2 % 10); _rpcs[num3] = 0L; _zdos[num3] = 0L; _bytes[num3] = 0L; } } _latestSlice = num; int num4 = (int)(num % 10); _rpcs[num4] += rpcCount; _zdos[num4] += zdoCount; _bytes[num4] += bytes; long num5 = 0L; long num6 = 0L; long num7 = 0L; for (int i = 0; i < 10; i++) { num5 += _rpcs[i]; num6 += _zdos[i]; num7 += _bytes[i]; } if (num5 <= Math.Max(1, maxRpcs) && num6 <= Math.Max(1, maxZdos)) { return num7 <= Math.Max(1024, maxBytes); } return false; } } internal static class WorldPacketValidator { internal unsafe static string? ValidateZdoData(ZDOMan man, ZNetPeer peer, ZPackage original, GuardSettings settings, out int updates, out ItemInspection itemInspection, out string? healthIssue) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_0297: 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_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_0306: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) updates = 0; itemInspection = new ItemInspection(); healthIssue = null; ZPackage val = new ZPackage(original.GetArray()); int num = val.ReadInt(); if (num < 0 || num > Math.Max(1, settings.MaxZdoUpdates.Value)) { return "invalid ZDO invalidation count " + num; } for (int i = 0; i < num; i++) { val.ReadZDOID(); } int num2 = Math.Max(1, settings.MaxZdoUpdates.Value); while (true) { ZDOID val2 = val.ReadZDOID(); if (((ZDOID)(ref val2)).IsNone()) { break; } if (++updates > num2) { return "too many ZDO updates in one packet"; } ushort num3 = val.ReadUShort(); uint num4 = val.ReadUInt(); long num5 = val.ReadLong(); Vector3 val3 = val.ReadVector3(); ZPackage serializedZdo = val.ReadPackage(); if (!InWorld(val3, settings.MaxWorldCoordinate.Value)) { ZDOID val4 = val2; return "out of bounds ZDO position for " + ((object)(*(ZDOID*)(&val4))/*cast due to .constrained prefix*/).ToString(); } ZDO zDO = man.GetZDO(val2); if (zDO == null) { if (((ZDOID)(ref val2)).UserID != peer.m_uid) { ZDOID val4 = val2; return "ZDO creator spoof for " + ((object)(*(ZDOID*)(&val4))/*cast due to .constrained prefix*/).ToString(); } if (num5 != 0L && num5 != peer.m_uid && num5 != ZNet.GetUID()) { ZDOID val4 = val2; return "ZDO owner spoof for " + ((object)(*(ZDOID*)(&val4))/*cast due to .constrained prefix*/).ToString(); } if (!NearPeer(peer, val3, settings.MaxObjectDistance.Value)) { ZDOID val4 = val2; return "remote ZDO creation for " + ((object)(*(ZDOID*)(&val4))/*cast due to .constrained prefix*/).ToString(); } } else { if (num4 <= zDO.DataRevision && num3 <= zDO.OwnerRevision) { continue; } if (num4 > zDO.DataRevision && val2 != peer.m_characterID && !WithinMove(zDO.GetPosition(), val3, settings.MaxObjectMoveDistance.Value)) { ZDOID val4 = val2; return "large ZDO position jump for " + ((object)(*(ZDOID*)(&val4))/*cast due to .constrained prefix*/).ToString(); } long owner = zDO.GetOwner(); if (owner == peer.m_uid) { if (num5 != peer.m_uid && num5 != 0L && num5 != ZNet.GetUID()) { ZDOID val4 = val2; return "ZDO owner spoof for " + ((object)(*(ZDOID*)(&val4))/*cast due to .constrained prefix*/).ToString(); } } else if (num5 != peer.m_uid) { ZDOID val4 = val2; return "unowned ZDO update for " + ((object)(*(ZDOID*)(&val4))/*cast due to .constrained prefix*/).ToString(); } if (owner != peer.m_uid && !NearPeer(peer, zDO.GetPosition(), settings.MaxObjectDistance.Value)) { ZDOID val4 = val2; return "remote ZDO ownership claim for " + ((object)(*(ZDOID*)(&val4))/*cast due to .constrained prefix*/).ToString(); } } if (settings.InspectItemValues.Value && (zDO == null || num4 > zDO.DataRevision)) { ItemPacketInspector.Inspect(val2, zDO, peer, serializedZdo, settings, itemInspection); } if (settings.InspectPlayerHealth.Value && (zDO == null || num4 > zDO.DataRevision) && healthIssue == null) { healthIssue = PlayerHealthInspector.Inspect(val2, peer.m_characterID, zDO, serializedZdo, settings.MaxPlayerHealth.Value); } } if (val.GetPos() != val.Size()) { return "trailing bytes in ZDO packet"; } return null; } internal unsafe static string? ValidateDestroy(ZDOMan man, ZNetPeer peer, ZPackage original, GuardSettings settings) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0042: 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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) ZPackage val = new ZPackage(original.GetArray()); int num = val.ReadInt(); if (num < 0 || num > Math.Max(1, settings.MaxDestroyCount.Value)) { return "invalid destroy count " + num; } for (int i = 0; i < num; i++) { ZDOID val2 = val.ReadZDOID(); ZDO zDO = man.GetZDO(val2); if (zDO != null && zDO.GetOwner() != peer.m_uid) { ZDOID val3 = val2; return "attempted destruction of another owner's ZDO " + ((object)(*(ZDOID*)(&val3))/*cast due to .constrained prefix*/).ToString(); } } if (val.GetPos() != val.Size()) { return "trailing bytes in destroy request"; } return null; } private static bool InWorld(Vector3 value, float max) { //IL_000d: 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_0027: 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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) max = Math.Max(1000f, max); if (Finite(value.x) && Finite(value.y) && Finite(value.z) && Math.Abs(value.x) <= max && Math.Abs(value.y) <= max) { return Math.Abs(value.z) <= max; } return false; } private static bool NearPeer(ZNetPeer peer, Vector3 position, float max) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (max <= 0f || ((ZDOID)(ref peer.m_characterID)).IsNone()) { return true; } Vector3 refPos = peer.GetRefPos(); if (!Finite(refPos.x) || !Finite(refPos.y) || !Finite(refPos.z)) { return false; } float num = refPos.x - position.x; float num2 = refPos.z - position.z; return num * num + num2 * num2 <= max * max; } private static bool WithinMove(Vector3 previous, Vector3 current, float max) { //IL_0008: 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) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: 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_0052: Unknown result type (might be due to invalid IL or missing references) if (max <= 0f || !Finite(previous.x) || !Finite(previous.y) || !Finite(previous.z)) { return true; } float num = previous.x - current.x; float num2 = previous.y - current.y; float num3 = previous.z - current.z; return num * num + num2 * num2 + num3 * num3 <= max * max; } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } }