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 list = new List(inventory.GetAllItems()); if (list == null || list.Count == 0) { return 0; } List list2 = new List(); List list3 = new List(); 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 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(); 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(); 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(); 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(); 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() where T : Object { return Object.FindObjectsByType((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(); 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 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 Enabled; internal static ConfigEntry RemoveFlaggedItems; internal static ConfigEntry RemoveKnownCheatPrefabs; internal static ConfigEntry ExtraPrefabNames; internal static ConfigEntry RemoveOverstacked; internal static ConfigEntry RemoveOverquality; internal static ConfigEntry ScanPlayers; internal static ConfigEntry ScanWorldDrops; internal static ConfigEntry ScanContainers; internal static ConfigEntry ScanItemStands; internal static ConfigEntry ScanIntervalSeconds; internal static ConfigEntry NotifyPlayers; internal static ConfigEntry LogDeletions; internal static ConfigEntry DryRun; internal static void Bind(ConfigFile config) { Enabled = config.Bind("1 - General", "Enabled", true, "Master switch. When off, the plugin does nothing."); RemoveFlaggedItems = config.Bind("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("2 - Detection", "RemoveKnownCheatPrefabs", true, "Delete vanilla debug/cheat prefabs such as SwordCheat and SledgeCheat even if they are not flagged."); ExtraPrefabNames = config.Bind("2 - Detection", "ExtraPrefabNames", "", "Comma-separated extra prefab names to delete (e.g. HammerCheat, MyModItem). Case-insensitive."); RemoveOverstacked = config.Bind("2 - Detection", "RemoveOverstacked", false, "Delete stacks larger than the item's vanilla max stack size."); RemoveOverquality = config.Bind("2 - Detection", "RemoveOverquality", false, "Delete items whose quality is higher than the prefab allows."); ScanPlayers = config.Bind("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("3 - Scan", "ScanWorldDrops", true, "Delete matching items lying on the ground in loaded zones."); ScanContainers = config.Bind("3 - Scan", "ScanContainers", true, "Remove matching items from chests, carts, ships, tombstones, and other containers in loaded zones."); ScanItemStands = config.Bind("3 - Scan", "ScanItemStands", true, "Remove matching items from item stands and armor stands."); ScanIntervalSeconds = config.Bind("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("4 - Output", "NotifyPlayers", true, "Show a center-screen message to a player when items are taken from their inventory."); LogDeletions = config.Bind("4 - Output", "LogDeletions", true, "Write a BepInEx log line for every deleted item."); DryRun = config.Bind("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 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"); } } } } }