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 CatosChestViewer v0.1.3
CatosChestViewer.dll
Decompiled 5 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Catosaur")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Shows the contents of the chest under the Valheim player's crosshair.")] [assembly: AssemblyFileVersion("0.1.3.0")] [assembly: AssemblyInformationalVersion("0.1.3+e69f2f9f900fe5a5ff041e0e3be3486d604cdc2f")] [assembly: AssemblyProduct("CatosChestViewer")] [assembly: AssemblyTitle("CatosChestViewer")] [assembly: AssemblyVersion("0.1.3.0")] [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 CatosChestViewer { internal sealed class ChestContentsSnapshot { internal IReadOnlyList<ChestItemEntry> Items { get; } internal int OccupiedSlots { get; } internal int TotalSlots { get; } internal string Fingerprint { get; } internal ChestContentsSnapshot(List<ChestItemEntry> items, int occupiedSlots, int totalSlots, string fingerprint) { Items = items.AsReadOnly(); OccupiedSlots = occupiedSlots; TotalSlots = totalSlots; Fingerprint = fingerprint; } } internal sealed class ChestItemEntry { internal string Name { get; } internal int Stack { get; } internal int SourceStackCount { get; } internal bool MatchesPlayerInventory { get; } internal ChestItemEntry(string name, int stack, int sourceStackCount, bool matchesPlayerInventory) { Name = name; Stack = stack; SourceStackCount = sourceStackCount; MatchesPlayerInventory = matchesPlayerInventory; } } internal static class ChestInventoryReader { private sealed class AggregateEntry { internal int Stack; internal int SourceStackCount; } private static Type _localizationType; private static PropertyInfo _localizationInstance; private static MethodInfo _localizeMethod; private static float _nextReadWarningTime; internal static bool TryRead(Container container, Player player, out ChestContentsSnapshot snapshot) { snapshot = null; if (!Object.op_Implicit((Object)(object)container)) { return false; } try { Inventory inventory = container.GetInventory(); if (inventory == null) { return false; } List<ItemData> allItemsInGridOrder = inventory.GetAllItemsInGridOrder(); List<ChestItemEntry> list = new List<ChestItemEntry>(); HashSet<string> playerItemKeys = GetPlayerItemKeys(player); string fingerprint = BuildFingerprint(allItemsInGridOrder, list, playerItemKeys); list.Sort(CompareEntriesByName); int occupiedSlots = Math.Max(0, inventory.NrOfItems()); int totalSlots = GetTotalSlots(inventory, occupiedSlots); snapshot = new ChestContentsSnapshot(list, occupiedSlots, totalSlots, fingerprint); return true; } catch (Exception ex) { if (Time.unscaledTime >= _nextReadWarningTime) { _nextReadWarningTime = Time.unscaledTime + 2f; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Chest inventory read failed: " + ex.Message)); } } return false; } } private static string BuildFingerprint(List<ItemData> items, List<ChestItemEntry> entries, HashSet<string> playerItemKeys) { string text = (ModConfig.StashSenseEnabled.Value ? "stash-sense-on" : "stash-sense-off"); if (items == null || items.Count == 0) { return text + "|empty"; } Dictionary<string, AggregateEntry> dictionary = new Dictionary<string, AggregateEntry>(StringComparer.Ordinal); foreach (ItemData item in items) { if (item != null && item.m_shared != null) { string key = item.m_shared.m_name ?? string.Empty; int num = Math.Max(0, item.m_stack); if (!dictionary.TryGetValue(key, out var value)) { value = new AggregateEntry(); dictionary.Add(key, value); } value.Stack = (int)Math.Min(2147483647L, (long)value.Stack + (long)num); value.SourceStackCount++; } } StringBuilder stringBuilder = new StringBuilder(); List<string> list = new List<string>(dictionary.Keys); list.Sort(StringComparer.Ordinal); foreach (string item2 in list) { AggregateEntry aggregateEntry = dictionary[item2]; bool flag = ModConfig.StashSenseEnabled.Value && playerItemKeys.Contains(item2); entries.Add(new ChestItemEntry(Localize(item2), aggregateEntry.Stack, aggregateEntry.SourceStackCount, flag)); stringBuilder.Append(item2).Append('\u001f').Append(aggregateEntry.Stack) .Append('\u001f') .Append(aggregateEntry.SourceStackCount) .Append('\u001f') .Append(flag ? '1' : '0') .Append('\u001e'); } if (stringBuilder.Length != 0) { return text + "|" + stringBuilder; } return text + "|empty"; } private static HashSet<string> GetPlayerItemKeys(Player player) { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); if (!ModConfig.StashSenseEnabled.Value || !Object.op_Implicit((Object)(object)player)) { return hashSet; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return hashSet; } List<ItemData> allItemsInGridOrder = inventory.GetAllItemsInGridOrder(); if (allItemsInGridOrder == null) { return hashSet; } foreach (ItemData item in allItemsInGridOrder) { if (item?.m_shared != null && !string.IsNullOrWhiteSpace(item.m_shared.m_name)) { hashSet.Add(item.m_shared.m_name); } } return hashSet; } private static int CompareEntriesByName(ChestItemEntry left, ChestItemEntry right) { int num = StringComparer.CurrentCultureIgnoreCase.Compare(left.Name, right.Name); if (num == 0) { return StringComparer.Ordinal.Compare(left.Name, right.Name); } return num; } private static int GetTotalSlots(Inventory inventory, int occupiedSlots) { int num = Math.Max(0, inventory.GetWidth()); int num2 = Math.Max(0, inventory.GetHeight()); long val = (long)num * (long)num2; return (int)Math.Min(2147483647L, Math.Max(val, occupiedSlots)); } internal static string Localize(string value) { if (string.IsNullOrWhiteSpace(value)) { return "Unknown item"; } try { EnsureLocalizationApi(); object obj = _localizationInstance?.GetValue(null, null); if (obj != null && _localizeMethod != null) { string text = _localizeMethod.Invoke(obj, new object[1] { value }) as string; if (!string.IsNullOrWhiteSpace(text) && text != value) { return text; } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Item localization failed: " + ex.Message)); } } return HumanizeKey(value); } private static void EnsureLocalizationApi() { if (_localizationType != null) { return; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType("Localization", throwOnError: false); if (!(type == null)) { _localizationType = type; _localizationInstance = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _localizeMethod = type.GetMethod("Localize", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null); break; } } } private static string HumanizeKey(string value) { string text = value.Trim(); if (text.StartsWith("$", StringComparison.Ordinal)) { text = text.Substring(1); } if (text.StartsWith("item_", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(5); } if (text.StartsWith("piece_", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(6); } text = text.Replace('_', ' ').Trim(); if (text.Length == 0) { return "Unknown item"; } return char.ToUpperInvariant(text[0]) + text.Substring(1); } } internal static class ChestOverlay { private static Container _target; private static string _renderedText; private static string _fingerprint; private static float _nextReadTime; internal static void Apply(Hud hud, Player player) { if (!Object.op_Implicit((Object)(object)hud) || !Object.op_Implicit((Object)(object)player) || !ModConfig.Enabled.Value) { Clear(); return; } if (!ChestTargetController.TryGetAccessibleChest(player, out var container)) { Clear(); return; } float unscaledTime = Time.unscaledTime; bool num = _target != container; if (num) { _target = container; _fingerprint = null; _renderedText = null; _nextReadTime = 0f; } if (num || unscaledTime >= _nextReadTime) { _nextReadTime = unscaledTime + Math.Max(0.025f, (float)ModConfig.UpdateIntervalMs.Value / 1000f); if (!ChestInventoryReader.TryRead(container, player, out var snapshot)) { Clear(); return; } if (!string.Equals(_fingerprint, snapshot.Fingerprint, StringComparison.Ordinal)) { _fingerprint = snapshot.Fingerprint; try { _renderedText = ChestTextFormatter.Format(container, snapshot); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Chest text formatting failed; hiding contents: " + ex.Message)); } Clear(); return; } } } if ((Object)(object)hud.m_hoverName != (Object)null && !string.IsNullOrEmpty(_renderedText)) { ((TMP_Text)hud.m_hoverName).text = _renderedText; } } internal static void Clear() { _target = null; _fingerprint = null; _renderedText = null; _nextReadTime = 0f; } } internal static class ChestTargetController { private static readonly MethodInfo CheckAccessMethod = AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null); private static bool _accessMethodWarningLogged; private static float _nextAccessWarningTime; internal static bool TryGetAccessibleChest(Player player, out Container container) { container = null; if (!Object.op_Implicit((Object)(object)player) || !ModConfig.Enabled.Value) { return false; } GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if (!Object.op_Implicit((Object)(object)hoverObject)) { return false; } Container val = hoverObject.GetComponentInParent<Container>() ?? hoverObject.GetComponentInChildren<Container>(true); if (!Object.op_Implicit((Object)(object)val) || !IsAccessible(val, player.GetPlayerID())) { return false; } container = val; return true; } private static bool IsAccessible(Container container, long playerId) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) try { if (container.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false)) { return false; } if (CheckAccessMethod == null) { if (!_accessMethodWarningLogged) { _accessMethodWarningLogged = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)"Container.CheckAccess(long) was not found; hiding chest contents."); } } return false; } object obj = CheckAccessMethod.Invoke(container, new object[1] { playerId }); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch (Exception ex) { if (Time.unscaledTime >= _nextAccessWarningTime) { _nextAccessWarningTime = Time.unscaledTime + 2f; ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("Chest access check failed; hiding contents: " + ex.Message)); } } return false; } } } internal static class ChestTextFormatter { private static readonly Regex RichTextTag = new Regex("<[^>]*>", RegexOptions.Compiled); private const string HeaderColor = "#FFD166FF"; private const string NameColor = "#F2F5FFFF"; private const string CountColor = "#7CFFB2FF"; private const string StashSenseColor = "#FFF06AFF"; internal static string Format(Container container, ChestContentsSnapshot snapshot) { if (snapshot == null) { return string.Empty; } int num = Math.Max(1, ModConfig.MaxLines.Value); int maxCharacters = Math.Max(100, ModConfig.MaxTextCharacters.Value); StringBuilder stringBuilder = new StringBuilder(); if (ModConfig.ShowHeader.Value) { string value = Clean(ChestInventoryReader.Localize(container.GetHoverName())); AppendLine(stringBuilder, string.Format("<color={0}><b>{1}</b></color> <color={2}><b>{3}/{4}</b></color>", "#FFD166FF", EscapeRichText(value), "#7CFFB2FF", snapshot.OccupiedSlots, snapshot.TotalSlots), maxCharacters); } else { AppendLine(stringBuilder, string.Format("<color={0}><b>{1}/{2}</b></color>", "#7CFFB2FF", snapshot.OccupiedSlots, snapshot.TotalSlots), maxCharacters); } int num2 = 0; foreach (ChestItemEntry item in snapshot.Items) { if (num2 >= num) { break; } string text = EscapeRichText(Clean(item.Name)); string text2 = ((ModConfig.ShowStackCount.Value && item.SourceStackCount > 1) ? $" ({item.SourceStackCount} stacks)" : string.Empty); string text3 = ((ModConfig.StashSenseEnabled.Value && item.MatchesPlayerInventory) ? "#FFF06AFF" : "#F2F5FFFF"); string value2 = string.Format("<color={0}><b>{1}</b></color> <color={2}><b>x{3}</b></color>{4}", text3, text, "#7CFFB2FF", Math.Max(0, item.Stack), text2); if (!AppendLine(stringBuilder, value2, maxCharacters)) { break; } num2++; } if (snapshot.Items.Count == 0 && ModConfig.ShowEmptyMessage.Value) { AppendLine(stringBuilder, "<color=#7CFFB2FF>" + EscapeRichText(Clean(ModConfig.EmptyMessage.Value)) + "</color>", maxCharacters); } return stringBuilder.ToString().TrimEnd('\r', '\n'); } private static bool AppendLine(StringBuilder text, string value, int maxCharacters) { value = (string.IsNullOrWhiteSpace(value) ? "Unknown" : value.Trim()); int num = value.Length + ((text.Length != 0) ? Environment.NewLine.Length : 0); if (text.Length + num > maxCharacters) { return false; } if (text.Length > 0) { text.AppendLine(); } text.Append(value); return true; } private static string Clean(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } return RichTextTag.Replace(value, string.Empty).Replace('\r', ' ').Replace('\n', ' ') .Trim(); } private static string EscapeRichText(string value) { return value.Replace("&", "&").Replace("<", "<").Replace(">", ">"); } } [HarmonyPatch(typeof(Hud), "UpdateCrosshair")] internal static class ChestViewerPatches { private static void Postfix(Hud __instance, Player player, float bowDrawPercentage) { try { ChestOverlay.Apply(__instance, player); } catch (Exception arg) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)$"Chest hover update failed: {arg}"); } ChestOverlay.Clear(); } } } internal static class ModConfig { internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<int> UpdateIntervalMs; internal static ConfigEntry<int> MaxLines; internal static ConfigEntry<int> MaxTextCharacters; internal static ConfigEntry<bool> ShowHeader; internal static ConfigEntry<bool> ShowStackCount; internal static ConfigEntry<bool> StashSenseEnabled; internal static ConfigEntry<bool> ShowEmptyMessage; internal static ConfigEntry<string> EmptyMessage; internal static void Bind(ConfigFile config) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown Enabled = config.Bind<bool>("General", "Enabled", true, "Enable the client-only chest contents display."); UpdateIntervalMs = config.Bind<int>("General", "UpdateIntervalMs", 100, new ConfigDescription("Minimum milliseconds between content refresh checks.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(25, 500), Array.Empty<object>())); MaxLines = config.Bind<int>("Display", "MaxLines", 30, new ConfigDescription("Maximum number of item lines to display.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); MaxTextCharacters = config.Bind<int>("Display", "MaxTextCharacters", 1200, new ConfigDescription("Maximum formatted hover-text length.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 4096), Array.Empty<object>())); ShowHeader = config.Bind<bool>("Display", "ShowHeader", true, "Show the chest name above the contents."); ShowStackCount = config.Bind<bool>("Display", "ShowStackCount", true, "Show how many source stacks contributed to aggregated item rows."); StashSenseEnabled = config.Bind<bool>("Stash Sense", "Enabled", true, "Highlight chest items that are also present in your local inventory."); ShowEmptyMessage = config.Bind<bool>("Display", "ShowEmptyMessage", true, "Show Empty when the targeted chest has no items."); EmptyMessage = config.Bind<string>("Display", "EmptyMessage", "Empty", "Text shown for an empty chest when ShowEmptyMessage is enabled."); } } [BepInPlugin("com.catosaur.catoschestviewer", "Catos Chest Viewer", "0.1.2")] [BepInProcess("valheim.exe")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "com.catosaur.catoschestviewer"; public const string Name = "Catos Chest Viewer"; public const string Version = "0.1.2"; private Harmony _harmony; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); _harmony = new Harmony("com.catosaur.catoschestviewer"); _harmony.PatchAll(typeof(ChestViewerPatches)); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Catos Chest Viewer 0.1.2 client-only loaded."); } private void LateUpdate() { ChestOverlay.Apply(Hud.instance, Player.m_localPlayer); } private void OnDestroy() { ChestOverlay.Clear(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchAll("com.catosaur.catoschestviewer"); } } } }