Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PortalMetalLock v1.5.1
PortalMetalLock.dll
Decompiled 19 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Utils; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyVersion("0.0.0.0")] namespace BakaPortalMetalLock; [BepInPlugin("baka.PortalMetalLock", "Portal Metal Lock", "1.5.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class PortalMetalLock : BaseUnityPlugin { private sealed class Sets { public HashSet<string> Keywords; public HashSet<string> Extra; public HashSet<string> Exclude; public HashSet<string> ExcludeTokens; } private sealed class ConfigurationManagerAttributes { public bool? IsAdminOnly; } [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class Patch_ObjectDB_Awake { private static void Postfix(ObjectDB __instance) { ApplyTo(__instance, "ObjectDB.Awake"); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static class Patch_ObjectDB_CopyOtherDB { private static void Postfix(ObjectDB __instance) { ApplyTo(__instance, "ObjectDB.CopyOtherDB"); } } [HarmonyPatch(typeof(ZNetScene), "Awake")] private static class Patch_ZNetScene_Awake { private static void Postfix() { if ((Object)(object)Instance != (Object)null) { ((MonoBehaviour)Instance).StartCoroutine(DelayedRescans()); } } } [HarmonyPatch(typeof(Player), "OnInventoryChanged")] private static class Patch_Player_OnInventoryChanged { private static void Postfix(Player __instance) { if (!((Object)(object)__instance == (Object)null)) { RestampInventory(((Humanoid)__instance).GetInventory()); } } } [HarmonyPatch(typeof(ItemDrop), "Awake")] private static class Patch_ItemDrop_Awake { private static void Postfix(ItemDrop __instance) { if (!((Object)(object)__instance == (Object)null) && __instance.m_itemData != null) { RestampItem(__instance.m_itemData); } } } [HarmonyPatch(typeof(Inventory), "IsTeleportable")] private static class Patch_Inventory_IsTeleportable { private static void Prefix(Inventory __instance) { RestampInventory(__instance, fromTeleportCheck: true); } } [HarmonyPatch(typeof(Humanoid), "IsTeleportable")] private static class Patch_Humanoid_IsTeleportable { private static void Prefix(Humanoid __instance) { if (!((Object)(object)__instance == (Object)null)) { Inventory inventory = __instance.GetInventory(); if (inventory != null) { RestampInventory(inventory, fromTeleportCheck: true); } } } } public const string GUID = "baka.PortalMetalLock"; public const string Version = "1.5.1"; internal static ManualLogSource Log; internal static PortalMetalLock Instance; private static ConfigEntry<bool> _enabled; private static ConfigEntry<string> _keywords; private static ConfigEntry<string> _extraPrefabs; private static ConfigEntry<string> _excludePrefabs; private static ConfigEntry<string> _excludeTokens; private static ConfigEntry<bool> _logDecisions; private const string DefaultKeywords = "copper,tin,bronze,iron,silver,blackmetal,flametal,metal,ore,ores,ingot,ingots,steel,nickel,zinc,lead,brass,cupronickel,cupro,bellmetal,frosteel,darksteel,moltensteel,whitemetal,amethysteel,ifrytium,cerium,ferroboron,darkiron,thorium,aurametal,vanadium,gold,electrum,bismuth,tungsten,cobalt,pewter,mithril,adamant,adamantite,orichalcum,platinum,titanium,aluminium,aluminum,chromium"; private const string DefaultExcludeTokens = "nail,nails,meat,necklace,food,fish,key,coin,gem,leather,hide,pelt,trophy,wood,resin,seed"; private const ItemType BlockableType = (ItemType)1; private static readonly HashSet<string> SuspiciousTokens = new HashSet<string>(StringComparer.Ordinal) { "metal", "ore", "ores", "scrap", "scraps", "ingot", "ingots", "bar", "bars", "steel", "iron", "copper", "tin", "bronze", "silver", "blackmetal", "flametal", "nickel", "zinc", "lead", "brass", "cupronickel", "cupro", "bellmetal", "frosteel", "darksteel", "moltensteel", "whitemetal", "amethysteel", "ifrytium", "cerium", "ferroboron", "thorium", "aurametal", "vanadium", "gold", "electrum", "bismuth", "tungsten", "cobalt" }; private static readonly Regex CamelBoundary = new Regex("(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", RegexOptions.Compiled); private static readonly Regex NonAlphaNum = new Regex("[^A-Za-z0-9]+", RegexOptions.Compiled); private static Sets _sets; private static int _restampLogCooldown; private void Awake() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Expected O, but got Unknown //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; Instance = this; _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, new ConfigDescription("Master switch. When on, matching raw metal/ore/bar/scrap Material items are made non-teleportable so wood portals refuse them. ALL equipment goes through, and the Ashlands stone portal still carries everything (vanilla behaviour).", (AcceptableValueBase)null, new object[1] { AdminOnly() })); _keywords = ((BaseUnityPlugin)this).Config.Bind<string>("General", "MetalKeywords", "copper,tin,bronze,iron,silver,blackmetal,flametal,metal,ore,ores,ingot,ingots,steel,nickel,zinc,lead,brass,cupronickel,cupro,bellmetal,frosteel,darksteel,moltensteel,whitemetal,amethysteel,ifrytium,cerium,ferroboron,darkiron,thorium,aurametal,vanadium,gold,electrum,bismuth,tungsten,cobalt,pewter,mithril,adamant,adamantite,orichalcum,platinum,titanium,aluminium,aluminum,chromium", new ConfigDescription("Comma-separated, case-insensitive keywords. Names are split into tokens on camelCase and non-letters, and an item is blocked if any token EQUALS one of these keywords (whole-token match, not substring -- so 'tin' will not match 'chitin' nor 'ore' match 'core'). Only items of type Material are ever considered, so equipment that contains a metal word is never affected.", (AcceptableValueBase)null, new object[1] { AdminOnly() })); _extraPrefabs = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ExtraPrefabs", "", new ConfigDescription("Comma-separated EXACT prefab names to ALWAYS force non-teleportable (overrides type/keyword/exclude checks), for mod metals the keywords miss. Use VNEI in-game, or the PortalMetalLock_items.txt dump, to find exact names.", (AcceptableValueBase)null, new object[1] { AdminOnly() })); _excludePrefabs = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ExcludePrefabs", "BronzeNails,IronNails,LeatherScraps,SilverNecklace,BrassChain_bal", new ConfigDescription("Comma-separated EXACT prefab names to NEVER touch (false-positive guard, highest priority after ExtraPrefabs).", (AcceptableValueBase)null, new object[1] { AdminOnly() })); _excludeTokens = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ExcludeTokens", "nail,nails,meat,necklace,food,fish,key,coin,gem,leather,hide,pelt,trophy,wood,resin,seed", new ConfigDescription("Comma-separated tokens; any Material item whose name contains one of these tokens is left teleportable even if a metal keyword matched. Keeps nails, leather scraps, food and necklaces portable.", (AcceptableValueBase)null, new object[1] { AdminOnly() })); _logDecisions = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "LogDecisions", true, new ConfigDescription("When true, after each ObjectDB scan the mod writes a full item dump to BepInEx/config/PortalMetalLock_items.txt (prefab | display | type | teleportable | decision) and logs which metal-ish items it SKIPPED and why. Use this to verify classification and tune ExtraPrefabs/ExcludePrefabs. Set false to quieten once tuned.", (AcceptableValueBase)null, new object[1] { AdminOnly() })); Harmony val = new Harmony("baka.PortalMetalLock"); val.PatchAll(); IEnumerable<MethodBase> patchedMethods = val.GetPatchedMethods(); HashSet<string> hashSet = new HashSet<string>(); foreach (MethodBase item in patchedMethods) { hashSet.Add(item.DeclaringType?.FullName + "." + item.Name); } bool num = hashSet.Contains("Inventory.IsTeleportable"); bool flag = hashSet.Contains("Humanoid.IsTeleportable"); Log.LogInfo((object)("PortalMetalLock v1.5.1 loaded. Harmony patches applied: " + string.Join(", ", hashSet))); if (!num) { Log.LogWarning((object)"CRITICAL: Inventory.IsTeleportable patch MISSING -- teleport gate may not fire!"); } if (!flag) { Log.LogWarning((object)"CRITICAL: Humanoid.IsTeleportable patch MISSING -- teleport gate fallback unavailable!"); } } private static ConfigurationManagerAttributes AdminOnly() { return new ConfigurationManagerAttributes { IsAdminOnly = true }; } private static Sets RebuildSets() { Sets obj = new Sets { Keywords = SplitSet(_keywords.Value, lower: true), Extra = SplitSet(_extraPrefabs.Value, lower: false), Exclude = SplitSet(_excludePrefabs.Value, lower: false), ExcludeTokens = SplitSet(_excludeTokens.Value, lower: true) }; _sets = obj; return obj; } private static Sets CurrentSets() { return _sets ?? RebuildSets(); } private static bool ShouldBlock(string prefab, SharedData shared, Sets s) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Invalid comparison between Unknown and I4 if (shared == null) { return false; } if (!string.IsNullOrEmpty(prefab) && s.Extra.Contains(prefab)) { return true; } if (!string.IsNullOrEmpty(prefab) && s.Exclude.Contains(prefab)) { return false; } if ((int)shared.m_itemType != 1) { return false; } HashSet<string> hashSet = Tokenize(prefab, shared.m_name); if (hashSet.Overlaps(s.ExcludeTokens)) { return false; } return hashSet.Overlaps(s.Keywords); } private static IEnumerator DelayedRescans() { float[] array = new float[3] { 12f, 18f, 30f }; for (int i = 0; i < array.Length; i++) { float delay = array[i]; yield return (object)new WaitForSeconds(delay); ApplyTo(ObjectDB.instance, "delayed rescan +" + delay + "s"); } } private static void RestampInventory(Inventory inv, bool fromTeleportCheck = false) { if (_enabled == null || !_enabled.Value || inv == null) { return; } List<ItemData> allItems = inv.GetAllItems(); if (allItems == null) { return; } int num = 0; for (int i = 0; i < allItems.Count; i++) { if (RestampItem(allItems[i])) { num++; } } if (fromTeleportCheck && num > 0 && ++_restampLogCooldown % 60 == 1) { Log.LogInfo((object)("IsTeleportable Prefix re-stamped " + num + " item(s) that had reverted to teleportable=true.")); } } private static bool RestampItem(ItemData item) { if (_enabled == null || !_enabled.Value || item == null) { return false; } SharedData shared = item.m_shared; if (shared == null || !shared.m_teleportable) { return false; } if (ShouldBlock(((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null, shared, CurrentSets())) { shared.m_teleportable = false; return true; } return false; } private static int ApplyTo(ObjectDB odb, string source) { //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Invalid comparison between Unknown and I4 if (_enabled == null || !_enabled.Value) { return 0; } if ((Object)(object)odb == (Object)null || odb.m_items == null) { return 0; } Sets sets = RebuildSets(); bool value = _logDecisions.Value; int num = 0; List<string> list = (value ? new List<string>(odb.m_items.Count) : null); List<string> list2 = (value ? new List<string>() : null); for (int i = 0; i < odb.m_items.Count; i++) { GameObject val = odb.m_items[i]; if ((Object)(object)val == (Object)null) { continue; } ItemDrop component = val.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null || component.m_itemData == null) { continue; } SharedData shared = component.m_itemData.m_shared; if (shared == null) { continue; } string text = ((Object)val).name ?? string.Empty; bool flag = ShouldBlock(text, shared, sets); if (flag && shared.m_teleportable) { shared.m_teleportable = false; num++; } if (value) { string text2 = (flag ? "BLOCK" : (sets.Exclude.Contains(text) ? "skip(ExcludePrefab)" : (((int)shared.m_itemType != 1) ? "skip(non-Material)" : "skip(no-match)"))); list.Add(text + " | " + shared.m_name + " | " + ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString() + " | teleportable=" + shared.m_teleportable + " | " + text2); if (!flag && Tokenize(text, shared.m_name).Overlaps(SuspiciousTokens)) { list2.Add(text + " (type=" + ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString() + ")"); } } } if (num > 0 || value) { Log.LogInfo((object)("PortalMetalLock (" + source + "): marked " + num + " item(s) non-teleportable.")); } if (value) { WriteDump(source, list); if (list2 != null && list2.Count > 0) { Log.LogInfo((object)(" SKIPPED metal-ish (left teleportable -> add to ExtraPrefabs if wrong): " + string.Join(", ", list2.Take(120)) + ((list2.Count > 120) ? (" ...(+" + (list2.Count - 120) + ")") : ""))); } } return num; } private static void WriteDump(string source, List<string> dump) { if (dump == null) { return; } try { string path = Path.Combine(Paths.ConfigPath, "PortalMetalLock_items.txt"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# PortalMetalLock v1.5.1 item dump"); stringBuilder.AppendLine("# scan source: " + source + " time: " + DateTime.Now.ToString("s")); stringBuilder.AppendLine("# prefab | display | itemType | teleportable | decision"); stringBuilder.AppendLine("# (decision BLOCK = made non-teleportable; only Material items can be blocked)"); dump.Sort(StringComparer.OrdinalIgnoreCase); foreach (string item in dump) { stringBuilder.AppendLine(item); } File.WriteAllText(path, stringBuilder.ToString()); } catch (Exception ex) { Log.LogWarning((object)("PortalMetalLock: could not write item dump: " + ex.Message)); } } private static HashSet<string> Tokenize(params string[] names) { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (string text in names) { if (string.IsNullOrEmpty(text)) { continue; } string[] array = NonAlphaNum.Split(text); foreach (string text2 in array) { if (text2.Length == 0) { continue; } string[] array2 = CamelBoundary.Split(text2); foreach (string text3 in array2) { if (text3.Length != 0) { hashSet.Add(text3.ToLowerInvariant()); } } } } return hashSet; } private static HashSet<string> SplitSet(string csv, bool lower) { HashSet<string> hashSet = new HashSet<string>(lower ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(csv)) { return hashSet; } string[] array = csv.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { hashSet.Add(lower ? text.ToLowerInvariant() : text); } } return hashSet; } }