Decompiled source of ValheimAnticheat v0.0.1

ValheimAnticheat.dll

Decompiled 2 weeks ago
using 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;
		}
	}
}