Decompiled source of NoCheatItems v1.0.1

NoCheatItems.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
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("NoCheatItems")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+3519b007f6c06dca33c61898ebb8addb1cca0f07")]
[assembly: AssemblyProduct("NoCheatItems")]
[assembly: AssemblyTitle("NoCheatItems")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 NoCheatItems
{
	internal static class CheatCleaner
	{
		internal static readonly string[] DefaultCheatPrefabs = new string[2] { "SwordCheat", "SledgeCheat" };

		private static bool _sweeping;

		internal static bool Sweeping => _sweeping;

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

		internal static bool IsCheatItem(ItemData item, out string reason)
		{
			reason = null;
			if (item == null)
			{
				return false;
			}
			if (ModConfig.RemoveFlaggedItems.Value && item.m_cheated)
			{
				reason = "1.0 cheated flag";
				return true;
			}
			string prefabName = GetPrefabName(item);
			if (IsBannedPrefab(prefabName))
			{
				reason = "cheat prefab " + prefabName;
				return true;
			}
			SharedData shared = item.m_shared;
			if (shared != null)
			{
				if (ModConfig.RemoveOverstacked.Value && shared.m_maxStackSize > 0 && item.m_stack > shared.m_maxStackSize)
				{
					reason = $"overstacked {item.m_stack}/{shared.m_maxStackSize}";
					return true;
				}
				if (ModConfig.RemoveOverquality.Value && shared.m_maxQuality > 0 && item.m_quality > shared.m_maxQuality)
				{
					reason = $"overquality {item.m_quality}/{shared.m_maxQuality}";
					return true;
				}
			}
			return false;
		}

		internal static bool IsBannedPrefab(string prefab)
		{
			if (string.IsNullOrEmpty(prefab))
			{
				return false;
			}
			string[] defaultCheatPrefabs;
			if (ModConfig.RemoveKnownCheatPrefabs.Value)
			{
				defaultCheatPrefabs = DefaultCheatPrefabs;
				foreach (string value in defaultCheatPrefabs)
				{
					if (prefab.Equals(value, StringComparison.OrdinalIgnoreCase))
					{
						return true;
					}
				}
			}
			string value2 = ModConfig.ExtraPrefabNames.Value;
			if (string.IsNullOrWhiteSpace(value2))
			{
				return false;
			}
			defaultCheatPrefabs = value2.Split(new char[3] { ',', ';', '\n' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text in defaultCheatPrefabs)
			{
				if (prefab.Equals(text.Trim(), StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		internal static string GetPrefabName(ItemData item)
		{
			if ((Object)(object)item?.m_dropPrefab != (Object)null)
			{
				return StripClone(((Object)item.m_dropPrefab).name);
			}
			if (item?.m_shared == null)
			{
				return "";
			}
			return item.m_shared.m_name;
		}

		internal static string StripClone(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return "";
			}
			int num = name.IndexOf('(');
			if (num <= 0)
			{
				return name;
			}
			return name.Substring(0, num).Trim();
		}

		internal static int SweepInventory(Inventory inventory, string context, Player owner = null)
		{
			if (inventory == null || !ModConfig.Enabled.Value)
			{
				return 0;
			}
			List<ItemData> list = new List<ItemData>(inventory.GetAllItems());
			if (list == null || list.Count == 0)
			{
				return 0;
			}
			List<ItemData> list2 = new List<ItemData>();
			List<string> list3 = new List<string>();
			foreach (ItemData item in list)
			{
				if (IsCheatItem(item, out var reason))
				{
					list2.Add(item);
					list3.Add(reason);
				}
			}
			if (list2.Count == 0)
			{
				return 0;
			}
			if ((Object)(object)owner == (Object)null)
			{
				owner = FindPlayer(inventory);
			}
			int num = 0;
			for (int i = 0; i < list2.Count; i++)
			{
				ItemData val = list2[i];
				string text = list3[i];
				string text2 = Describe(val);
				if (ModConfig.LogDeletions.Value)
				{
					string text3 = (((Object)(object)owner != (Object)null) ? owner.GetPlayerName() : context);
					NoCheatItemsPlugin.Log.LogInfo((object)("[" + (ModConfig.DryRun.Value ? "dry-run" : "delete") + "] " + text2 + " (" + text + ") from " + text3));
				}
				if (ModConfig.DryRun.Value)
				{
					num++;
					continue;
				}
				if ((Object)(object)owner != (Object)null && val.m_equipped)
				{
					((Humanoid)owner).UnequipItem(val, false);
				}
				inventory.RemoveItem(val);
				num++;
			}
			if (num > 0 && (Object)(object)owner != (Object)null && ModConfig.NotifyPlayers.Value && !ModConfig.DryRun.Value)
			{
				((Character)owner).Message((MessageType)2, (num == 1) ? "Cheated item removed." : $"{num} cheated items removed.", 0, (Sprite)null, false);
			}
			return num;
		}

		internal static void HandleWorldDrop(ItemDrop drop)
		{
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: 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)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			if (ModConfig.Enabled.Value && ModConfig.ScanWorldDrops.Value && IsServer() && !((Object)(object)drop == (Object)null) && HasValidNetView((Component)(object)drop) && (IsCheatItem(drop.m_itemData, out var reason) || IsBannedPrefab(StripClone(((Object)((Component)drop).gameObject).name))))
			{
				string text = Describe(drop.m_itemData);
				if (string.IsNullOrEmpty(text))
				{
					text = StripClone(((Object)((Component)drop).gameObject).name);
				}
				if (ModConfig.LogDeletions.Value)
				{
					Vector3 position = ((Component)drop).transform.position;
					NoCheatItemsPlugin.Log.LogInfo((object)string.Format("[{0}] world drop {1} ({2}) at {3:F0},{4:F0},{5:F0}", ModConfig.DryRun.Value ? "dry-run" : "delete", text, reason ?? "cheat prefab", position.x, position.y, position.z));
				}
				if (!ModConfig.DryRun.Value)
				{
					DestroyNetworked(((Component)drop).gameObject);
				}
			}
		}

		internal static void SweepLoadedWorld(string context)
		{
			if (_sweeping || !ModConfig.Enabled.Value || !IsServer())
			{
				return;
			}
			_sweeping = true;
			try
			{
				if (ModConfig.ScanPlayers.Value)
				{
					List<Player> allPlayers = Player.GetAllPlayers();
					if (allPlayers != null)
					{
						foreach (Player item in allPlayers)
						{
							if (!((Object)(object)item == (Object)null))
							{
								SweepInventory(((Humanoid)item).GetInventory(), "player " + item.GetPlayerName(), item);
							}
						}
					}
				}
				if (ModConfig.ScanContainers.Value)
				{
					Container[] array = FindLoaded<Container>();
					foreach (Container val in array)
					{
						if (!((Object)(object)val == (Object)null) && HasValidNetView((Component)(object)val))
						{
							SweepInventory(val.GetInventory(), DescribeContainer(val));
						}
					}
				}
				if (ModConfig.ScanWorldDrops.Value)
				{
					ItemDrop[] array2 = FindLoaded<ItemDrop>();
					for (int i = 0; i < array2.Length; i++)
					{
						HandleWorldDrop(array2[i]);
					}
				}
				if (ModConfig.ScanItemStands.Value)
				{
					SweepStands();
				}
			}
			catch (Exception arg)
			{
				NoCheatItemsPlugin.Log.LogError((object)$"Sweep ({context}) failed: {arg}");
			}
			finally
			{
				_sweeping = false;
			}
		}

		internal static void RunGuardedInventorySweep(Inventory inventory)
		{
			if (_sweeping || inventory == null || !ModConfig.Enabled.Value || !IsServer())
			{
				return;
			}
			bool value = ModConfig.ScanPlayers.Value;
			bool value2 = ModConfig.ScanContainers.Value;
			if (!value && !value2)
			{
				return;
			}
			Player val = FindPlayer(inventory);
			if ((Object)(object)val != (Object)null)
			{
				if (!value)
				{
					return;
				}
			}
			else if (!value2)
			{
				return;
			}
			_sweeping = true;
			try
			{
				SweepInventory(inventory, ((Object)(object)val != (Object)null) ? ("player " + val.GetPlayerName()) : "container", val);
			}
			finally
			{
				_sweeping = false;
			}
		}

		private static void SweepStands()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			ItemStand[] array = FindLoaded<ItemStand>();
			foreach (ItemStand val in array)
			{
				if ((Object)(object)val == (Object)null || !HasValidNetView((Component)(object)val) || !val.HaveAttachment())
				{
					continue;
				}
				string text = PrefabNameFromHash(val.GetAttachedItem());
				if (string.IsNullOrEmpty(text))
				{
					text = val.m_currentItemName;
				}
				if (!string.IsNullOrEmpty(text) && IsBannedPrefab(StripClone(text)))
				{
					LogStand("item stand", text, ((Component)val).transform.position);
					if (!ModConfig.DryRun.Value)
					{
						val.DestroyAttachment();
					}
				}
			}
			ArmorStand[] array2 = FindLoaded<ArmorStand>();
			foreach (ArmorStand val2 in array2)
			{
				if ((Object)(object)val2 == (Object)null || !HasValidNetView((Component)(object)val2))
				{
					continue;
				}
				for (int j = 0; j < 16; j++)
				{
					if (!val2.HaveAttachment(j))
					{
						continue;
					}
					string text2 = PrefabNameFromHash(val2.GetAttachedItem(j));
					if (!string.IsNullOrEmpty(text2) && IsBannedPrefab(StripClone(text2)))
					{
						LogStand("armor stand", text2, ((Component)val2).transform.position);
						if (!ModConfig.DryRun.Value)
						{
							val2.DestroyAttachment(j);
						}
					}
				}
			}
		}

		private static void LogStand(string kind, string prefab, Vector3 pos)
		{
			//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)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			if (ModConfig.LogDeletions.Value)
			{
				NoCheatItemsPlugin.Log.LogInfo((object)string.Format("[{0}] {1} {2} at {3:F0},{4:F0},{5:F0}", ModConfig.DryRun.Value ? "dry-run" : "delete", kind, prefab, pos.x, pos.y, pos.z));
			}
		}

		private static T[] FindLoaded<T>() where T : Object
		{
			return Object.FindObjectsByType<T>((FindObjectsInactive)0, (FindObjectsSortMode)0);
		}

		private static string PrefabNameFromHash(int hash)
		{
			if (hash == 0 || (Object)(object)ObjectDB.instance == (Object)null)
			{
				return "";
			}
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(hash);
			if (!((Object)(object)itemPrefab != (Object)null))
			{
				return "";
			}
			return StripClone(((Object)itemPrefab).name);
		}

		private static bool HasValidNetView(Component component)
		{
			if ((Object)(object)component == (Object)null)
			{
				return false;
			}
			ZNetView component2 = component.GetComponent<ZNetView>();
			if ((Object)(object)component2 != (Object)null)
			{
				return component2.IsValid();
			}
			return false;
		}

		private static void DestroyNetworked(GameObject go)
		{
			if (!((Object)(object)go == (Object)null))
			{
				if ((Object)(object)ZNetScene.instance != (Object)null)
				{
					ZNetScene.instance.Destroy(go);
				}
				else
				{
					Object.Destroy((Object)(object)go);
				}
			}
		}

		private static Player FindPlayer(Inventory inventory)
		{
			List<Player> allPlayers = Player.GetAllPlayers();
			if (allPlayers == null)
			{
				return null;
			}
			foreach (Player item in allPlayers)
			{
				if ((Object)(object)item != (Object)null && ((Humanoid)item).GetInventory() == inventory)
				{
					return item;
				}
			}
			return null;
		}

		private static string Describe(ItemData item)
		{
			if (item == null)
			{
				return "item";
			}
			string prefabName = GetPrefabName(item);
			string text = ((item.m_shared != null) ? item.m_shared.m_name : "");
			int stack = item.m_stack;
			if (!string.IsNullOrEmpty(prefabName) && prefabName != text)
			{
				return $"{prefabName} x{stack} ({text})";
			}
			if (!string.IsNullOrEmpty(prefabName))
			{
				return $"{prefabName} x{stack}";
			}
			return $"stack {stack}";
		}

		private static string DescribeContainer(Container container)
		{
			//IL_002c: 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_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			string text = ((!string.IsNullOrEmpty(container.m_name)) ? container.m_name : StripClone(((Object)((Component)container).gameObject).name));
			Vector3 position = ((Component)container).transform.position;
			return $"{text} at {position.x:F0},{position.y:F0},{position.z:F0}";
		}
	}
	internal static class ModConfig
	{
		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> RemoveFlaggedItems;

		internal static ConfigEntry<bool> RemoveKnownCheatPrefabs;

		internal static ConfigEntry<string> ExtraPrefabNames;

		internal static ConfigEntry<bool> RemoveOverstacked;

		internal static ConfigEntry<bool> RemoveOverquality;

		internal static ConfigEntry<bool> ScanPlayers;

		internal static ConfigEntry<bool> ScanWorldDrops;

		internal static ConfigEntry<bool> ScanContainers;

		internal static ConfigEntry<bool> ScanItemStands;

		internal static ConfigEntry<float> ScanIntervalSeconds;

		internal static ConfigEntry<bool> NotifyPlayers;

		internal static ConfigEntry<bool> LogDeletions;

		internal static ConfigEntry<bool> DryRun;

		internal static void Bind(ConfigFile config)
		{
			Enabled = config.Bind<bool>("1 - General", "Enabled", true, "Master switch. When off, the plugin does nothing.");
			RemoveFlaggedItems = config.Bind<bool>("2 - Detection", "RemoveFlaggedItems", true, "Delete items with Valheim 1.0's cheated flag (the grey italic tooltip: \"This item was summoned through cheating means.\").");
			RemoveKnownCheatPrefabs = config.Bind<bool>("2 - Detection", "RemoveKnownCheatPrefabs", true, "Delete vanilla debug/cheat prefabs such as SwordCheat and SledgeCheat even if they are not flagged.");
			ExtraPrefabNames = config.Bind<string>("2 - Detection", "ExtraPrefabNames", "", "Comma-separated extra prefab names to delete (e.g. HammerCheat, MyModItem). Case-insensitive.");
			RemoveOverstacked = config.Bind<bool>("2 - Detection", "RemoveOverstacked", false, "Delete stacks larger than the item's vanilla max stack size.");
			RemoveOverquality = config.Bind<bool>("2 - Detection", "RemoveOverquality", false, "Delete items whose quality is higher than the prefab allows.");
			ScanPlayers = config.Bind<bool>("3 - Scan", "ScanPlayers", true, "Remove matching items from online player inventories (including equipped items). Offline characters are client-side and can only be cleaned when that player is connected.");
			ScanWorldDrops = config.Bind<bool>("3 - Scan", "ScanWorldDrops", true, "Delete matching items lying on the ground in loaded zones.");
			ScanContainers = config.Bind<bool>("3 - Scan", "ScanContainers", true, "Remove matching items from chests, carts, ships, tombstones, and other containers in loaded zones.");
			ScanItemStands = config.Bind<bool>("3 - Scan", "ScanItemStands", true, "Remove matching items from item stands and armor stands.");
			ScanIntervalSeconds = config.Bind<float>("3 - Scan", "ScanIntervalSeconds", 8f, "How often to sweep loaded players, containers, and world drops. Pickup and inventory changes are also cleaned immediately.");
			NotifyPlayers = config.Bind<bool>("4 - Output", "NotifyPlayers", true, "Show a center-screen message to a player when items are taken from their inventory.");
			LogDeletions = config.Bind<bool>("4 - Output", "LogDeletions", true, "Write a BepInEx log line for every deleted item.");
			DryRun = config.Bind<bool>("4 - Output", "DryRun", false, "Log what would be deleted without actually removing anything.");
		}
	}
	[HarmonyPatch(typeof(Inventory), "Changed")]
	internal static class InventoryChangedPatch
	{
		private static void Postfix(Inventory __instance)
		{
			CheatCleaner.RunGuardedInventorySweep(__instance);
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "Awake")]
	internal static class ItemDropAwakePatch
	{
		private static void Postfix(ItemDrop __instance)
		{
			CheatCleaner.HandleWorldDrop(__instance);
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "Load")]
	internal static class ItemDropLoadPatch
	{
		private static void Postfix(ItemDrop __instance)
		{
			CheatCleaner.HandleWorldDrop(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "OnSpawned")]
	internal static class PlayerSpawnedPatch
	{
		private static void Postfix(Player __instance)
		{
			if (ModConfig.Enabled.Value && ModConfig.ScanPlayers.Value && CheatCleaner.IsServer())
			{
				CheatCleaner.RunGuardedInventorySweep(((Humanoid)__instance).GetInventory());
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	internal static class TerminalInitPatch
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__0_0;

			internal void <Postfix>b__0_0(ConsoleEventArgs args)
			{
				if (!CheatCleaner.IsServer())
				{
					args.Context.AddString("NoCheatItems only runs on the server.");
					return;
				}
				CheatCleaner.SweepLoadedWorld("console");
				args.Context.AddString("NoCheatItems sweep complete. See BepInEx log for details.");
			}
		}

		private static void Postfix()
		{
			//IL_0033: 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_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			object obj = <>c.<>9__0_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					if (!CheatCleaner.IsServer())
					{
						args.Context.AddString("NoCheatItems only runs on the server.");
					}
					else
					{
						CheatCleaner.SweepLoadedWorld("console");
						args.Context.AddString("NoCheatItems sweep complete. See BepInEx log for details.");
					}
				};
				<>c.<>9__0_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("nocheatitems_sweep", "Sweep loaded cheated items now", (ConsoleEvent)obj, false, false, true, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}
	}
	[BepInPlugin("ageous27.nocheatitems", "NoCheatItems", "1.0.1")]
	public class NoCheatItemsPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "ageous27.nocheatitems";

		public const string PluginName = "NoCheatItems";

		public const string PluginVersion = "1.0.1";

		private Harmony _harmony;

		private float _scanTimer;

		internal static NoCheatItemsPlugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("ageous27.nocheatitems");
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			Log.LogInfo((object)"NoCheatItems 1.0.1 loaded. Server-side cheated-item cleanup is active.");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			if (ModConfig.Enabled.Value && CheatCleaner.IsServer())
			{
				float num = Mathf.Max(1f, ModConfig.ScanIntervalSeconds.Value);
				_scanTimer += Time.deltaTime;
				if (!(_scanTimer < num))
				{
					_scanTimer = 0f;
					CheatCleaner.SweepLoadedWorld("periodic");
				}
			}
		}
	}
}