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 BottomlessChest v0.4.0
BottomlessChest.Logic.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("BottomlessChest.Logic")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BottomlessChest.Logic")] [assembly: AssemblyTitle("BottomlessChest.Logic")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } } namespace BottomlessChest.Logic { public readonly struct IndexEntry { public string ItemId { get; } public int Quality { get; } public int Count { get; } public int WorldLevel { get; } public bool Cheated { get; } public IndexEntry(string itemId, int quality, int count, int worldLevel, bool cheated) { ItemId = itemId; Quality = quality; Count = count; WorldLevel = worldLevel; Cheated = cheated; } } public sealed class ChestIndex { private readonly IndexEntry[] _entries; public long Generation { get; } public long Version { get; } public IReadOnlyList<IndexEntry> Entries => _entries; private ChestIndex(long generation, long version, IndexEntry[] entries) { Generation = generation; Version = version; _entries = entries; } public static ChestIndex From(long generation, long version, IEnumerable<IStorableItem> items) { List<IndexEntry> list = new List<IndexEntry>(); Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); if (items != null) { foreach (IStorableItem item in items) { if (item != null && item.ItemId != null) { string key = item.ItemId + "/" + item.Quality + "/" + item.WorldLevel + "/" + (item.Cheated ? "1" : "0"); if (dictionary.TryGetValue(key, out var value)) { IndexEntry indexEntry = list[value]; list[value] = new IndexEntry(indexEntry.ItemId, indexEntry.Quality, indexEntry.Count + item.Stack, indexEntry.WorldLevel, indexEntry.Cheated); } else { dictionary[key] = list.Count; list.Add(new IndexEntry(item.ItemId, item.Quality, item.Stack, item.WorldLevel, item.Cheated)); } } } } return new ChestIndex(generation, version, list.ToArray()); } public int CountOf(string itemId, int quality) { if (itemId == null) { return 0; } int num = 0; IndexEntry[] entries = _entries; for (int i = 0; i < entries.Length; i++) { IndexEntry indexEntry = entries[i]; if (indexEntry.ItemId == itemId && (quality < 0 || indexEntry.Quality == quality)) { num += indexEntry.Count; } } return num; } public int Count(Func<string, bool> itemIdMatches, int quality, int minWorldLevel) { if (itemIdMatches == null) { return 0; } int num = 0; IndexEntry[] entries = _entries; for (int i = 0; i < entries.Length; i++) { IndexEntry indexEntry = entries[i]; if ((quality < 0 || indexEntry.Quality == quality) && (minWorldLevel < 0 || indexEntry.WorldLevel >= minWorldLevel) && indexEntry.Count > 0 && itemIdMatches(indexEntry.ItemId)) { num += indexEntry.Count; } } return num; } public int Take(string itemId, int quality, int amount, int minWorldLevel = -1) { if (itemId == null || amount <= 0) { return 0; } int num = 0; for (int i = 0; i < _entries.Length; i++) { if (num >= amount) { break; } IndexEntry indexEntry = _entries[i]; if (!(indexEntry.ItemId != itemId) && (quality < 0 || indexEntry.Quality == quality) && (minWorldLevel < 0 || indexEntry.WorldLevel >= minWorldLevel)) { int num2 = ((indexEntry.Count < amount - num) ? indexEntry.Count : (amount - num)); _entries[i] = new IndexEntry(indexEntry.ItemId, indexEntry.Quality, indexEntry.Count - num2, indexEntry.WorldLevel, indexEntry.Cheated); num += num2; } } return num; } } public sealed class ChestIndexCache { private readonly Dictionary<string, ChestIndex> _byStore = new Dictionary<string, ChestIndex>(StringComparer.Ordinal); public void Put(string storeId, ChestIndex index) { if (!string.IsNullOrEmpty(storeId) && index != null && (!_byStore.TryGetValue(storeId, out var value) || !IsNewer(value, index))) { _byStore[storeId] = index; } } public bool TryGet(string storeId, out ChestIndex index) { if (string.IsNullOrEmpty(storeId)) { index = null; return false; } return _byStore.TryGetValue(storeId, out index); } public void Forget(string storeId) { if (!string.IsNullOrEmpty(storeId)) { _byStore.Remove(storeId); } } public void Clear() { _byStore.Clear(); } private static bool IsNewer(ChestIndex held, ChestIndex incoming) { if (held.Generation <= incoming.Generation) { if (held.Generation == incoming.Generation) { return held.Version > incoming.Version; } return false; } return true; } } public static class ConfigDefaults { public static string Upgrade(string stored, string previousDefault, string currentDefault) { if (!string.Equals(stored, previousDefault, StringComparison.Ordinal)) { return stored; } return currentDefault; } } public static class ContentsDiff { public static Dictionary<string, int> Tally(IEnumerable<IStorableItem> items) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); if (items == null) { return dictionary; } foreach (IStorableItem item in items) { if (item != null) { Add(dictionary, item.ItemId, item.Stack); } } return dictionary; } public static void Add(Dictionary<string, int> tally, string itemId, int stack) { if (tally != null && itemId != null) { tally.TryGetValue(itemId, out var value); tally[itemId] = value + stack; } } public static string Describe(IReadOnlyDictionary<string, int> before, IReadOnlyDictionary<string, int> after) { if (before == null || after == null) { return null; } SortedSet<string> sortedSet = new SortedSet<string>(); foreach (KeyValuePair<string, int> item in before) { sortedSet.Add(item.Key); } foreach (KeyValuePair<string, int> item2 in after) { sortedSet.Add(item2.Key); } StringBuilder stringBuilder = new StringBuilder(); foreach (string item3 in sortedSet) { before.TryGetValue(item3, out var value); after.TryGetValue(item3, out var value2); int num = value2 - value; if (num != 0) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(item3).Append(' ').Append((num > 0) ? "+" : "-") .Append((num > 0) ? num : (-num)); } } if (stringBuilder.Length <= 0) { return null; } return stringBuilder.ToString(); } } public readonly struct GridPos : IEquatable<GridPos> { public int X { get; } public int Y { get; } public GridPos(int x, int y) { X = x; Y = y; } public bool Equals(GridPos other) { if (X == other.X) { return Y == other.Y; } return false; } public override bool Equals(object obj) { if (obj is GridPos other) { return Equals(other); } return false; } public override int GetHashCode() { return (X * 397) ^ Y; } public override string ToString() { return $"({X}, {Y})"; } } public static class GridPacker { public static GridPos PositionOf(int index, int width) { RequirePositiveWidth(width); return new GridPos(index % width, index / width); } public static int RowsNeeded(int count, int width) { RequirePositiveWidth(width); if (count > 0) { return (count - 1) / width + 1; } return 0; } public static int LastPageRow(int count, int width, int pageSlots) { RequirePositiveWidth(width); if (count > pageSlots) { return RowsNeeded(count - pageSlots, width); } return 0; } public static int RowsForChest(int count, int width, int minRows) { RequirePositiveWidth(width); int num = RowsNeeded(count, width) + 1; int num2 = ((minRows < 1) ? 1 : minRows); if (num <= num2) { return num2; } return num; } public static bool LayoutFitsWindow(IReadOnlyList<GridPos> positions, int width, int rows) { RequirePositiveWidth(width); if (rows < 1 || positions == null) { return false; } if (positions.Count > width * rows) { return false; } HashSet<int> hashSet = new HashSet<int>(); foreach (GridPos position in positions) { if (position.X < 0 || position.X >= width || position.Y < 0 || position.Y >= rows) { return false; } if (!hashSet.Add(position.Y * width + position.X)) { return false; } } return true; } public static GridPos FirstEmptySlot(IEnumerable<GridPos> occupied, int width, int height, bool topFirst) { RequirePositiveWidth(width); if (height <= 0) { return new GridPos(-1, -1); } bool[] array = new bool[(long)width * (long)height]; if (occupied != null) { foreach (GridPos item in occupied) { if (item.X >= 0 && item.X < width && item.Y >= 0 && item.Y < height) { array[(long)item.Y * (long)width + item.X] = true; } } } for (int i = 0; i < height; i++) { int num = (topFirst ? i : (height - 1 - i)); for (int j = 0; j < width; j++) { if (!array[(long)num * (long)width + j]) { return new GridPos(j, num); } } } return new GridPos(-1, -1); } private static void RequirePositiveWidth(int width) { if (width <= 0) { throw new ArgumentOutOfRangeException("width", width, "Grid width must be at least 1."); } } } public sealed class IndexedItem : IStorableItem { public string ItemId { get; } public int Quality { get; } public int Stack { get; } public int WorldLevel { get; } public bool Cheated { get; } public string DisplayName => ItemId; public string SearchKey => TextKey.Of(ItemId); public ItemKind Kind => ItemKind.Unknown; public int MaxStackSize => int.MaxValue; public int Variant => 0; public string CustomData => null; public IndexedItem(string itemId, int quality, int stack, int worldLevel = 0, bool cheated = false) { ItemId = itemId; Quality = quality; Stack = stack; WorldLevel = worldLevel; Cheated = cheated; } } public enum PayloadFormat { Unknown, VanillaIntCount, VanillaUShortCount, Bottomless } public readonly struct PayloadHeader { public PayloadFormat Format { get; } public int ItemVersion { get; } public int Count { get; } public int ItemsOffset { get; } public PayloadHeader(PayloadFormat format, int itemVersion, int count, int itemsOffset) { Format = format; ItemVersion = itemVersion; Count = count; ItemsOffset = itemsOffset; } } public static class InventoryPayload { public const int Marker = 1112294217; public const int FirstUShortCountVersion = 108; private const int LowestKnownItemVersion = 100; private const int HighestPlausibleItemVersion = 999; public static bool TryReadHeader(byte[] payload, out PayloadHeader header) { header = default(PayloadHeader); if (payload == null || payload.Length < 6) { return false; } int num = ReadInt(payload, 0); if (num == 1112294217) { if (payload.Length < 12) { return false; } int version = ReadInt(payload, 4); int count = ReadInt(payload, 8); return Accept(PayloadFormat.Bottomless, version, count, 12, out header); } if (num < 100 || num > 999) { return false; } if (num >= 108) { return Accept(PayloadFormat.VanillaUShortCount, num, ReadUShort(payload, 4), 6, out header); } if (payload.Length < 8) { return false; } return Accept(PayloadFormat.VanillaIntCount, num, ReadInt(payload, 4), 8, out header); } public static byte[] Wrap(byte[] vanillaPayload, int trueCount) { if (vanillaPayload == null) { throw new ArgumentNullException("vanillaPayload"); } if (trueCount < 0) { throw new ArgumentOutOfRangeException("trueCount", trueCount, "A stack count cannot be negative."); } if (!TryReadHeader(vanillaPayload, out var header)) { throw new ArgumentException("Not a serialized inventory: its leading value is neither an item version nor our marker.", "vanillaPayload"); } int num = vanillaPayload.Length - header.ItemsOffset; byte[] array = new byte[12 + num]; WriteInt(array, 0, 1112294217); WriteInt(array, 4, header.ItemVersion); WriteInt(array, 8, trueCount); Buffer.BlockCopy(vanillaPayload, header.ItemsOffset, array, 12, num); return array; } public static bool TryUnwrapToVanilla(byte[] wrapped, out byte[] vanilla) { vanilla = null; if (!TryReadHeader(wrapped, out var header) || header.Format != PayloadFormat.Bottomless) { return false; } bool flag = header.ItemVersion >= 108; if (flag && header.Count > 65535) { return false; } int num = wrapped.Length - header.ItemsOffset; int num2 = (flag ? 2 : 4); vanilla = new byte[4 + num2 + num]; WriteInt(vanilla, 0, header.ItemVersion); if (flag) { vanilla[4] = (byte)header.Count; vanilla[5] = (byte)(header.Count >> 8); } else { WriteInt(vanilla, 4, header.Count); } Buffer.BlockCopy(wrapped, header.ItemsOffset, vanilla, 4 + num2, num); return true; } private static bool Accept(PayloadFormat format, int version, int count, int offset, out PayloadHeader header) { if (count < 0) { header = default(PayloadHeader); return false; } header = new PayloadHeader(format, version, count, offset); return true; } private static int ReadInt(byte[] buffer, int offset) { return buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24); } private static int ReadUShort(byte[] buffer, int offset) { return buffer[offset] | (buffer[offset + 1] << 8); } private static void WriteInt(byte[] buffer, int offset, int value) { buffer[offset] = (byte)value; buffer[offset + 1] = (byte)(value >> 8); buffer[offset + 2] = (byte)(value >> 16); buffer[offset + 3] = (byte)(value >> 24); } } public interface IStorableItem { string ItemId { get; } string DisplayName { get; } string SearchKey { get; } ItemKind Kind { get; } int Stack { get; } int MaxStackSize { get; } int Quality { get; } int WorldLevel { get; } bool Cheated { get; } int Variant { get; } string CustomData { get; } } public enum ItemKind { Unknown, Material, Food, Weapon, Armor, Ammo, Tool, Trophy, Misc } public sealed class ItemOrdering : IComparer<IStorableItem> { public static ItemOrdering ByName { get; } = new ItemOrdering(); public int Compare(IStorableItem a, IStorableItem b) { if (a == b) { return 0; } if (a == null) { return -1; } if (b == null) { return 1; } int num = string.CompareOrdinal(a.SearchKey ?? string.Empty, b.SearchKey ?? string.Empty); if (num != 0) { return num; } int num2 = b.Quality.CompareTo(a.Quality); if (num2 != 0) { return num2; } return b.Stack.CompareTo(a.Stack); } } public sealed class ItemQuery { private static readonly Dictionary<string, ItemKind> KindTokens = new Dictionary<string, ItemKind>(StringComparer.Ordinal) { { "@material", ItemKind.Material }, { "@food", ItemKind.Food }, { "@weapon", ItemKind.Weapon }, { "@armor", ItemKind.Armor }, { "@ammo", ItemKind.Ammo }, { "@tool", ItemKind.Tool }, { "@trophy", ItemKind.Trophy }, { "@misc", ItemKind.Misc } }; private readonly List<string> _textTerms; private readonly List<ItemKind> _kindTerms; public bool IsEmpty { get { if (_textTerms.Count == 0) { return _kindTerms.Count == 0; } return false; } } private ItemQuery(List<string> textTerms, List<ItemKind> kindTerms) { _textTerms = textTerms; _kindTerms = kindTerms; } public static ItemQuery Parse(string query) { List<string> list = new List<string>(); List<ItemKind> list2 = new List<ItemKind>(); if (!string.IsNullOrWhiteSpace(query)) { string[] array = query.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = TextKey.Of(array[i]); if (text.Length != 0) { if (KindTokens.TryGetValue(text, out var value)) { list2.Add(value); } else { list.Add(text); } } } } return new ItemQuery(list, list2); } public bool Matches(IStorableItem item) { if (IsEmpty) { return true; } if (_kindTerms.Count > 0 && !_kindTerms.Contains(item.Kind)) { return false; } if (_textTerms.Count == 0) { return true; } string text = item.SearchKey ?? string.Empty; foreach (string textTerm in _textTerms) { if (text.IndexOf(textTerm, StringComparison.Ordinal) < 0) { return false; } } return true; } } public sealed class OfferQueue<TItem> where TItem : class { private sealed class Waiting { internal string StoreId; internal bool Message; } private sealed class InFlight { internal IReadOnlyList<TItem> Items; internal bool Message; internal float SentAt; } private sealed class ByReference : IEqualityComparer<TItem> { public bool Equals(TItem x, TItem y) { return x == y; } public int GetHashCode(TItem obj) { return RuntimeHelpers.GetHashCode(obj); } } private readonly float _timeout; private readonly List<Waiting> _waiting = new List<Waiting>(); private readonly Dictionary<string, InFlight> _inFlight = new Dictionary<string, InFlight>(StringComparer.Ordinal); private readonly Dictionary<TItem, int> _reserved = new Dictionary<TItem, int>(new ByReference()); public OfferQueue(float timeoutSeconds) { _timeout = timeoutSeconds; } public void Enqueue(string storeId, bool message) { if (string.IsNullOrEmpty(storeId)) { return; } foreach (Waiting item in _waiting) { if (item.StoreId == storeId) { item.Message |= message; return; } } _waiting.Add(new Waiting { StoreId = storeId, Message = message }); } public bool TryNext(float now, out string storeId, out bool message) { storeId = null; message = false; foreach (InFlight value in _inFlight.Values) { if (now - value.SentAt < _timeout) { return false; } } for (int i = 0; i < _waiting.Count; i++) { if (!_inFlight.ContainsKey(_waiting[i].StoreId)) { storeId = _waiting[i].StoreId; message = _waiting[i].Message; _waiting.RemoveAt(i); return true; } } return false; } public void Sent(string storeId, IReadOnlyList<TItem> items, bool message, float now) { IReadOnlyList<TItem> readOnlyList = items ?? Array.Empty<TItem>(); _inFlight[storeId] = new InFlight { Items = readOnlyList, Message = message, SentAt = now }; foreach (TItem item in readOnlyList) { Reserve(item); } } public bool Complete(string storeId, out IReadOnlyList<TItem> items, out bool message) { items = Array.Empty<TItem>(); message = false; if (storeId == null || !_inFlight.TryGetValue(storeId, out var value)) { return false; } _inFlight.Remove(storeId); foreach (TItem item in value.Items) { Release(item); } items = value.Items; message = value.Message; return true; } public int OverdueCount(float now) { int num = 0; foreach (InFlight value in _inFlight.Values) { if (now - value.SentAt >= _timeout) { num++; } } return num; } public bool IsReserved(TItem item) { if (item != null) { return _reserved.ContainsKey(item); } return false; } public void Reserve(TItem item) { if (item != null) { _reserved[item] = ((!_reserved.TryGetValue(item, out var value)) ? 1 : (value + 1)); } } public void Release(TItem item) { if (item != null && _reserved.TryGetValue(item, out var value)) { if (value <= 1) { _reserved.Remove(item); } else { _reserved[item] = value - 1; } } } public void Clear() { _waiting.Clear(); _inFlight.Clear(); _reserved.Clear(); } } public sealed class ReapplyTrigger { private const int Unset = -1; private int _appliedAt = -1; public bool NeedsApply(int count) { return _appliedAt != count; } public void NoteApplied(int count) { _appliedAt = count; } public void Invalidate() { _appliedAt = -1; } } public static class ReferenceFingerprint { public static ulong Of<T>(List<T> items) where T : class { ulong num = (ulong)(-3750763034362895579L ^ (items?.Count ?? 0)); if (items == null) { return num; } for (int i = 0; i < items.Count; i++) { num ^= (uint)RuntimeHelpers.GetHashCode(items[i]); num *= 1099511628211L; } return num; } } public static class StackIdentity { public static string Key(string itemId, string sharedName, int quality, int variant, int worldLevel) { itemId = itemId ?? string.Empty; sharedName = sharedName ?? string.Empty; return $"{itemId.Length}:{itemId}|{sharedName.Length}:{sharedName}|{quality}|{variant}|{worldLevel}"; } } public static class StackRules { public static int MergeAmount(int incoming, int targetStack, int maxStackSize) { if (incoming <= 0 || maxStackSize <= 1) { return 0; } int num = maxStackSize - targetStack; if (num <= 0) { return 0; } if (incoming >= num) { return num; } return incoming; } public static int TakeAmount(int requested, int available) { if (available <= 0) { return 0; } if (requested > 0 && requested < available) { return requested; } return available; } } public enum WorldLayout { Flat, Chunked } public readonly struct StoreCandidate { public string Path { get; } public bool FromLocalFallback { get; } public StoreCandidate(string path, bool fromLocalFallback) { Path = path; FromLocalFallback = fromLocalFallback; } public override string ToString() { return Path; } } public static class StoreLocations { public const string Extension = ".bottomless.dat"; private static readonly string[] Generations = new string[3] { "", ".old", ".old2" }; public static string WritePath(string worldsRoot, string worldName) { return PathFor(worldsRoot, worldName, WorldLayout.Flat); } private static string PathFor(string root, string worldName, WorldLayout layout) { if (layout != WorldLayout.Chunked) { return Combine(root, worldName + ".bottomless.dat"); } return Combine(Combine(root, worldName), worldName + ".bottomless.dat"); } public static IReadOnlyList<StoreCandidate> ReadCandidates(string worldsRoot, string localWorldsRoot, string worldName) { List<StoreCandidate> list = new List<StoreCandidate>(); if (string.IsNullOrEmpty(worldName)) { return list; } HashSet<string> hashSet = new HashSet<string>(); (string, bool)[] array = new(string, bool)[2] { (worldsRoot, false), (localWorldsRoot, true) }; for (int i = 0; i < array.Length; i++) { var (text, fromLocalFallback) = array[i]; if (string.IsNullOrEmpty(text)) { continue; } WorldLayout[] array2 = new WorldLayout[2] { WorldLayout.Chunked, WorldLayout.Flat }; foreach (WorldLayout layout in array2) { string text2 = PathFor(text, worldName, layout); string[] generations = Generations; foreach (string text3 in generations) { string text4 = text2 + text3; if (hashSet.Add(text4)) { list.Add(new StoreCandidate(text4, fromLocalFallback)); } } } } return list; } private static string Combine(string left, string right) { if (string.IsNullOrEmpty(left)) { return right; } return left.TrimEnd('/', '\\') + "/" + right; } } public static class TextKey { public static string Of(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } string text = value.Normalize(NormalizationForm.FormD); StringBuilder stringBuilder = new StringBuilder(text.Length); string text2 = text; foreach (char c in text2) { if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark) { stringBuilder.Append(c); } } return stringBuilder.ToString().Normalize(NormalizationForm.FormC).ToLowerInvariant(); } } }
BottomlessChest.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using 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.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BottomlessChest.Commands; using BottomlessChest.Compat; using BottomlessChest.Core; using BottomlessChest.Filter; using BottomlessChest.Logic; using BottomlessChest.Net; using BottomlessChest.Piece; using BottomlessChest.Settings; using BottomlessChest.Storage; using GUIFramework; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Splatform; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("BottomlessChest")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.4.0.0")] [assembly: AssemblyInformationalVersion("0.4.0")] [assembly: AssemblyProduct("BottomlessChest")] [assembly: AssemblyTitle("BottomlessChest")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.4.0.0")] [module: UnverifiableCode] namespace BottomlessChest { [BepInPlugin("com.myrridin.bottomlesschest", "BottomlessChest", "0.4.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.myrridin.bottomlesschest"; public const string PluginName = "BottomlessChest"; public const string PluginVersion = "0.4.0"; private Harmony _harmony; internal static ManualLogSource Log { get; private set; } internal static bool Degraded { get; private set; } private void Awake() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; try { ModConfig.Bind(((BaseUnityPlugin)this).Config); } catch (Exception arg) { Log.LogError((object)$"Could not read configuration, falling back to defaults: {arg}"); } try { BottomlessChestPiece.Register(); } catch (Exception arg2) { Log.LogError((object)("CRITICAL: the chest prefab could not be registered. " + $"Existing chests will be removed by the game. {arg2}")); } try { _harmony = new Harmony("com.myrridin.bottomlesschest"); _harmony.PatchAll(typeof(Plugin).Assembly); } catch (Exception arg3) { Degraded = true; Log.LogError((object)("Patching failed - running in degraded mode. Chests will behave " + $"as ordinary containers; their stored contents are untouched. {arg3}")); try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception arg4) { Log.LogError((object)$"Could not roll back partial patches: {arg4}"); } } if (!Degraded) { try { ValheimPlusBridge.Attach(_harmony); } catch (Exception ex) { Log.LogWarning((object)("ValheimPlus integration could not start: " + ex.Message)); } } try { ChestRpc.Register(); } catch (Exception arg5) { Log.LogError((object)$"Could not register networking; multiplayer chests will not sync: {arg5}"); } try { CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new BottomlessCommand()); } catch (Exception arg6) { Log.LogError((object)$"Could not register console commands: {arg6}"); } try { WorldSaveHooks.Install(SidecarStore.Instance); } catch (Exception arg7) { Log.LogError((object)$"Could not hook world saving: {arg7}"); } Log.LogInfo((object)("BottomlessChest 0.4.0 loaded" + (Degraded ? " (DEGRADED)" : string.Empty) + ".")); } private void OnDestroy() { ItemAdapter.ClearCache(); ChestSessions.PersistAll(); ChestSessions.Clear(); WorldSaveHooks.Uninstall(); SidecarStore.Instance.Flush(); SidecarStore.Instance.Unload(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } namespace BottomlessChest.Storage { internal interface IChestStore { int Count { get; } bool IsReady { get; } bool TryGet(string storeId, out byte[] contents); void Put(string storeId, byte[] contents); void Flush(); void Unload(); } internal static class InventorySerializer { private const int LegacyVersion = 106; internal static byte[] Save(Inventory inventory, string label) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown if (inventory == null) { return null; } ZPackage val = new ZPackage(); inventory.Save(val); byte[] array = val.GetArray(); int count = inventory.m_inventory.Count; PayloadHeader val2 = default(PayloadHeader); if (!InventoryPayload.TryReadHeader(array, ref val2)) { Plugin.Log.LogError((object)($"Refusing to save chest {label}: the game produced {array.Length} bytes " + "this build cannot read back. Its save format has probably changed.")); return null; } byte[] array2 = InventoryPayload.Wrap(array, count); PayloadHeader val3 = default(PayloadHeader); if (!InventoryPayload.TryReadHeader(array2, ref val3) || ((PayloadHeader)(ref val3)).Count != count) { Plugin.Log.LogError((object)($"Refusing to save chest {label}: it holds {count} stacks but the " + "payload does not read back as holding that many. The chest's stored contents are untouched.")); return null; } return array2; } internal static bool Load(Inventory inventory, byte[] contents, out int expected) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Invalid comparison between Unknown and I4 //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Invalid comparison between Unknown and I4 //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) expected = 0; if (contents == null || contents.Length == 0) { return true; } PayloadHeader header = default(PayloadHeader); if (!InventoryPayload.TryReadHeader(contents, ref header)) { if (!LoadWithGame(inventory, contents)) { return false; } Plugin.Log.LogError((object)("Chest store has a header this build does not recognise. The game read " + $"{inventory.m_inventory.Count} stack(s) from it, but there is no way to " + "know whether that is all of them, so this chest will not be saved.")); return false; } expected = ((PayloadHeader)(ref header)).Count; if ((int)((PayloadHeader)(ref header)).Format == 3) { return LoadOurs(inventory, contents, header); } if ((int)((PayloadHeader)(ref header)).Format == 1 && ((PayloadHeader)(ref header)).ItemVersion == 106 && TryLoadLegacy(inventory, contents, header)) { return true; } return LoadWithGame(inventory, contents); } private static bool LoadOurs(Inventory inventory, byte[] contents, PayloadHeader header) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00ca: Unknown result type (might be due to invalid IL or missing references) if (((PayloadHeader)(ref header)).ItemVersion < 108) { byte[] contents2 = default(byte[]); if (InventoryPayload.TryUnwrapToVanilla(contents, ref contents2)) { return LoadWithGame(inventory, contents2); } Plugin.Log.LogError((object)($"Chest store is in this mod's format at item version {((PayloadHeader)(ref header)).ItemVersion}, " + "which cannot be expressed for the game's loader. Leaving the chest empty; it will refuse to save, so nothing stored is overwritten.")); inventory.m_inventory.Clear(); return false; } if ((Object)(object)ObjectDB.instance == (Object)null) { Plugin.Log.LogError((object)"Cannot read a chest store before the item database exists. Leaving the chest empty; it will refuse to save."); inventory.m_inventory.Clear(); return false; } List<ItemData> list = new List<ItemData>(Math.Min(((PayloadHeader)(ref header)).Count, 4096)); try { ZPackage val = new ZPackage(contents); val.ReadInt(); val.ReadInt(); val.ReadInt(); Item val2 = (Item)((PayloadHeader)(ref header)).ItemVersion; for (int i = 0; i < ((PayloadHeader)(ref header)).Count; i++) { ItemData val3 = new ItemData(); int num = ItemData.Load(val, val3, val2); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(num); ItemDrop val4 = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val4 == (Object)null || val4.m_itemData?.m_shared == null) { Plugin.Log.LogWarning((object)$"Skipping unknown item prefab hash {num}."); continue; } ItemData val5 = ItemTemplates.For(((Object)itemPrefab).name) ?? val4.m_itemData; val3.m_shared = val5.m_shared; val3.m_dropPrefab = itemPrefab; list.Add(val3); } } catch (Exception ex) { Plugin.Log.LogError((object)($"Chest store could not be read after {list.Count} of {((PayloadHeader)(ref header)).Count} " + "stacks: " + ex.Message + ". Leaving the chest empty; it will refuse to save, so the stored contents are untouched.")); inventory.m_inventory.Clear(); return false; } Commit(inventory, list); return true; } private static bool TryLoadLegacy(Inventory inventory, byte[] contents, PayloadHeader header) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ObjectDB.instance == (Object)null) { return false; } List<ItemData> list; try { ZPackage val = new ZPackage(contents); val.ReadInt(); val.ReadInt(); list = new List<ItemData>(Math.Min(((PayloadHeader)(ref header)).Count, 4096)); for (int i = 0; i < ((PayloadHeader)(ref header)).Count; i++) { string text = val.ReadString(); int stack = val.ReadInt(); float durability = val.ReadSingle(); Vector2i gridPos = val.ReadVector2i(); bool equipped = val.ReadBool(); int quality = val.ReadInt(); int variant = val.ReadInt(); long crafterID = val.ReadLong(); string crafterName = val.ReadString(); int num = val.ReadInt(); Dictionary<string, string> dictionary = null; for (int j = 0; j < num; j++) { dictionary = dictionary ?? new Dictionary<string, string>(num); dictionary[val.ReadString()] = val.ReadString(); } int worldLevel = val.ReadInt(); bool pickedUp = val.ReadBool(); if (string.IsNullOrEmpty(text)) { continue; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text); ItemDrop val2 = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val2 == (Object)null || val2.m_itemData?.m_shared == null) { Plugin.Log.LogWarning((object)("Skipping unknown item prefab '" + text + "'.")); continue; } ItemData val3 = (ItemTemplates.For(text) ?? val2.m_itemData).Clone(); val3.m_dropPrefab = itemPrefab; val3.m_stack = stack; val3.m_durability = durability; val3.m_gridPos = gridPos; val3.m_equipped = equipped; val3.m_quality = quality; val3.m_variant = variant; val3.m_crafterID = crafterID; val3.m_crafterName = crafterName; val3.m_worldLevel = worldLevel; val3.m_pickedUp = pickedUp; if (dictionary != null) { val3.m_customData = dictionary; } list.Add(val3); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Fast inventory read failed, falling back to the game's loader: " + ex.Message)); return false; } Commit(inventory, list); return true; } private static bool LoadWithGame(Inventory inventory, byte[] contents) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown try { inventory.Load(new ZPackage(contents)); return true; } catch (Exception ex) { Plugin.Log.LogError((object)("The game could not read this chest store either: " + ex.Message + ". Leaving the chest empty; it will refuse to save.")); inventory.m_inventory.Clear(); return false; } } private static void Commit(Inventory inventory, List<ItemData> loaded) { inventory.m_inventory.Clear(); inventory.m_inventory.AddRange(loaded); } } internal static class ItemTemplates { [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class AwakePatch { private static void Postfix() { Clear(); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static class CopyPatch { private static void Postfix() { Clear(); } } private static readonly Dictionary<string, ItemData> Cache = new Dictionary<string, ItemData>(); internal static ItemData For(string name) { if (Cache.TryGetValue(name, out var value)) { return value; } ObjectDB instance = ObjectDB.instance; GameObject val = (((Object)(object)instance != (Object)null) ? instance.GetItemPrefab(name) : null); if ((Object)(object)val == (Object)null) { return null; } ItemData val2 = Build(val); if (val2 != null) { Cache[name] = val2; } return val2; } private static ItemData Build(GameObject prefab) { GameObject val = null; bool forceDisableInit = ZNetView.m_forceDisableInit; ZNetView.m_forceDisableInit = true; try { val = Object.Instantiate<GameObject>(prefab); ItemDrop component = val.GetComponent<ItemDrop>(); return ((Object)(object)component != (Object)null) ? component.m_itemData : null; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not build an item template from '" + ((Object)prefab).name + "', falling back to its prefab data: " + ex.Message)); return null; } finally { ZNetView.m_forceDisableInit = forceDisableInit; if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } } internal static void Clear() { Cache.Clear(); } } internal sealed class SidecarStore : IChestStore { private sealed class Entry { internal byte[] Contents; internal long LastWrittenUtcTicks; } private const int FormatVersion = 1; private const uint Magic = 1112294193u; private const string Extension = ".bottomless.dat"; private const CloudStorageFileGrouping SaveGrouping = (CloudStorageFileGrouping)1; private readonly Dictionary<string, Entry> _entries = new Dictionary<string, Entry>(StringComparer.Ordinal); private string _loadedWorld; private bool _dirty; private bool _warnedNotAuthority; private bool _warnedAboutCloudFallback; internal static SidecarStore Instance { get; } = new SidecarStore(); public int Count => _entries.Count; internal static bool IsServerAuthority { get { ZNet instance = ZNet.instance; if (!((Object)(object)instance == (Object)null)) { return instance.IsServer(); } return true; } } public bool IsReady { get { if (!IsServerAuthority) { return false; } EnsureLoaded(); World world = ZNet.m_world; if (world != null) { return _loadedWorld == world.m_worldName; } return false; } } internal IEnumerable<(string StoreId, int StackCount)> Describe() { EnsureLoaded(); PayloadHeader val = default(PayloadHeader); foreach (KeyValuePair<string, Entry> entry in _entries) { int item = (InventoryPayload.TryReadHeader(entry.Value.Contents, ref val) ? ((PayloadHeader)(ref val)).Count : (-1)); yield return (StoreId: entry.Key, StackCount: item); } } public bool TryGet(string storeId, out byte[] contents) { EnsureLoaded(); if (_entries.TryGetValue(storeId, out var value)) { contents = value.Contents; return true; } contents = null; return false; } public void Put(string storeId, byte[] contents) { EnsureLoaded(); _entries[storeId] = new Entry { Contents = contents, LastWrittenUtcTicks = DateTime.UtcNow.Ticks }; _dirty = true; } public void Unload() { _entries.Clear(); _loadedWorld = null; _dirty = false; } private void EnsureLoaded() { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if (!IsServerAuthority) { if (!_warnedNotAuthority) { _warnedNotAuthority = true; Plugin.Log.LogWarning((object)"Connected to a remote server. Chest contents live on the server and client-side sync is not implemented yet, so chests will read as empty here. Nothing is written locally and nothing stored is at risk."); } return; } World world = ZNet.m_world; if (world == null || _loadedWorld == world.m_worldName) { return; } _entries.Clear(); _dirty = false; _loadedWorld = world.m_worldName; IReadOnlyList<StoreCandidate> candidates = StoreLocations.ReadCandidates(WorldsFolder(world.m_fileSource), WorldsFolder((FileSource)2), world.m_worldName); string text = SavePath(world, world.m_fileSource); foreach (StoreCandidate item in ByMostRecent(candidates, world)) { StoreCandidate current = item; FileSource source = (FileSource)(((StoreCandidate)(ref current)).FromLocalFallback ? 2 : ((int)world.m_fileSource)); if (TryRead(((StoreCandidate)(ref current)).Path, source)) { if (((StoreCandidate)(ref current)).Path != text) { Plugin.Log.LogInfo((object)("Read chest store from '" + ((StoreCandidate)(ref current)).Path + "', which is not where this build writes. It will be saved to '" + text + "' instead, and the file it was read from is left in place as a backup.")); } return; } } Plugin.Log.LogInfo((object)("No existing chest store for world '" + world.m_worldName + "'. Starting empty.")); } private static List<StoreCandidate> ByMostRecent(IReadOnlyList<StoreCandidate> candidates, World world) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) List<StoreCandidate> list = new List<StoreCandidate>(candidates); Dictionary<string, DateTime> writtenAt = new Dictionary<string, DateTime>(StringComparer.Ordinal); Dictionary<string, int> position = new Dictionary<string, int>(StringComparer.Ordinal); for (int i = 0; i < list.Count; i++) { StoreCandidate val = list[i]; FileSource val2 = (FileSource)(((StoreCandidate)(ref val)).FromLocalFallback ? 2 : ((int)world.m_fileSource)); position[((StoreCandidate)(ref val)).Path] = i; try { writtenAt[((StoreCandidate)(ref val)).Path] = (FileHelpers.Exists(((StoreCandidate)(ref val)).Path, val2) ? FileHelpers.GetLastWriteTime(((StoreCandidate)(ref val)).Path, val2) : DateTime.MinValue); } catch { writtenAt[((StoreCandidate)(ref val)).Path] = DateTime.MinValue; } } list.Sort(delegate(StoreCandidate left, StoreCandidate right) { int num = writtenAt[((StoreCandidate)(ref right)).Path].CompareTo(writtenAt[((StoreCandidate)(ref left)).Path]); return (num == 0) ? position[((StoreCandidate)(ref left)).Path].CompareTo(position[((StoreCandidate)(ref right)).Path]) : num; }); return list; } private static string WorldsFolder(FileSource source) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return SaveSystem.GetWorldsSaveRootPath(source); } private static string SavePath(World world, FileSource source) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return StoreLocations.WritePath(WorldsFolder(source), world.m_worldName); } private FileSource? ChooseSource(World world) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) FileSource fileSource = world.m_fileSource; if ((int)fileSource != 4 || !FileHelpers.CloudStorageSupportedAndEnabled) { return fileSource; } ulong num = 0uL; foreach (Entry value in _entries.Values) { num += (ulong)value.Contents.Length; } num *= 2; long remainingCloudCapacity = FileHelpers.GetRemainingCloudCapacity(); Plugin.Log.LogDebug((object)$"Cloud store needs {num / 1024}KB; {remainingCloudCapacity / 1024}KB remaining."); if (!FileHelpers.OperationExceedsCloudCapacity(num)) { return fileSource; } if (!FileHelpers.LocalStorageFallbackSupported) { Plugin.Log.LogError((object)($"Chest contents need {num / 1024}KB but only {remainingCloudCapacity / 1024}KB of cloud " + "storage remains, and local storage is unavailable. Not saving, to avoid exhausting the quota your world saves also use.")); return null; } if (!_warnedAboutCloudFallback) { _warnedAboutCloudFallback = true; Plugin.Log.LogWarning((object)($"Chest contents ({num / 1024}KB) would exceed the remaining cloud quota " + $"({remainingCloudCapacity / 1024}KB), which your world saves share. Writing them to local " + "storage instead. They will not follow this world to another machine.")); } return (FileSource)2; } private bool TryRead(string path, FileSource source) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown if (!FileHelpers.Exists(path, source)) { return false; } FileReader val = null; try { val = new FileReader(path, source, (FileHelperType)0); BinaryReader binary = val.m_binary; if (binary.ReadUInt32() != 1112294193) { throw new InvalidDataException("bad magic - not a bottomless chest store"); } int num = binary.ReadInt32(); if (num > 1) { throw new InvalidDataException($"file is format v{num}, this build understands up to v{1}"); } int num2 = binary.ReadInt32(); for (int i = 0; i < num2; i++) { string text = binary.ReadString(); long lastWrittenUtcTicks = binary.ReadInt64(); int num3 = binary.ReadInt32(); byte[] array = binary.ReadBytes(num3); if (array.Length != num3) { throw new EndOfStreamException("entry '" + text + "' is truncated"); } _entries[text] = new Entry { Contents = array, LastWrittenUtcTicks = lastWrittenUtcTicks }; } Plugin.Log.LogInfo((object)$"Loaded {_entries.Count} chest store(s) from {Path.GetFileName(path)}."); return true; } catch (Exception ex) { Plugin.Log.LogError((object)("Could not read chest store '" + path + "': " + ex.Message)); _entries.Clear(); return false; } finally { if (val != null) { val.Dispose(); } } } public void Flush() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Invalid comparison between Unknown and I4 //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) if (!_dirty) { return; } World world = ZNet.m_world; if (world == null || _loadedWorld != world.m_worldName) { return; } Stopwatch stopwatch = Stopwatch.StartNew(); FileSource? val = ChooseSource(world); if (!val.HasValue) { return; } FileSource value = val.Value; string text = SavePath(world, value); string text2 = text + ".new"; string text3 = text + ".old"; FileWriter val2 = null; try { if ((int)value != 4) { FileHelpers.EnsureDirectoryExists(text); } val2 = new FileWriter(text2, (CloudStorageFileGrouping)1, (FileHelperType)0, value); if ((int)val2.Status != 0 || val2.m_binary == null) { Plugin.Log.LogError((object)($"Could not open '{text2}' for writing ({val2.Status}). The chest " + "store was not saved; the previous file is untouched and the write will be retried.")); return; } BinaryWriter binary = val2.m_binary; binary.Write(1112294193u); binary.Write(1); binary.Write(_entries.Count); foreach (KeyValuePair<string, Entry> entry in _entries) { binary.Write(entry.Key); binary.Write(entry.Value.LastWrittenUtcTicks); binary.Write(entry.Value.Contents.Length); binary.Write(entry.Value.Contents); } val2.Finish(); if ((int)val2.Status != 2) { Plugin.Log.LogError((object)($"Chest store write to '{text2}' did not complete ({val2.Status}). " + "The previous file is untouched and the write will be retried.")); val2 = null; return; } val2 = null; if (FileHelpers.Exists(text3, value)) { string text4 = text + ".old2"; if (FileHelpers.Exists(text4, value)) { FileHelpers.Delete(text4, value); } FileHelpers.Copy(text3, value, text4, (CloudStorageFileGrouping)1, value); } FileHelpers.ReplaceOldFile(text, text2, text3, (CloudStorageFileGrouping)1, value); _dirty = false; long num = 0L; foreach (Entry value2 in _entries.Values) { num += value2.Contents.Length; } Plugin.Log.LogInfo((object)$"Saved {_entries.Count} chest store(s), {num / 1024}KB, in {stopwatch.ElapsedMilliseconds}ms."); } catch (Exception arg) { Plugin.Log.LogError((object)$"Failed to save chest store: {arg}"); if (val2 != null) { val2.Finish(); } } } } internal static class WorldSaveHooks { private static Action _onSaveStarted; internal static void Install(IChestStore store) { _onSaveStarted = delegate { try { ChestSessions.PersistAll(); ChestSessions.ReleaseIdle(TimeSpan.FromMinutes(10.0)); store.Flush(); } catch (Exception arg) { Plugin.Log.LogError((object)$"Chest store flush failed during world save: {arg}"); } }; ZNet.WorldSaveStarted = (Action)Delegate.Combine(ZNet.WorldSaveStarted, _onSaveStarted); } internal static void Uninstall() { if (_onSaveStarted != null) { ZNet.WorldSaveStarted = (Action)Delegate.Remove(ZNet.WorldSaveStarted, _onSaveStarted); _onSaveStarted = null; } } } } namespace BottomlessChest.Settings { internal static class ModConfig { private const string DefaultRequirements = "Wood:10"; private const string PreviousDefaultRequirements = "FineWood:20,BlackMetal:10,SurtlingCore:5"; private const string DefaultBodyTint = "#C9B79A"; private const string DefaultLidTint = "#3A2E22"; private const string DefaultGlowColour = "#FFC489"; private const float DefaultGlowStrength = 0.25f; private const string PreviousBodyTint = "#C4C2BC"; private const string PreviousLidTint = "#2E2E33"; private const string PreviousGlowColour = "#8FA86B"; private const float PreviousGlowStrength = 0.35f; internal static ConfigEntry<string> RecipeRequirements; internal static ConfigEntry<float> ModelScale; internal static ConfigEntry<string> BodyTint; internal static ConfigEntry<string> LidTint; internal static ConfigEntry<string> GlowColour; internal static ConfigEntry<float> GlowStrength; internal static ConfigEntry<bool> EnableTestingCommands; internal static void Bind(ConfigFile cfg) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown EnableTestingCommands = cfg.Bind<bool>("Testing", "EnableTestingCommands", false, "Enables 'bottomless fill' and 'bottomless empty', which exist to test the mod rather than to play with. They still require devcommands on top of this. Off by default because filling a chest with a hundred thousand items is not something anyone should be one typo away from."); ModelScale = cfg.Bind<float>("Appearance", "ModelScale", 1f, new ConfigDescription("Scale multiplier on the chest model, relative to the vanilla personal chest - which is already small. Colliders scale too, so low values make it fiddly to click. Requires a restart.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 3f), Array.Empty<object>())); BodyTint = cfg.Bind<string>("Appearance", "BodyTint", "#C9B79A", "Hex colour multiplied over the chest body. Darker values read as a solid object; avoid blue-cyan, which is the colour of the placement ghost."); LidTint = cfg.Bind<string>("Appearance", "LidTint", "#3A2E22", "Hex colour multiplied over the chest lid. Darker than the body makes the glow read against it."); GlowColour = cfg.Bind<string>("Appearance", "GlowColour", "#FFC489", "Hex colour of the emissive glow on the chest lid."); GlowStrength = cfg.Bind<float>("Appearance", "GlowStrength", 0.25f, new ConfigDescription("Multiplier on the lid glow. Around 0.5 is a subtle sheen; above 1 pushes it into bloom and reads as a light source.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); RecipeRequirements = cfg.Bind<string>("Crafting", "Requirements", "Wood:10", "Build cost, as Item:Amount pairs separated by commas. Item names are prefab names, not display names."); ConfigEntry<int> val = cfg.Bind<int>("Advanced", "AppliedDefaultChanges", 0, "Internal. Which of this mod's default changes have already been applied to this file, so they are applied once and never fight a value you chose. Do not edit."); if (val.Value < 1) { val.Value = 1; string text = ConfigDefaults.Upgrade(RecipeRequirements.Value, "FineWood:20,BlackMetal:10,SurtlingCore:5", "Wood:10"); if (text != RecipeRequirements.Value) { RecipeRequirements.Value = text; Plugin.Log.LogInfo((object)("Build cost was the old default; updated to " + text + ".")); } if (BodyTint.Value == "#C4C2BC" && LidTint.Value == "#2E2E33" && GlowColour.Value == "#8FA86B" && GlowStrength.Value == 0.35f) { BodyTint.Value = "#C9B79A"; LidTint.Value = "#3A2E22"; GlowColour.Value = "#FFC489"; GlowStrength.Value = 0.25f; Plugin.Log.LogInfo((object)"Chest colours were the old defaults; updated to the 0.3.0 look."); } } } internal static RequirementConfig[] ParseRequirements() { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown List<RequirementConfig> list = new List<RequirementConfig>(); string[] array = RecipeRequirements.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { string[] array2 = text.Split(new char[1] { ':' }); if (array2.Length != 2 || !int.TryParse(array2[1].Trim(), out var result) || result <= 0) { Plugin.Log.LogWarning((object)("Ignoring malformed build requirement '" + text.Trim() + "'. Expected Item:Amount.")); continue; } list.Add(new RequirementConfig { Item = array2[0].Trim(), Amount = result, Recover = true }); } return list.ToArray(); } } } namespace BottomlessChest.Piece { internal static class BottomlessChestPiece { internal const string PrefabName = "bottomless_chest"; private const string DisplayNameToken = "$piece_bottomlesschest"; private const string BasePrefabName = "piece_chest_private"; private static readonly Color FallbackTint = new Color(0.77f, 0.76f, 0.74f); private static readonly Color FallbackLid = new Color(0.18f, 0.18f, 0.2f); private static readonly Color FallbackGlow = new Color(0.56f, 0.66f, 0.42f); private static readonly string[] LidMarkers = new string[4] { "top", "lid", "open", "closed" }; private static Color ReadColour(string hex, Color fallback) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) Color result = default(Color); if (!string.IsNullOrWhiteSpace(hex) && ColorUtility.TryParseHtmlString(hex.Trim(), ref result)) { return result; } Plugin.Log.LogWarning((object)("Could not read colour '" + hex + "'; using the default instead.")); return fallback; } internal static void Register() { PrefabManager.OnVanillaPrefabsAvailable += AddPiece; CustomLocalization localization = LocalizationManager.Instance.GetLocalization(); string text = "English"; localization.AddTranslation(ref text, new Dictionary<string, string> { { "piece_bottomlesschest", "Bottomless Chest" }, { "piece_bottomlesschest_desc", "Holds everything. Type to find it again." }, { "bottomless_search", "Search..." }, { "bottomless_toolarge", "This chest holds too much to open over the network." } }); } private static void AddPiece() { //IL_0011: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Invalid comparison between Unknown and I4 //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) PrefabManager.OnVanillaPrefabsAvailable -= AddPiece; try { PieceConfig val = new PieceConfig { Name = "$piece_bottomlesschest", Description = "$piece_bottomlesschest_desc", PieceTable = PieceTables.Hammer, Category = PieceCategories.Furniture, CraftingStation = CraftingStations.Workbench, Requirements = ModConfig.ParseRequirements() }; CustomPiece val2 = new CustomPiece("bottomless_chest", "piece_chest_private", val); if ((Object)(object)val2.PiecePrefab == (Object)null) { Plugin.Log.LogError((object)"Could not clone 'piece_chest_private'. The chest will not be buildable."); return; } Container component = val2.PiecePrefab.GetComponent<Container>(); if ((Object)(object)component != (Object)null) { component.m_name = "$piece_bottomlesschest"; if ((int)component.m_privacy != 2) { Plugin.Log.LogInfo((object)(string.Format("Base prefab '{0}' is {1}; forcing Public so ", "piece_chest_private", component.m_privacy) + "other players can use the chest.")); component.m_privacy = (PrivacySetting)2; } } else { Plugin.Log.LogWarning((object)"Cloned chest has no Container component; its name will be wrong."); } ApplyScale(val2.PiecePrefab); ApplyTint(val2.PiecePrefab); val2.PiecePrefab.AddComponent<BottomlessContainer>(); PieceManager.Instance.AddPiece(val2); Plugin.Log.LogInfo((object)"Registered piece 'bottomless_chest'."); } catch (Exception arg) { Plugin.Log.LogError((object)$"Failed to register the bottomless chest piece: {arg}"); } } private static void ApplyScale(GameObject prefab) { //IL_001f: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) float value = ModConfig.ModelScale.Value; if (!Mathf.Approximately(value, 1f)) { Vector3 localScale = prefab.transform.localScale; prefab.transform.localScale = localScale * value; Plugin.Log.LogInfo((object)$"Chest model scaled by {value:0.##}x: {localScale} -> {prefab.transform.localScale}."); } } private static void ApplyTint(GameObject prefab) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) Color val = ReadColour(ModConfig.BodyTint.Value, FallbackTint); Color val2 = ReadColour(ModConfig.LidTint.Value, FallbackLid); Color val3 = ReadColour(ModConfig.GlowColour.Value, FallbackGlow) * ModConfig.GlowStrength.Value; List<string> list = new List<string>(); Renderer[] componentsInChildren = prefab.GetComponentsInChildren<Renderer>(true); foreach (Renderer renderer in componentsInChildren) { Material[] sharedMaterials = renderer.sharedMaterials; Material[] array = (Material[])(object)new Material[sharedMaterials.Length]; for (int j = 0; j < sharedMaterials.Length; j++) { if (!((Object)(object)sharedMaterials[j] == (Object)null)) { Material val4 = new Material(sharedMaterials[j]); string[] obj = new string[6] { ((Object)renderer).name, "/", ((Object)sharedMaterials[j]).name, " (shader ", null, null }; Shader shader = sharedMaterials[j].shader; obj[4] = ((shader != null) ? ((Object)shader).name : null); obj[5] = ")"; list.Add(string.Concat(obj)); bool flag = Array.Exists(LidMarkers, (string marker) => ((Object)renderer).name.IndexOf(marker, StringComparison.OrdinalIgnoreCase) >= 0); if (val4.HasProperty("_Color")) { val4.SetColor("_Color", val4.GetColor("_Color") * (flag ? val2 : val)); } if (flag && val4.HasProperty("_EmissionColor")) { val4.EnableKeyword("_EMISSION"); val4.SetColor("_EmissionColor", val3); } array[j] = val4; } } renderer.sharedMaterials = array; } Plugin.Log.LogDebug((object)("Chest renderers: " + string.Join(", ", list))); } } } namespace BottomlessChest.Net { internal enum ChestMessage { Open, Page, PageResult, Take, Granted, Put, Accepted, Close, Fill, Clear, StackAll, Stacked, Counts, IndexRequest, IndexResult, TakeByName, DepositForward, DepositRefused, PutRefused } internal static class ChestRpc { private const string RpcName = "BottomlessChest_Sync"; private static CustomRPC _rpc; internal static readonly ChestIndexCache Indexes = new ChestIndexCache(); internal static bool Ready => _rpc != null; internal static bool CanSend { get { if (_rpc != null) { return ZRoutedRpc.instance != null; } return false; } } internal static void DepositForward(string storeId, byte[] itemBytes) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(16); val.Write(storeId); val.Write(itemBytes); ToServer(val); } internal static void TakeByName(string storeId, long version, string itemId, int quality, int amount, int minWorldLevel = -1) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown if (!string.IsNullOrEmpty(storeId) && !string.IsNullOrEmpty(itemId) && amount > 0 && CanSend) { ZPackage val = new ZPackage(); val.Write(15); val.Write(storeId); val.Write(version); val.Write(itemId); val.Write(quality); val.Write(minWorldLevel); val.Write(amount); ToServer(val); } } internal static void RequestIndex(string storeId) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown if (!string.IsNullOrEmpty(storeId) && Ready) { ChestIndex val2 = default(ChestIndex); ChestIndex val = (Indexes.TryGet(storeId, ref val2) ? val2 : null); ZPackage val3 = new ZPackage(); val3.Write(13); val3.Write(storeId); val3.Write((val != null) ? val.Generation : 0); val3.Write((val != null) ? val.Version : (-1)); ToServer(val3); } } internal static void Register() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_0027: Expected O, but got Unknown _rpc = NetworkManager.Instance.AddRPC("BottomlessChest_Sync", new CoroutineHandler(OnServerReceive), new CoroutineHandler(OnClientReceive)); Plugin.Log.LogInfo((object)"Registered RPC 'BottomlessChest_Sync'."); } private static void ToServer(ZPackage package) { if (_rpc != null && ZRoutedRpc.instance != null) { _rpc.SendPackage(ZRoutedRpc.instance.GetServerPeerID(), package); } } internal static void Open(string storeId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(0); val.Write(storeId); ToServer(val); } internal static void RequestPage(string storeId, string query, int scrollRow, int requestId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write(storeId); val.Write(query ?? string.Empty); val.Write(scrollRow); val.Write(requestId); ToServer(val); } internal static void Take(string storeId, long version, IReadOnlyList<int> indices, IReadOnlyList<int> amounts = null, bool dropOnArrival = false) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(3); val.Write(storeId); val.Write(version); val.Write(indices.Count); for (int i = 0; i < indices.Count; i++) { val.Write(indices[i]); val.Write((amounts != null && i < amounts.Count) ? amounts[i] : 0); } val.Write(dropOnArrival); ToServer(val); } internal static void Put(string storeId, int putId, byte[] itemBytes) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(5); val.Write(storeId); val.Write(putId); val.Write(itemBytes); ToServer(val); } internal static void Fill(string storeId, int stacks, string prefabName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(8); val.Write(storeId); val.Write(stacks); val.Write(prefabName ?? string.Empty); ToServer(val); } internal static void Clear(string storeId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_0019: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(9); val.Write(storeId); ToServer(val); } internal static void StackAll(string storeId, byte[] candidateBytes) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(10); val.Write(storeId); val.Write(candidateBytes); ToServer(val); } internal static void Close(string storeId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(7); val.Write(storeId); ToServer(val); } private static IEnumerator OnServerReceive(long sender, ZPackage package) { if (package != null && package.Size() != 0) { ChestMessage kind = (ChestMessage)package.ReadInt(); string storeId = package.ReadString(); Handle(sender, kind, storeId, package); } yield break; } private static void Handle(long sender, ChestMessage kind, string storeId, ZPackage package) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown try { HandleMessage(sender, kind, storeId, package); } catch (Exception arg) { Plugin.Log.LogError((object)$"Failed to handle {kind} for chest {storeId}: {arg}"); try { switch (kind) { case ChestMessage.Put: SendPutRefused(sender, storeId, 0); break; case ChestMessage.StackAll: { ZPackage val = new ZPackage(); val.Write(11); val.Write(storeId); val.Write(0); _rpc.SendPackage(sender, val); break; } } } catch (Exception arg2) { Plugin.Log.LogError((object)$"Could not even refuse {kind} for chest {storeId}: {arg2}"); } } } private static void HandleMessage(long sender, ChestMessage kind, string storeId, ZPackage package) { //IL_085c: Unknown result type (might be due to invalid IL or missing references) //IL_0863: Expected O, but got Unknown //IL_0522: Unknown result type (might be due to invalid IL or missing references) //IL_0529: Expected O, but got Unknown //IL_07f5: Unknown result type (might be due to invalid IL or missing references) //IL_07fc: Expected O, but got Unknown //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_056d: Expected O, but got Unknown //IL_05a3: Unknown result type (might be due to invalid IL or missing references) //IL_05aa: Expected O, but got Unknown //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Expected O, but got Unknown //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Expected O, but got Unknown //IL_06dd: Unknown result type (might be due to invalid IL or missing references) //IL_06e4: Expected O, but got Unknown ChestSession session2; switch (kind) { case ChestMessage.Open: { ChestSession chestSession7 = ChestSessions.Acquire(storeId); if (chestSession7 != null) { SendPage(sender, chestSession7, 0); } break; } case ChestMessage.Page: { string query = package.ReadString(); int scrollRow = package.ReadInt(); int requestId = package.ReadInt(); ChestSession chestSession8 = ChestSessions.Acquire(storeId); if (chestSession8 != null) { chestSession8.SetQuery(query); SendPage(sender, chestSession8, scrollRow, requestId); } break; } case ChestMessage.Take: { long num = package.ReadLong(); int num2 = package.ReadInt(); List<int> list = new List<int>(num2); List<int> list2 = new List<int>(num2); for (int i = 0; i < num2; i++) { list.Add(package.ReadInt()); list2.Add(package.ReadInt()); } bool flag = package.ReadBool(); if (!ChestSessions.TryGet(storeId, out var session)) { break; } if (RefusesToChange(session, sender, "a withdrawal")) { SendPage(sender, session, -1); break; } if (session.Version != num) { Plugin.Log.LogWarning((object)($"Rejecting stale take on chest {storeId}: client is at v{num}, " + $"session is at v{session.Version}.")); SendPage(sender, session, -1); break; } List<ItemData> list3 = session.Take(list, list2); ChestSessions.Persist(session); Plugin.Log.LogDebug((object)($"Take from {storeId}: {list.Count} requested, {list3.Count} removed, " + $"{session.TotalCount} left.")); byte[] array = Serialize(list3); if (array == null) { Plugin.Log.LogError((object)($"Could not send {list3.Count} taken stack(s) from chest {storeId}. " + "Putting them back rather than dropping them.")); foreach (ItemData item in list3) { session.Add(item); } ChestSessions.Persist(session); SendPage(sender, session, -1); } else { ZPackage val = new ZPackage(); val.Write(4); val.Write(storeId); val.Write(array); val.Write(flag); _rpc.SendPackage(sender, val); SendCounts(sender, session); } break; } case ChestMessage.Put: { int num3 = package.ReadInt(); byte[] bytes = package.ReadByteArray(); bool flag2 = ChestSessions.TryGet(storeId, out session2); ChestSession chestSession3 = ChestSessions.Acquire(storeId); if (chestSession3 == null) { SendPutRefused(sender, storeId, num3); break; } if (RefusesToChange(chestSession3, sender, "a deposit")) { SendPutRefused(sender, storeId, num3); if (flag2) { SendPage(sender, chestSession3, -1); } else { ChestSessions.Release(storeId); } break; } if (!TryDeserialize(bytes, out var items) || items.Count == 0) { Plugin.Log.LogError((object)("Could not read a deposit into chest " + storeId + "; refusing it so the sender keeps the item.")); SendPutRefused(sender, storeId, num3); if (flag2) { SendPage(sender, chestSession3, -1); } else { ChestSessions.Release(storeId); } break; } foreach (ItemData item2 in items) { chestSession3.Deposit(item2); } ChestSessions.Persist(chestSession3); ZPackage val2 = new ZPackage(); val2.Write(6); val2.Write(storeId); val2.Write(num3); _rpc.SendPackage(sender, val2); if (flag2) { SendPage(sender, chestSession3, -1); } else { ChestSessions.Release(storeId); } break; } case ChestMessage.Fill: { int stacks = package.ReadInt(); string prefabName = package.ReadString(); if (!TestingAllowed(sender, "fill", storeId)) { break; } ChestSession chestSession9 = ChestSessions.Acquire(storeId); if (chestSession9 == null || RefusesToChange(chestSession9, sender, "a fill")) { break; } string error; List<ItemData> list5 = TestData.Build(stacks, prefabName, out error); if (error != null) { Plugin.Log.LogWarning((object)("Fill refused for chest " + storeId + ": " + error)); break; } foreach (ItemData item3 in list5) { chestSession9.Add(item3); } ChestSessions.Persist(chestSession9); Plugin.Log.LogDebug((object)$"Added {list5.Count} filler stacks to chest {storeId}."); SendPage(sender, chestSession9, -1); break; } case ChestMessage.Clear: { if (!TestingAllowed(sender, "empty", storeId)) { break; } ChestSession chestSession2 = ChestSessions.Acquire(storeId); if (chestSession2 != null) { if (RefusesToChange(chestSession2, sender, "an empty")) { SendPage(sender, chestSession2, -1); break; } int totalCount = chestSession2.TotalCount; chestSession2.Clear(); ChestSessions.Persist(chestSession2); Plugin.Log.LogDebug((object)$"Emptied chest {storeId} of {totalCount} stacks."); SendPage(sender, chestSession2, 0); } break; } case ChestMessage.StackAll: { if (!TryDeserialize(package.ReadByteArray(), out var items3)) { Plugin.Log.LogError((object)("Could not read a Place Stacks offer for chest " + storeId + "; refusing all of it so the sender keeps everything.")); ZPackage val5 = new ZPackage(); val5.Write(11); val5.Write(storeId); val5.Write(0); _rpc.SendPackage(sender, val5); break; } bool flag3 = ChestSessions.TryGet(storeId, out session2); ChestSession chestSession6 = ChestSessions.Acquire(storeId); if (chestSession6 == null) { ZPackage val6 = new ZPackage(); val6.Write(11); val6.Write(storeId); val6.Write(0); _rpc.SendPackage(sender, val6); break; } if (RefusesToChange(chestSession6, sender, "a deposit")) { ZPackage val7 = new ZPackage(); val7.Write(11); val7.Write(storeId); val7.Write(0); _rpc.SendPackage(sender, val7); if (!flag3) { ChestSessions.Release(storeId); } break; } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (ItemData item4 in chestSession6.Inventory.m_inventory) { hashSet.Add(item4.m_shared.m_name); } List<int> list4 = new List<int>(); for (int j = 0; j < items3.Count; j++) { ItemData val8 = items3[j]; if (hashSet.Contains(val8.m_shared.m_name)) { chestSession6.Deposit(val8); list4.Add(j); } } Plugin.Log.LogDebug((object)($"Deposit into {storeId}: {items3.Count} offered, {hashSet.Count} distinct types held, " + $"{list4.Count} kept.")); if (list4.Count > 0) { ChestSessions.Persist(chestSession6); } ZPackage val9 = new ZPackage(); val9.Write(11); val9.Write(storeId); val9.Write(list4.Count); foreach (int item5 in list4) { val9.Write(item5); } _rpc.SendPackage(sender, val9); if (flag3) { SendPage(sender, chestSession6, -1); } else { ChestSessions.Release(storeId); } break; } case ChestMessage.Close: ChestSessions.Release(storeId); break; case ChestMessage.IndexRequest: { long num4 = package.ReadLong(); long num5 = package.ReadLong(); ChestSession chestSession4 = ChestSessions.Acquire(storeId); if (chestSession4 != null && (chestSession4.Generation != num4 || chestSession4.ContentsVersion != num5)) { SendIndex(sender, chestSession4); } break; } case ChestMessage.DepositForward: { byte[] array2 = package.ReadByteArray(); ChestSession chestSession5 = ChestSessions.Acquire(storeId); if (chestSession5 == null || RefusesToChange(chestSession5, sender, "a deposit from another mod") || !TryDeserialize(array2, out var items2) || items2.Count == 0) { Plugin.Log.LogWarning((object)("Could not keep an item forwarded into chest " + storeId + "; sending it back to be dropped on the ground.")); ZPackage val3 = new ZPackage(); val3.Write(17); val3.Write(storeId); val3.Write(array2); _rpc.SendPackage(sender, val3); break; } foreach (ItemData item6 in items2) { try { chestSession5.Deposit(item6); } catch (Exception arg) { Plugin.Log.LogError((object)$"Could not keep an item forwarded into chest {storeId}: {arg}"); ZPackage val4 = new ZPackage(); val4.Write(17); val4.Write(storeId); val4.Write(Serialize(new List<ItemData> { item6 })); _rpc.SendPackage(sender, val4); } } ChestSessions.Persist(chestSession5); break; } case ChestMessage.TakeByName: { package.ReadLong(); string itemId = package.ReadString(); int quality = package.ReadInt(); int minWorldLevel = package.ReadInt(); int amount = package.ReadInt(); ChestSession chestSession = ChestSessions.Acquire(storeId); if (chestSession != null) { if (!RefusesToChange(chestSession, sender, "a removal by another mod") && chestSession.RemoveByName(itemId, quality, amount, minWorldLevel) > 0) { ChestSessions.Persist(chestSession); } SendIndex(sender, chestSession); } break; } case ChestMessage.PageResult: case ChestMessage.Granted: case ChestMessage.Accepted: case ChestMessage.Stacked: case ChestMessage.Counts: case ChestMessage.IndexResult: break; } } private static void SendPutRefused(long peer, string storeId, int putId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(18); val.Write(storeId); val.Write(putId); _rpc.SendPackage(peer, val); } private static void SendCounts(long peer, ChestSession session) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(12); val.Write(session.StoreId); val.Write(session.Version); val.Write(session.TotalCount); val.Write(session.MatchCount); val.Write(session.TotalWeight); _rpc.SendPackage(peer, val); } private static void DropNear(string storeId, List<ItemData> items) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) if (items == null || items.Count == 0) { return; } Vector3? val = null; foreach (BottomlessContainer item in BottomlessContainer.Loaded) { if ((Object)(object)item != (Object)null && item.CurrentStoreId == storeId) { val = ((Component)item).transform.position; break; } } if (!val.HasValue && (Object)(object)Player.m_localPlayer != (Object)null) { val = ((Component)Player.m_localPlayer).transform.position; } if (!val.HasValue) { Plugin.Log.LogError((object)$"Nowhere to drop {items.Count} item(s) sent back from chest {storeId}; they are lost."); return; } foreach (ItemData item2 in items) { if ((Object)(object)item2?.m_dropPrefab == (Object)null) { Plugin.Log.LogError((object)("An item sent back from chest " + storeId + " has no drop prefab and cannot be placed on the ground; it is lost.")); } else { ItemDrop.DropItem(item2, item2.m_stack, val.Value + Vector3.up, Quaternion.identity); } } } private static void SendIndex(long peer, ChestSession session) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) ChestIndex index = session.Index; ZPackage val = new ZPackage(); val.Write(14); val.Write(session.StoreId); val.Write(index.Generation); val.Write(index.Version); if (session.ReadOnly) { val.Write(0); _rpc.SendPackage(peer, val); return; } val.Write(index.Entries.Count); foreach (IndexEntry entry in index.Entries) { IndexEntry current = entry; val.Write(((IndexEntry)(ref current)).ItemId); val.Write(((IndexEntry)(ref current)).Quality); val.Write(((IndexEntry)(ref current)).WorldLevel); val.Write(((IndexEntry)(ref current)).Cheated); val.Write(((IndexEntry)(ref current)).Count); } _rpc.SendPackage(peer, val); } private static void SendPage(long peer, ChestSession session, int scrollRow, int requestId = 0) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown int scrollRow2 = ((scrollRow < 0) ? session.LastScrollRow : scrollRow); List<ItemData> items = session.Page(scrollRow2, ChestView.PageSlots); int lastScrollRow = session.LastScrollRow; ZPackage val = new ZPackage(); val.Write(2); val.Write(session.StoreId); val.Write(session.Version); val.Write(session.TotalCount); val.Write(session.MatchCount); val.Write(session.TotalWeight); val.Write(lastScrollRow); val.Write(requestId); val.Write(Serialize(items)); _rpc.SendPackage(peer, val); } private static bool RefusesToChange(ChestSession session, long sender, string what) { if (session == null || !session.ReadOnly) { return false; } Plugin.Log.LogError((object)("Refusing " + what + " on chest " + session.StoreId + ": it loaded short and is open read-only, so nothing can be written to it. Whoever asked keeps their items. Something in this chest has an item prefab this install cannot resolve.")); return true; } internal static byte[] Serialize(List<ItemData> items) { //IL_0008: 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_0023: Expected O, but got Unknown Inventory val = new Inventory("page", (Sprite)null, 8, 4); val.m_inventory.AddRange(items); return InventorySerializer.Save(val, "page"); } private static bool TestingAllowed(long sender, string what, string storeId) { if (ModConfig.EnableTestingCommands != null && ModConfig.EnableTestingCommands.Value) { return true; } Plugin.Log.LogWarning((object)($"Refused '{what}' on chest {storeId} from peer {sender}: testing commands are " + "disabled here. Enable EnableTestingCommands in the Testing section of this machine's config if that was intended.")); return false; } private static List<ItemData> Deserialize(byte[] bytes) { if (!TryDeserialize(bytes, out var items)) { return new List<ItemData>(); } return items; } private static bool TryDeserialize(byte[] bytes, out List<ItemData> items) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown Inventory val = new Inventory("page", (Sprite)null, 8, 4096); bool result = true; if (bytes != null && bytes.Length != 0) { result = InventorySerializer.Load(val, bytes, out var expected) && val.m_inventory.Count == expected; } items = new List<ItemData>(val.m_inventory); return result; } private static IEnumerator OnClientReceive(long sender, ZPackage package) { if (package == null || package.Size() == 0) { yield break; } ChestMessage chestMessage = (ChestMessage)package.ReadInt(); string text = package.ReadString(); switch (chestMessage) { case ChestMessage.PageResult: { long version2 = package.ReadLong(); int total2 = package.ReadInt(); int matches2 = package.ReadInt(); float weight2 = package.ReadSingle(); int scrollRow = package.ReadInt(); int requestId = package.ReadInt(); List<ItemData> items3 = Deserialize(package.ReadByteArray()); ChestView.ApplyPage(text, version2, total2, matches2, weight2, scrollRow, requestId, items3); break; } case ChestMessage.Granted: { List<ItemData> items; bool num2 = TryDeserialize(package.ReadByteArray(), out items); bool dropOnArrival = package.ReadBool(); if (!num2) { Plugin.Log.LogError((object)("Could not fully read the items granted from chest " + text + "; " + $"{items.Count} recovered. The server removed them before sending, " + "so anything missing is lost. This install is most likely missing a content mod the server has.")); } ChestView.ApplyGranted(items, dropOnArrival); break; } case ChestMessage.Counts: { long version = package.ReadLong(); int total = package.ReadInt(); int matches = package.ReadInt(); float weight = package.ReadSingle(); ChestView.ApplyCounts(text, version, total, matches, weight); break; } case ChestMessage.Accepted: ChestView.ApplyAccepted(package.ReadInt()); break; case ChestMessage.PutRefused: ChestView.ApplyPutRefused(text, package.ReadInt()); break; case ChestMessage.DepositRefused: { if (!TryDeserialize(package.ReadByteArray(), out var items2)) { Plugin.Log.LogError((object)("Could not fully read the items chest " + text + " sent back; " + $"{items2.Count} recovered.")); } DropNear(text, items2); break; } case ChestMessage.IndexResult: { long num3 = package.ReadLong(); long num4 = package.ReadLong(); int num5 = package.ReadInt(); List<IStorableItem> list2 = new List<IStorableItem>(num5); for (int j = 0; j < num5; j++) { string text2 = package.ReadString(); int num6 = package.ReadInt(); int num7 = package.ReadInt(); bool flag = package.ReadBool(); int num8 = package.ReadInt(); list2.Add((IStorableItem)new IndexedItem(text2, num6, num8, num7, flag)); } Indexes.Put(text, ChestIndex.From(num3, num4, (IEnumerable<IStorableItem>)list2)); break; } case ChestMessage.Stacked: { int num = package.ReadInt(); List<int> list = new List<int>(num); for (int i = 0; i < num; i++) { list.Add(package.ReadInt()); } ChestView.ApplyStacked(text, list); break; } } } } } namespace BottomlessChest.Gui { internal static class ChestGamepadPaging { [HarmonyPatch(typeof(InventoryGrid), "UpdateGamepad")] private static class Patch { private static void Prefix(InventoryGrid __instance) { if (Plugin.Degraded || !ChestView.IsOpen || !ChestView.IsDisplaying(__instance.m_inventory) || (Object)(object)__instance.m_uiGroup == (Object)null || !__instance.m_uiGroup.IsActive || !ZInput.IsExclusiveGamepadActive()) { return; } bool num = __instance.m_selected.y >= __instance.m_height - 1 && ChestView.ScrollRow < ChestView.MaxScroll; bool flag = __instance.m_selected.y <= 0 && ChestView.ScrollRow > 0; int num2 = ((num && (ZInput.GetButton("JoyDPadDown") || ZInput.GetButton("JoyLStickDown"))) ? 1 : ((flag && (ZInput.GetButton("JoyDPadUp") || ZInput.GetButton("JoyLStickUp"))) ? (-1) : 0)); if (num2 == 0) { _held = 0; return; } bool flag2 = ((num2 <= 0) ? (ZInput.GetButtonDown("JoyDPadUp") || ZInput.GetButtonDown("JoyLStickUp")) : (ZInput.GetButtonDown("JoyDPadDown") || ZInput.GetButtonDown("JoyLStickDown"))); float unscaledTime = Time.unscaledTime; if (_held != num2) { _held = num2; _nextStep = unscaledTime + 0.35f; if (!flag2) { return; } } else { if (unscaledTime < _nextStep && !flag2) { return; } _nextStep = unscaledTime + (flag2 ? 0.35f : 0.12f); } ChestView.Scroll(num2); if (num2 > 0) { ZInput.ResetButtonStatus("JoyDPadDown"); ZInput.ResetButtonStatus("JoyLStickDown"); } else { ZInput.ResetButtonStatus("JoyDPadUp"); ZInput.ResetButtonStatus("JoyLStickUp"); } } } private const float FirstRepeat = 0.35f; private const float Repeat = 0.12f; private static int _held; private static float _nextStep; } internal sealed class ChestScrollbarBridge : MonoBehaviour { private const float MinimumHandle = 0.12f; private Scrollbar _bar; private ScrollRect _detachedFrom; private bool _writing; private bool _barWasActive; private bool _hadBar; private bool _wasVertical; internal void Bind(InventoryGrid grid) { //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)grid == (Object)null) { return; } _bar = grid.m_scrollbar; if ((Object)(object)_bar == (Object)null) { return; } _barWasActive = ((Component)_bar).gameObject.activeSelf; ScrollRect component = ((Component)grid).GetComponent<ScrollRect>(); if ((Object)(object)component != (Object)null && ((Object)(object)component.verticalScrollbar == (Object)(object)_bar || (Object)(object)component.verticalScrollbar == (Object)null)) { _hadBar = (Object)(object)component.verticalScrollbar == (Object)(object)_bar; _detachedFrom = component; component.verticalScrollbar = null; _wasVertical = !_hadBar || component.vertical; component.vertical = false; component.StopMovement(); if ((Object)(object)component.content != (Object)null) { component.content.anchoredPosition = new Vector2(component.content.anchoredPosition.x, 0f); } } Plugin.Log.LogDebug((object)("Scrollbar bridge bound: bar was " + (_barWasActive ? "visible" : "hidden") + ", ScrollRect " + (((Object)(object)_detachedFrom == (Object)null) ? "not claimed" : "detached") + ".")); ((UnityEvent<float>)(object)_bar.onValueChanged).AddListener((UnityAction<float>)OnBarMoved); Sync(); } private void OnBarMoved(float value) { if (!_writing && ChestView.IsOpen) { ChestView.ScrollTo(Mathf.RoundToInt(TopFraction(value) * (float)ChestView.MaxScroll)); } } private void Update() { if ((Object)(object)_bar != (Object)null && ChestView.IsOpen) { Sync(); } } private void Sync() { int totalRows = ChestView.TotalRows; int rowsOnScreen = ChestView.RowsOnScreen; int maxScroll = ChestView.MaxScroll; bool flag = maxScroll > 0; if (((Component)_bar).gameObject.activeSelf != flag) { ((Component)_bar).gameObject.SetActive(flag); } _writing = true; _bar.size = ((totalRows <= rowsOnScreen) ? 1f : Mathf.Clamp((float)rowsOnScreen / (float)totalRows, 0.12f, 1f)); if (!ChestView.PageRequestPending) { float value = ((maxScroll <= 0) ? 0f : ((float)ChestView.ScrollRow / (float)maxScroll)); _bar.SetValueWithoutNotify(TopFraction(value)); } _writing = false; } private float TopFraction(float value) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if ((int)_bar.direction != 2 && (int)_bar.direction != 1) { return value; } return 1f - value; } private void OnDestroy() { if ((Object)(object)_bar != (Object)null) { ((UnityEvent<float>)(object)_bar.onValueChanged).RemoveListener((UnityAction<float>)OnBarMoved); _bar.size = 1f; if (((Component)_bar).gameObject.activeSelf != _barWasActive) { ((Component)_bar).gameObject.SetActive(_barWasActive); } } if ((Object)(object)_detachedFrom != (Object)null) { _detachedFrom.vertical = _wasVertical; if (_hadBar) { _detachedFrom.verticalScrollbar = _bar; } _detachedFrom = null; } } } internal sealed class ChestScroller : MonoBehaviour { private const float StickThreshold = 0.5f; private const float StickFirstRepeat = 0.35f; private const float StickRepeat = 0.12f; private bool _stickHeld; private float _nextStickStep; private float _wheel; private void Update() { if (ChestView.IsOpen) { ChestView.Tick(); ScrollWithWheel(); ScrollWithRightStick(); } } private void ScrollWithRightStick() { if (!ZInput.IsGamepadActive()) { _stickHeld = false; return; } float joyRightStickY = ZInput.GetJoyRightStickY(); if (Mathf.Abs(joyRightStickY) < 0.5f) { _stickHeld = false; return; } float unscaledTime = Time.unscaledTime; if (!_stickHeld || !(unscaledTime < _nextStickStep)) { _nextStickStep = unscaledTime + (_stickHeld ? 0.12f : 0.35f); _stickHeld = true; ChestView.Scroll((!(joyRightStickY < 0f)) ? 1 : (-1)); } } private void ScrollWithWheel() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) float y = Input.mouseScrollDelta.y; if (Mathf.Abs(y) < 0.01f) { return; } if (!PointerOverChest()) { _wheel = 0f; return; } _wheel += y; while (_wheel >= 1f) { _wheel -= 1f; ChestView.Scroll(-1); } while (_wheel <= -1f) { _wheel += 1f; ChestView.Scroll(1); } } private static bool PointerOverChest() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) InventoryGui instance = InventoryGui.instance; RectTransform val = (((Object)(object)instance != (Object)null) ? instance.m_container : null); if ((Object)(object)val == (Object)null) { return false; } Canvas componentInParent = ((Component)val).GetComponentInParent<Canvas>(); Camera val2 = (((Object)(object)componentInParent == (Object)null || (int)componentInParent.renderMode == 0) ? null : componentInParent.worldCamera); return RectTransformUtility.RectangleContainsScreenPoint(val, Vector2.op_Implicit(Input.mousePosition), val2); } } internal static class ContainerWeightPatch { [HarmonyPatch(typeof(InventoryGui), "UpdateContainerWeight")] private static class Patch { private static bool Prefix(InventoryGui __instance) { if (Plugin.Degraded || !ChestView.IsRemote || (Object)(object)__instance.m_currentContainer == (Object)null) { return true; } __instance.m_containerWeight.text = Mathf.CeilToInt(ChestView.RemoteWeight).ToString(); return false; } } } internal static class DropSlotHighlight { [HarmonyPatch(typeof(InventoryGrid), "UpdateGui")] private static class Patch { private static void Postfix(InventoryGrid __instance) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) RectTransform gridRoot = __instance.m_gridRoot; if ((Object)(object)gridRoot == (Object)null || Plugin.Degraded || ZInput.IsTouchActive()) { return; } int num = (ChestView.IsDisplaying(__instance.m_inventory) ? ChestView.DropSlotIndex : (-1)); for (int i = 0; i < ((Transform)gridRoot).childCount; i++) { InventoryElement component = ((Component)((Transform)gridRoot).GetChild(i)).GetComponent<InventoryElement>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.m_dropFocus == (Object)null)) { bool flag = i == num; ((Graphic)component.m_dropFocus).color = (flag ? DropColour(component) : Color.clear); ApplyGlyph(component, flag); } } } private static Color DropColour(InventoryElement element) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) Color dropFocusOriginalColor = element.DropFocusOriginalColor; if (!(dropFocusOriginalColor.a > 0f)) { return Fallback; } return dropFocusOriginalColor; } private static void ApplyGlyph(InventoryElement element, bool isDropSlot) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) TMP_Text amount = element.m_amount; if (!((Object)(object)amount == (Object)null)) { RectTransform rectTransform = amount.rectTransform; if (!_defaultsCaptured) { _defaultAlignment = amount.alignment; _defaultFontSize = amount.fontSize; _defaultColour = ((Graphic)amount).color; _defaultAutoSize = amount.enableAutoSizing; _defaultAnchorMin = rectTransform.anchorMin; _defaultAnchorMax = rectTransform.anchorMax; _defaultOffsetMin = rectTransform.offsetMin; _defaultOffsetMax = rectTransform.offsetMax; _defaultsCaptured = true; } if (isDropSlot) { rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; amount.enableAutoSizing = false; amount.alignment = (TextAlignmentOptions)514; amount.fontSize = _defaultFontSize * 3f; ((Graphic)amount).color = GlyphColour; amount.text = "+"; ((Behaviour)amount).enabled = true; } else { rectTransform.anchorMin = _defaultAnchorMin; rectTransform.anchorMax = _defaultAnchorMax; rectTransform.offsetMin = _defaultOffsetMin; rectTransform.offsetMax = _defaultOffsetMax; amount.enableAutoSizing = _defaultAutoSize; amount.alignment = _defaultAlignment; amount.fontSize = _defaultFontSize; ((Graphic)amount).color = _defaultColour; } } } } private static readonly Color Fallback = new Color(0.65f, 0.85f, 0.55f, 0.55f); private const string DropGlyph = "+"; private const float GlyphScale = 3f; private static readonly Color GlyphColour = new Color(1f, 1f, 1f, 0.45f); private static TextAlignmentOptions _defaultAlignment; private static float _defaultFontSize; private static Color _defaultColour; private static bool _defaultAutoSize; private static Vector2 _defaultAnchorMin; private static Vector2 _defaultAnchorMax; private static Vector2 _defaultOffsetMin; private static Vector2 _defaultOffsetMax; private static bool _defaultsCaptured; } internal static class ScrollbarProbe { [HarmonyPatch(typeof(InventoryGui), "Show")] private static class Patch { private static void Postfix(InventoryGui __instance, Container container) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (_reported || !DebugLogging.Enabled || (Object)(object)container == (Object)null || (Object)(object)__instance.m_containerGrid == (Object)null || !BottomlessContainer.TryResolve(container, out var _)) { return; } _reported = true; StringBuilder stringBuilder = new StringBuilder(); Scrollbar scrollbar = __instance.m_containerGrid.m_scrollbar; stringBuilder.AppendLine("--- container scrollbar probe ---"); stringBuilder.AppendLine("scrollbar: " + (((Object)(object)scrollbar == (Object)null) ? "NULL" : ((Object)scrollbar).name)); if ((Object)(object)scrollbar != (Object)null) { stringBuilder.AppendLine($" direction: {scrollbar.direction}, size: {scrollbar.size:0.###}, value: {scrollbar.value:0.###}"); stringBuilder.AppendLine(" ancestors: " + Chain(((Component)scrollbar).transform)); Transform parent = ((Component)scrollbar).transform.parent; if ((Object)(object)parent != (Object)null) { stringBuilder.AppendLine(" siblings:"); for (int i = 0; i < parent.childCount; i++) { Transform child = parent.GetChild(i); stringBuilder.AppendLine(" " + ((Object)child).name + " [" + Components(child) + "]"); } } } stringBuilder.AppendLine(" gridRoot ancestors: " + Chain((Transform)(object)__instance.m_containerGrid.m_gridRoot)); ScrollRect[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<ScrollRect>(true); foreach (ScrollRect val in componentsInChildren) { bool flag = (Object)(object)scrollbar != (Object)null && (Object)(object)val.verticalScrollbar == (Object)(object)scrollbar; stringBuilder.AppendLine($" ScrollRect '{((Object)val).name}' ownsThisBar={flag} " + "content='" + (((Object)(object)val.content == (Object)null) ? "null" : ((Object)val.content).name) + "' viewport='" + (((Object)(object)val.viewport == (Object)null) ? "null" : ((Object)val.viewport).name) + "'"); } Plugin.Log.LogDebug((object)stringBuilder.ToString()); } } private static bool _reported; internal static void Reset() { _reported = false; } private static string Chain(Transform t) { if ((Object)(object)t == (Object)null) { return "null"; } StringBuilder stringBuilder = new StringBuilder(((Object)t).name); Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { stringBuilder.Append(" < ").Append(((Object)parent).name); parent = parent.parent; } return stringBuilder.ToString(); } private static string Components(Transform t) { StringBuilder stringBuilder = new StringBuilder(); Component[] components = ((Component)t).GetComponents<Component>(); foreach (Component val in components) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(((object)val).GetType().Name); } return stringBuilder.ToString(); } } internal static class SearchBox { [HarmonyPatch(typeof(InventoryGui), "Show")] private static class ShowPatch { private static void Postfix(InventoryGui __instance, Container container) { if (!((Object)(object)container == (Object)null) && BottomlessContainer.TryResolve(container, out var bottomless)) { bottomless.RefreshFromServer(); Teardown(); ChestView.Begin(container.GetInventory(), bottomless); Build(__instance); } } } [HarmonyPatch(typeof(InventoryGui), "Hide")] private static class HidePatch { private static void Postfix() { if (_hideCancelled) { _hideCancelled = false; return; } Teardown(); BottomlessContainer.FlushAllPending(); } } [HarmonyPatch(typeof(InventoryGui), "Update")] private static class StatusRefreshPatch { private static void Postfix() { if ((Object)(object)_status != (Object)null) { UpdateStatus(); } if (_input != null && ChestView.IsOpen && ZInput.IsGamepadActive() && ZInput.GetButtonDown("JoyBack")) { ZInput.ResetButtonStatus("JoyBack"); _input.Focus(); } } } [HarmonyPatch(typeof(InventoryGui), "Hide")] private static class EscapeGuard { private static bool Prefix() { _hideCancelled = TryConsumeEscape(); return !_hideCancelled; } } private const float SearchBoxLift = 6f; private const float StatusGap = 26f; private const float StatusWidth = 280f; private static string _lastStatusRaw; private static string _lastStatusFitted; private static GameObject _field; private static GameObject _hiddenTitle; private static int _escapeFrame = -1000; private const int EscapeGraceFrames = 3; private static bool _hideCancelled; private static SearchField _input; private static Text _status; internal static void NoteEscapePressed() { _escapeFrame = Time.frameCount; } private static void Build(InventoryGui gui) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)gui.m_container == (Object)null) { return; } try { TMP_Text containerName = gui.m_containerName; RectTransform val = (((Object)(object)containerName != (Object)null) ? containerName.rectTransform : null); Transform val2 = (((Object)(object)val != (Object)null) ? ((Transform)val).parent : ((Component)gui.m_container).transform); _input = SearchField.TryCloneVanilla(val2); if (_input == null) { _input = SearchField.FromLegacy(GUIManager.Instance.CreateInputField(val2, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, 44f), (ContentType)0, "$bottomless_search", 16, 280f, 30f)); } if (_input == null) { Plugin.Log.LogError((object)"Search field could not be created; the chest opens without one."); DestroyWidgets(); return; } _field = _input.GameObject; if ((Object)(object)val != (Object)null) { RectTransform component = _field.GetComponent<RectTransform>(); component.anchorMin = val.anchorMin; component.anchorMax = val.anchorMax; component.pivot = val.pivot; component.anchoredPosition = val.anchoredPosition + new Vector2(0f, 6f); component.sizeDelta = new Vector2(280f, 30f); _hiddenTitle = ((Component)containerName).gameObject; _hiddenTitle.SetActive(false); } _input.OnChanged(OnChanged); SearchFocusGuard searchFocusGuard = _field.AddComponent<SearchFocusGuard>(); searchFocusGuard.Bind(_input); if (!ZInput.IsGamepadActive()) { searchFocusGuard.TakeFocus(); } _field.AddComponent<ChestScroller>(); _field.AddComponent<ChestScrollbarBridge>().Bind(gui.m_containerGrid); _field.transform.SetAsLastSibling(); Plugin.Log.LogDebug((object)("Search box ready (parent '" + ((Object)gui.m_container).name + "', " + $"{ChestView.TotalCount} items in chest).")); _status = GUIManager.Instance.CreateText(string.Empty, _field.transform.parent, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), Vector2.zero, GUIManager.Instance.AveriaSerifBold, 13, GUIManager.Instance.ValheimOrange, true, Color.black, 280f, 20f, false).GetComponent<Text>(); _status.horizontalOverflow = (HorizontalWrapMode)1; _status.verticalOverflow = (VerticalWrapMode)0; RectTransform component2 = _field.GetComponent<RectTransform>(); RectTransform rectTransform = ((Graphic)_status).rectTransform; rectTransform.anchorMin = component2.anchorMin; rectTransform.anchorMax = component2.anchorMax; rectTransform.pivot = component2.pivot; rectTransform.anchoredPosition = component2.anchoredPosition - new Vector2(0f, 26f); rectTransform.sizeDelta = new Vector2(280f, 20f); UpdateStatus(); } catch (Exception arg) { Plugin.Log.LogError((object)$"Could not build the chest search box: {arg}"); DestroyWidgets(); } } internal static bool TryConsumeEscape() { if (_input == null || string.IsNullOrEmpty(_input.Text)) { return false; } bool keyDown = Input.GetKeyDown((KeyCode)27); bool flag = Time.frameCount - _escapeFrame <= 3; if (!keyDown && !flag) { return false; } _escapeFrame = -1000; _input.Text = string.Empty; _input.Focus(); return true; } private static void OnChanged(string text) { try { ChestView.SetQuery(text); UpdateStatus(); } catch (Exception arg) { Plugin.Log.LogError((object)$"Filtering failed for '{text}': {arg}"); } } private static void UpdateStatus() { if (!((Object)(object)_status == (Object)null)) { if (ChestView.AwaitingContents) { _status.text = "Loading from server..."; return; } string text = (ChestView.IsFiltering ? $"{ChestView.MatchCount} of {ChestView.TotalCount} items" : $"{ChestView.TotalCount} items"); int totalRows = ChestView.TotalRows; int num = 4; _status.text = Ellipsize((totalRows > num) ? $"{text} - rows {ChestView.ScrollRow + 1}-{Mathf.Min(ChestView.ScrollRow + num, totalRows)} of {totalRows}" : text); } } private static string Ellipsize(string raw) { if (raw == _lastStatusRaw) { return _lastStatusFitted; } _lastStatusRaw = raw; _status.text = raw; if (_status.preferredWidth <= 280f) { return _lastStatusFitted = raw; } int num = 0; int num2 = raw.Length; while (num < num2) { int num3 = (num + num2 + 1) / 2; _status.text = raw.Substring(0, num3) + "..."; if (_status.preferredWidth <= 280f) { num = num3; } else { num2 = num3 - 1; } } return _lastStatusFitted = raw.Substring(0, num).TrimEnd(Array.Empty<char>()) + "..."; } private static void Teardown() { DestroyWidgets(); ChestView.End(); } private static void DestroyWidgets() { if ((Object)(object)_hiddenTitle != (Object)null) { _hiddenTitle.SetActive(true); _hiddenTitle = null; } if ((Object)(object)_field != (Object)null) { GUIManager.BlockInput(false); Object.Destroy((Object)(object)_field); _field = null; _input = null; } if ((Object)(object)_status != (Object)null) { Object.Destroy((Object)(object)((Component)_status).gameObject); _status = null; } } } internal sealed class SearchField { private readonly GuiInputField _modern; private readonly InputField _legacy; internal GameObject GameObject { get; } internal bool ReportsFocusChanges => (Object)(object)_modern != (Object)null; internal string Text { get { if (!((Object)(object)_modern != (Object)null)) { return _legacy.text; } return ((TMP_InputField)_modern).text; } set { if ((Object)(object)_modern != (Object)null) { ((TMP_InputField)_modern).text = value; } else { _legacy.text = value; } } } internal bool IsFocused { get { if (!((Object)(object)_modern != (Object)null)) { return _legacy.isFocused; } return ((TMP_InputField)_modern).isFocused; } } private SearchField(GameObject gameObject, GuiInputField modern, InputField legacy) { GameObject = gameObject; _modern = modern; _legacy = legacy; } internal void OnChanged(Action<string> handler) { if ((Object)(object)_modern != (Object)null) { ((UnityEvent<string>)(object)((TMP_InputField)_modern).onValueChanged).AddListener((UnityAction<string>)delegate(string text) { handler(text); }); } else { ((UnityEvent<string>)(object)_legacy.onValueChanged).AddListener((UnityAction<string>)delegate(string text) { handler(text); }); } } internal void OnFocusChanged(Action gained, Action lost) { ((UnityEvent<string>)(object)((TMP_InputField)_modern).onSelect).AddListener((UnityAction<string>)delegate { gained(); }); ((UnityEvent<string>)(object)((TMP_InputField)_modern).onDeselect).AddListener(