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 SwapGear v1.2.1
BepInEx/plugins/SwapGear/SwapGear.dll
Decompiled a week agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; 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 Kriona.Shared; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Kriona")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Kriona Hagen - MIT License")] [assembly: AssemblyFileVersion("1.2.1.0")] [assembly: AssemblyInformationalVersion("1.2.1")] [assembly: AssemblyProduct("Swap Gear")] [assembly: AssemblyTitle("Swap Gear")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.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 SwapGear { [HarmonyPatch(typeof(Switch), "GetHoverText")] public static class SwitchHoverPatch { private static void Postfix(Switch __instance, ref string __result) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (!Swap.Running) { ArmorStand componentInParent = ((Component)__instance).GetComponentInParent<ArmorStand>(); if (!((Object)(object)componentInParent == (Object)null) && Swap.CanSwap(componentInParent)) { __result = __result + "\n[<color=yellow><b>" + ((object)Plugin.SwapKey.Value/*cast due to .constrained prefix*/).ToString() + "</b></color>] Swap gear (Esc to cancel)"; } } } } [BepInPlugin("kriona.SwapGear", "Swap Gear", "1.2.1")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "kriona.SwapGear"; public const string PluginName = "Swap Gear"; public const string PluginVersion = "1.2.1"; public static ConfigEntry<KeyboardShortcut> SwapKey; public static ConfigEntry<float> PickupTimeout; public static ConfigEntry<bool> InstantEquip; private Harmony harmony; private Coroutine swap; private ConfigWatcher watcher; private void Awake() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown SwapKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "SwapKey", new KeyboardShortcut((KeyCode)121, Array.Empty<KeyCode>()), "Key to press while looking at an armor stand to swap your gear with what's on it - a Unity KeyCode name, optionally with modifiers, e.g. Y or LeftControl + Y"); PickupTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("General", "PickupTimeout", 10f, "Seconds to wait for the armor stand's items to be picked up before giving up on equipping them"); InstantEquip = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "InstantEquip", false, "Unequip and equip gear instantly instead of taking as long as it does from the inventory"); watcher = new ConfigWatcher(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger, "Swap Gear"); harmony = new Harmony("kriona.SwapGear"); harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Swap Gear 1.2.1 loaded"); } private void Update() { watcher.Update(); if (Swap.Running || !IsSwapKeyDown()) { return; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && ((Character)localPlayer).TakeInput()) { ArmorStand hoveredStand = Swap.GetHoveredStand(localPlayer); if ((Object)(object)hoveredStand != (Object)null) { swap = ((MonoBehaviour)this).StartCoroutine(Swap.Run(localPlayer, hoveredStand)); } } } private static bool IsSwapKeyDown() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = SwapKey.Value; if ((int)((KeyboardShortcut)(ref value)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey, false)) { return false; } foreach (KeyCode modifier in ((KeyboardShortcut)(ref value)).Modifiers) { if (!ZInput.GetKey(modifier, false)) { return false; } } return true; } private void OnDestroy() { if (swap != null) { ((MonoBehaviour)this).StopCoroutine(swap); Swap.Running = false; } watcher?.Dispose(); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } internal static class Swap { private class StandItem { public int Slot; public string PrefabName; public string Name; public ItemType Type; public ItemData Worn; public ItemData PickedUp; public bool Queued; public bool Done; } private const float OwnershipTimeout = 5f; private const float AttachTimeout = 2f; public static bool Running; public static ArmorStand GetHoveredStand(Player player) { GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return null; } ArmorStand componentInParent = hoverObject.GetComponentInParent<ArmorStand>(); if (!((Object)(object)componentInParent != (Object)null) || !CanSwap(componentInParent)) { return null; } return componentInParent; } public static bool CanSwap(ArmorStand stand) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!stand.m_nview.IsValid() || !PrivateArea.CheckAccess(((Component)stand).transform.position, 0f, false, false)) { return false; } for (int i = 0; i < stand.m_slots.Count; i++) { if (stand.HaveAttachment(i)) { return true; } } return false; } public static IEnumerator Run(Player player, ArmorStand stand) { Running = true; try { List<StandItem> items = GetStandItems(stand); if (items.Count == 0) { yield break; } foreach (StandItem item2 in items) { ItemData worn = GetWornItem(player, item2.Type); if (worn != null && !items.Exists((StandItem other) => other.Worn == worn)) { item2.Worn = worn; } } if (!stand.m_nview.IsOwner()) { stand.m_nview.InvokeRPC("RPC_RequestOwn", Array.Empty<object>()); } foreach (StandItem item3 in items) { if (item3.Worn != null) { StartUnequip(player, item3.Worn); } } while (items.Exists((StandItem standItem) => standItem.Worn != null && ((Humanoid)player).IsItemEquiped(standItem.Worn))) { if (IsCanceled(player) || !IsStandValid(stand)) { CancelQueued(player, items); yield break; } if (items.Exists((StandItem standItem) => standItem.Worn != null && ((Humanoid)player).IsItemEquiped(standItem.Worn) && !player.IsEquipActionQueued(standItem.Worn))) { CancelQueued(player, items); ((Character)player).Message((MessageType)2, "Swap gear interrupted", 0, (Sprite)null, false); yield break; } yield return null; } float deadline = Time.time + 5f; while (!stand.m_nview.IsOwner()) { if (IsCanceled(player) || !IsStandValid(stand)) { yield break; } if (Time.time > deadline) { ((Character)player).Message((MessageType)2, "Swap gear failed - the armor stand is busy", 0, (Sprite)null, false); yield break; } yield return null; } HashSet<ItemData> before = new HashSet<ItemData>(((Humanoid)player).GetInventory().GetAllItems()); foreach (StandItem item4 in items) { stand.DropItem(item4.Slot); } foreach (StandItem item in items) { if (item.Worn == null) { continue; } int slot = FindSlot(stand, item.Slot, item.Worn); if (slot < 0 || !stand.UseItem(stand.m_slots[slot].m_switch, (Humanoid)(object)player, item.Worn)) { continue; } deadline = Time.time + 2f; while (!stand.HaveAttachment(slot) && ((Humanoid)player).GetInventory().ContainsItem(item.Worn) && Time.time < deadline) { if (!IsCanceled(player) && IsStandValid(stand)) { yield return null; continue; } yield break; } } deadline = Time.time + Plugin.PickupTimeout.Value; while (!EquipPickedUp(player, items, before, Time.time > deadline)) { if (IsCanceled(player)) { CancelQueued(player, items); yield break; } yield return null; } List<string> list = new List<string>(); foreach (StandItem item5 in items) { if (item5.PickedUp == null) { list.Add(item5.Name); } } string text = ((list.Count == 0) ? "Gear swapped" : ("Swap gear - not picked up: " + string.Join(", ", list))); ((Character)player).Message((MessageType)2, Localization.instance.Localize(text), 0, (Sprite)null, false); } finally { Running = false; } } private static List<StandItem> GetStandItems(ArmorStand stand) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) List<StandItem> list = new List<StandItem>(); for (int i = 0; i < stand.m_slots.Count; i++) { if (stand.HaveAttachment(i)) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(stand.GetAttachedItem(i)); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if (!((Object)(object)val == (Object)null)) { list.Add(new StandItem { Slot = i, PrefabName = ((Object)itemPrefab).name, Name = val.m_itemData.m_shared.m_name, Type = val.m_itemData.m_shared.m_itemType }); } } } return list; } private static ItemData GetWornItem(Player player, ItemType type) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected I4, but got Unknown //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Invalid comparison between Unknown and I4 ItemData result = ((Humanoid)player).m_rightItem ?? ((Humanoid)player).m_hiddenRightItem; ItemData val = ((Humanoid)player).m_leftItem ?? ((Humanoid)player).m_hiddenLeftItem; switch (type - 3) { case 3: return ((Humanoid)player).m_helmetItem; case 4: return ((Humanoid)player).m_chestItem; case 8: return ((Humanoid)player).m_legItem; case 14: return ((Humanoid)player).m_shoulderItem; case 15: return ((Humanoid)player).m_utilityItem; case 21: return ((Humanoid)player).m_trinketItem; case 1: case 2: case 19: return val; case 12: if (val != null && (int)val.m_shared.m_itemType == 15) { return val; } return result; case 0: case 11: case 16: return result; default: return null; } } private static int FindSlot(ArmorStand stand, int preferred, ItemData item) { if (Fits(stand, preferred, item)) { return preferred; } for (int i = 0; i < stand.m_slots.Count; i++) { if (Fits(stand, i, item)) { return i; } } return -1; } private static bool Fits(ArmorStand stand, int slot, ItemData item) { if (!stand.HaveAttachment(slot)) { return stand.CanAttach(stand.m_slots[slot], item); } return false; } private static bool IsInstant(ItemData item) { if (!Plugin.InstantEquip.Value) { return item.m_shared.m_equipDuration <= 0f; } return true; } private static void StartUnequip(Player player, ItemData item) { if (IsInstant(item) || !((Humanoid)player).IsItemEquiped(item)) { ((Humanoid)player).UnequipItem(item, true); } else if (!player.IsEquipActionQueued(item)) { player.QueueUnequipAction(item); } } private static void CancelQueued(Player player, List<StandItem> items) { foreach (StandItem item in items) { ((Humanoid)player).RemoveEquipAction(item.Worn); ((Humanoid)player).RemoveEquipAction(item.PickedUp); } } private static bool EquipPickedUp(Player player, List<StandItem> items, HashSet<ItemData> before, bool timedOut) { bool result = true; foreach (StandItem item in items) { if (item.Done) { continue; } if (item.PickedUp == null) { item.PickedUp = FindPickedUp(player, items, before, item.PrefabName); } if (item.PickedUp == null) { if (timedOut) { item.Done = true; } else { result = false; } } else if (item.Queued) { if (((Humanoid)player).IsItemEquiped(item.PickedUp) || !player.IsEquipActionQueued(item.PickedUp)) { item.Done = true; } else { result = false; } } else if (!IsInstant(item.PickedUp) && !((Humanoid)player).IsItemEquiped(item.PickedUp)) { player.QueueEquipAction(item.PickedUp); item.Queued = true; result = false; } else if (((Humanoid)player).IsItemEquiped(item.PickedUp) || ((Humanoid)player).EquipItem(item.PickedUp, true)) { item.Done = true; } else if (((Character)player).InAttack() || ((Character)player).InDodge()) { result = false; } else { item.Done = true; } } return result; } private static ItemData FindPickedUp(Player player, List<StandItem> items, HashSet<ItemData> before, string prefabName) { foreach (ItemData item in ((Humanoid)player).GetInventory().GetAllItems()) { if (!before.Contains(item) && !((Object)(object)item.m_dropPrefab == (Object)null) && !(((Object)item.m_dropPrefab).name != prefabName) && !items.Exists((StandItem other) => other.PickedUp == item)) { return item; } } return null; } private static bool IsCanceled(Player player) { if ((Object)(object)player == (Object)null || (Object)(object)Player.m_localPlayer != (Object)(object)player || ((Character)player).IsDead()) { return true; } if (ZInput.GetKeyDown((KeyCode)27, false)) { ((Character)player).Message((MessageType)2, "Swap gear canceled", 0, (Sprite)null, false); return true; } return false; } private static bool IsStandValid(ArmorStand stand) { if ((Object)(object)stand != (Object)null) { return stand.m_nview.IsValid(); } return false; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } } namespace Kriona.Shared { internal class ConfigMirror { private readonly ConfigFile config; private readonly Action changed; private bool syncing; public ConfigMirror(ConfigFile config, Action changed) { this.config = config; this.changed = changed; config.SaveOnConfigSet = false; config.SettingChanged += OnSettingChanged; } public ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description, AcceptableValueBase acceptableValues, int? order, string displayName) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes(); configurationManagerAttributes.Order = order; configurationManagerAttributes.DispName = displayName; return config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, acceptableValues, new object[1] { configurationManagerAttributes })); } public void Remove(ConfigEntryBase entry) { config.Remove(entry.Definition); } public void Sync(Action apply) { syncing = true; try { apply(); } finally { syncing = false; } } public static void SetDisplayName(ConfigEntryBase entry, string displayName) { object[] tags = entry.Description.Tags; for (int i = 0; i < tags.Length; i++) { if (tags[i] is ConfigurationManagerAttributes configurationManagerAttributes) { configurationManagerAttributes.DispName = displayName; } } } private void OnSettingChanged(object sender, SettingChangedEventArgs args) { if (!syncing) { changed(); } } } internal sealed class ConfigurationManagerAttributes { public int? Order; public string DispName; } internal class ConfigWatcher : IDisposable { private const float ReloadDelay = 0.5f; private readonly string path; private readonly Action reload; private readonly ManualLogSource log; private readonly string pluginName; private readonly FileSystemWatcher watcher; private string knownContents; private volatile bool fileChanged; private float reloadTime = -1f; public ConfigWatcher(string path, Action reload, ManualLogSource log, string pluginName) { this.path = path; this.reload = reload; this.log = log; this.pluginName = pluginName; Remember(); try { watcher = new FileSystemWatcher(Path.GetDirectoryName(path), Path.GetFileName(path)); watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite; watcher.Changed += delegate { fileChanged = true; }; watcher.Created += delegate { fileChanged = true; }; watcher.Renamed += delegate { fileChanged = true; }; watcher.EnableRaisingEvents = true; } catch (Exception ex) { watcher = null; log.LogWarning((object)("Couldn't watch " + path + " for changes, so changes made to it while Valheim is running aren't read: " + ex.Message)); } } public ConfigWatcher(ConfigFile config, ManualLogSource log, string pluginName) : this(config.ConfigFilePath, delegate { ReloadConfig(config); }, log, pluginName) { } private static void ReloadConfig(ConfigFile config) { bool saveOnConfigSet = config.SaveOnConfigSet; config.SaveOnConfigSet = false; try { config.Reload(); } finally { config.SaveOnConfigSet = saveOnConfigSet; } if (saveOnConfigSet && !MatchesFile(config)) { config.Save(); } } private static bool MatchesFile(ConfigFile config) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown Dictionary<ConfigDefinition, string> dictionary = new Dictionary<ConfigDefinition, string>(); string text = string.Empty; string[] array = File.ReadAllLines(config.ConfigFilePath); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.StartsWith("#")) { continue; } if (text2.StartsWith("[") && text2.EndsWith("]")) { text = text2.Substring(1, text2.Length - 2); continue; } string[] array2 = text2.Split(new char[1] { '=' }, 2); if (array2.Length == 2) { dictionary[new ConfigDefinition(text, array2[0].Trim())] = array2[1].Trim(); } } foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in config) { if (!dictionary.TryGetValue(item.Key, out var value)) { return false; } try { if (!object.Equals(TomlTypeConverter.ConvertToValue(value, item.Value.SettingType), item.Value.BoxedValue)) { return false; } } catch (Exception) { return false; } } return true; } public void Update() { if (fileChanged) { fileChanged = false; reloadTime = Time.realtimeSinceStartup + 0.5f; } if (reloadTime < 0f || Time.realtimeSinceStartup < reloadTime) { return; } reloadTime = -1f; string text; try { text = File.ReadAllText(path); } catch (FileNotFoundException) { return; } catch (IOException) { reloadTime = Time.realtimeSinceStartup + 0.5f; return; } if (text == knownContents) { return; } reload(); Remember(); if (pluginName != null) { log.LogInfo((object)("Reloaded " + Path.GetFileName(path))); MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, pluginName + ": config reloaded", 0, (Sprite)null, false, true); } } } public void Remember() { try { knownContents = File.ReadAllText(path); } catch (IOException) { knownContents = null; } } public void Dispose() { watcher?.Dispose(); } } internal class StateEntries { public const string Inherit = "inherit"; private static readonly AcceptableValueList<string> values = new AcceptableValueList<string>(new string[3] { "inherit", "true", "false" }); private readonly ConfigMirror mirror; private readonly string help; private readonly Dictionary<string, ConfigEntry<string>> entries = new Dictionary<string, ConfigEntry<string>>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> set = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public StateEntries(ConfigMirror mirror, string help) { this.mirror = mirror; this.help = help; } public void Set(string section, string key, string displayName, bool? state) { string entryName = GetEntryName(key, displayName); if (entries.TryGetValue(key, out var value) && ((ConfigEntryBase)value).Definition.Section != section) { mirror.Remove((ConfigEntryBase)(object)value); entries.Remove(key); value = null; } if (value == null) { try { value = mirror.Bind(section, key, "inherit", "Prefab " + key + " - " + help, (AcceptableValueBase)(object)values, null, entryName); } catch (ArgumentException) { return; } entries[key] = value; } else { ConfigMirror.SetDisplayName((ConfigEntryBase)(object)value, entryName); } value.Value = Format(state); set.Add(key); } public void RemoveUnset() { List<string> list = new List<string>(); foreach (KeyValuePair<string, ConfigEntry<string>> entry in entries) { if (!set.Contains(entry.Key)) { mirror.Remove((ConfigEntryBase)(object)entry.Value); list.Add(entry.Key); } } foreach (string item in list) { entries.Remove(item); } set.Clear(); } public bool TryGetState(string key, out bool? state) { state = null; if (!entries.TryGetValue(key, out var value)) { return false; } if (bool.TryParse(value.Value, out var result)) { state = result; } return true; } public static string Format(bool? state) { if (!state.HasValue) { return "inherit"; } if (!state.Value) { return "false"; } return "true"; } private static string GetEntryName(string key, string displayName) { string text = displayName.Trim(); if (text.Length == 0 || text.Equals(key, StringComparison.OrdinalIgnoreCase)) { return key; } return text + " (" + key + ")"; } } }