Decompiled source of BottomlessChest v0.4.0

BottomlessChest.Logic.dll

Decompiled 2 weeks ago
using 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
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(