Decompiled source of BottomlessChest v0.1.0

BottomlessChest.Logic.dll

Decompiled 20 hours 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 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 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;
		}

		private static void RequirePositiveWidth(int width)
		{
			if (width <= 0)
			{
				throw new ArgumentOutOfRangeException("width", width, "Grid width must be at least 1.");
			}
		}
	}
	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 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 StackPolicy
	{
		public bool Unlimited { get; }

		public int Multiplier { get; }

		public StackPolicy(bool unlimited, int multiplier)
		{
			Unlimited = unlimited;
			Multiplier = multiplier;
		}
	}
	public static class StackRules
	{
		public static bool CanMerge(IStorableItem a, IStorableItem b)
		{
			if (a == null || b == null)
			{
				return false;
			}
			if (a.MaxStackSize <= 1 || b.MaxStackSize <= 1)
			{
				return false;
			}
			if (string.Equals(a.ItemId, b.ItemId, StringComparison.Ordinal) && a.Quality == b.Quality && a.Variant == b.Variant)
			{
				return string.Equals(a.CustomData ?? string.Empty, b.CustomData ?? string.Empty, StringComparison.Ordinal);
			}
			return false;
		}

		public static int EffectiveStackLimit(IStorableItem item, StackPolicy policy)
		{
			if (policy.Unlimited)
			{
				return int.MaxValue;
			}
			long num = (long)item.MaxStackSize * (long)policy.Multiplier;
			if (num < item.MaxStackSize)
			{
				return item.MaxStackSize;
			}
			if (num <= int.MaxValue)
			{
				return (int)num;
			}
			return int.MaxValue;
		}

		public static IReadOnlyList<int> SplitForExit(int stack, int maxStackSize)
		{
			if (maxStackSize <= 0)
			{
				throw new ArgumentOutOfRangeException("maxStackSize", maxStackSize, "Stack ceiling must be at least 1.");
			}
			List<int> list = new List<int>();
			int num = stack;
			while (num > 0)
			{
				int num2 = Math.Min(num, maxStackSize);
				list.Add(num2);
				num -= num2;
			}
			return list;
		}
	}
	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 20 hours 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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BottomlessChest.Commands;
using BottomlessChest.Core;
using BottomlessChest.Filter;
using BottomlessChest.Logic;
using BottomlessChest.Net;
using BottomlessChest.Piece;
using BottomlessChest.Settings;
using BottomlessChest.Storage;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
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.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("BottomlessChest")]
[assembly: AssemblyTitle("BottomlessChest")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
namespace BottomlessChest
{
	[BepInPlugin("com.myrridin.bottomlesschest", "BottomlessChest", "0.1.0")]
	[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.1.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}");
				}
			}
			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.1.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 static class FastInventoryReader
	{
		private const int SupportedVersion = 106;

		internal static bool TryLoad(Inventory inventory, byte[] contents, out int expected)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: 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)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			expected = 0;
			if (contents == null || contents.Length < 8 || (Object)(object)ObjectDB.instance == (Object)null)
			{
				return false;
			}
			List<ItemData> list;
			try
			{
				ZPackage val = new ZPackage(contents);
				if (val.ReadInt() != 106)
				{
					return false;
				}
				expected = val.ReadInt();
				list = new List<ItemData>(expected);
				for (int i = 0; i < expected; 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 = 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;
			}
			inventory.m_inventory.Clear();
			inventory.m_inventory.AddRange(list);
			return true;
		}
	}
	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 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 readonly Dictionary<string, Entry> _entries = new Dictionary<string, Entry>(StringComparer.Ordinal);

		private string _loadedWorld;

		private bool _dirty;

		private bool _warnedNotAuthority;

		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_fileName;
				}
				return false;
			}
		}

		internal IEnumerable<(string StoreId, int StackCount)> Describe()
		{
			EnsureLoaded();
			foreach (KeyValuePair<string, Entry> entry in _entries)
			{
				int item;
				try
				{
					ZPackage val = new ZPackage(entry.Value.Contents);
					val.ReadInt();
					item = val.ReadInt();
				}
				catch
				{
					item = -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_006c: 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_009e: 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_fileName))
			{
				_entries.Clear();
				_dirty = false;
				_loadedWorld = world.m_fileName;
				string text = SavePath(world);
				if (!TryRead(text, world.m_fileSource) && !TryRead(text + ".old", world.m_fileSource) && !TryRead(text + ".old2", world.m_fileSource))
				{
					Plugin.Log.LogInfo((object)("No existing chest store for world '" + world.m_fileName + "'. Starting empty."));
				}
			}
		}

		private static string SavePath(World world)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return World.GetWorldSavePath(world.m_fileSource) + "/" + world.m_fileName + ".bottomless.dat";
		}

		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_0050: 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_006e: Expected O, but got Unknown
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: 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_fileName)
			{
				return;
			}
			Stopwatch stopwatch = Stopwatch.StartNew();
			string text = SavePath(world);
			string text2 = text + ".new";
			string text3 = text + ".old";
			FileWriter val = null;
			try
			{
				FileHelpers.EnsureDirectoryExists(World.GetWorldSavePath(world.m_fileSource));
				val = new FileWriter(text2, (FileHelperType)0, world.m_fileSource);
				BinaryWriter binary = val.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);
				}
				val.Finish();
				val = null;
				if (FileHelpers.Exists(text3, world.m_fileSource))
				{
					string text4 = text + ".old2";
					if (FileHelpers.Exists(text4, world.m_fileSource))
					{
						FileHelpers.Delete(text4, world.m_fileSource);
					}
					FileHelpers.Copy(text3, world.m_fileSource, text4, world.m_fileSource);
				}
				FileHelpers.ReplaceOldFile(text, text2, text3, world.m_fileSource);
				_dirty = false;
				long num = 0L;
				foreach (Entry value in _entries.Values)
				{
					num += value.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 (val != null)
				{
					val.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
	{
		internal static ConfigEntry<string> RecipeRequirements;

		internal static ConfigEntry<float> SnapshotDebounceSeconds;

		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 void Bind(ConfigFile cfg)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Expected O, but got Unknown
			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>()));
			SnapshotDebounceSeconds = cfg.Bind<float>("Multiplayer", "SnapshotDebounceSeconds", 1.5f, new ConfigDescription("How long to wait after the last change before sending a chest's contents to the server. Batches a burst of item moves into one message.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>()));
			BodyTint = cfg.Bind<string>("Appearance", "BodyTint", "#C4C2BC", "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", "#2E2E33", "Hex colour multiplied over the chest lid. Darker than the body makes the glow read against it.");
			GlowColour = cfg.Bind<string>("Appearance", "GlowColour", "#8FA86B", "Hex colour of the emissive glow on the chest lid.");
			GlowStrength = cfg.Bind<float>("Appearance", "GlowStrength", 0.35f, 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", "FineWood:20,BlackMetal:10,SurtlingCore:5", "Build cost, as Item:Amount pairs separated by commas. Item names are prefab names, not display names.");
		}

		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
	}
	internal static class ChestRpc
	{
		private const string RpcName = "BottomlessChest_Sync";

		private static CustomRPC _rpc;

		internal static bool Ready => _rpc != null;

		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)
		{
			//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_002f: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(storeId);
			val.Write(query ?? string.Empty);
			val.Write(scrollRow);
			ToServer(val);
		}

		internal static void Take(string storeId, long version, IReadOnlyList<int> indices)
		{
			//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);
			foreach (int index in indices)
			{
				val.Write(index);
			}
			ToServer(val);
		}

		internal static void Put(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_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_001f: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(5);
			val.Write(storeId);
			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)
			{
				yield break;
			}
			ChestMessage chestMessage = (ChestMessage)package.ReadInt();
			string text = package.ReadString();
			switch (chestMessage)
			{
			case ChestMessage.Open:
			{
				ChestSession chestSession2 = ChestSessions.Acquire(text);
				if (chestSession2 != null)
				{
					SendPage(sender, chestSession2, 0);
				}
				break;
			}
			case ChestMessage.Page:
			{
				string query = package.ReadString();
				int scrollRow = package.ReadInt();
				ChestSession chestSession3 = ChestSessions.Acquire(text);
				if (chestSession3 != null)
				{
					chestSession3.SetQuery(query);
					SendPage(sender, chestSession3, scrollRow);
				}
				break;
			}
			case ChestMessage.Take:
			{
				long num = package.ReadLong();
				int num2 = package.ReadInt();
				List<int> list4 = new List<int>(num2);
				for (int j = 0; j < num2; j++)
				{
					list4.Add(package.ReadInt());
				}
				if (ChestSessions.TryGet(text, out var session3))
				{
					if (session3.Version != num)
					{
						Plugin.Log.LogWarning((object)($"Rejecting stale take on chest {text}: client is at v{num}, " + $"session is at v{session3.Version}."));
						SendPage(sender, session3, -1);
						break;
					}
					List<ItemData> list5 = session3.Take(list4);
					ChestSessions.Persist(session3);
					Plugin.Log.LogDebug((object)($"Take from {text}: {list4.Count} requested, {list5.Count} removed, " + $"{session3.TotalCount} left."));
					ZPackage val4 = new ZPackage();
					val4.Write(4);
					val4.Write(text);
					val4.Write(Serialize(list5));
					_rpc.SendPackage(sender, val4);
					SendCounts(sender, session3);
				}
				break;
			}
			case ChestMessage.Put:
			{
				byte[] bytes = package.ReadByteArray();
				if (!ChestSessions.TryGet(text, out var session2))
				{
					break;
				}
				foreach (ItemData item in Deserialize(bytes))
				{
					session2.Add(item);
				}
				ChestSessions.Persist(session2);
				ZPackage val3 = new ZPackage();
				val3.Write(6);
				val3.Write(text);
				_rpc.SendPackage(sender, val3);
				SendPage(sender, session2, -1);
				break;
			}
			case ChestMessage.Fill:
			{
				int stacks = package.ReadInt();
				string prefabName = package.ReadString();
				ChestSession chestSession5 = ChestSessions.Acquire(text);
				if (chestSession5 == null)
				{
					break;
				}
				string error;
				List<ItemData> list3 = TestData.Build(stacks, prefabName, out error);
				if (error != null)
				{
					Plugin.Log.LogWarning((object)("Fill refused for chest " + text + ": " + error));
					break;
				}
				foreach (ItemData item2 in list3)
				{
					chestSession5.Add(item2);
				}
				ChestSessions.Persist(chestSession5);
				Plugin.Log.LogDebug((object)$"Added {list3.Count} filler stacks to chest {text}.");
				SendPage(sender, chestSession5, -1);
				break;
			}
			case ChestMessage.Clear:
			{
				ChestSession chestSession4 = ChestSessions.Acquire(text);
				if (chestSession4 != null)
				{
					int totalCount = chestSession4.TotalCount;
					chestSession4.Inventory.m_inventory.Clear();
					chestSession4.Touch();
					ChestSessions.Persist(chestSession4);
					Plugin.Log.LogDebug((object)$"Emptied chest {text} of {totalCount} stacks.");
					SendPage(sender, chestSession4, 0);
				}
				break;
			}
			case ChestMessage.StackAll:
			{
				List<ItemData> list = Deserialize(package.ReadByteArray());
				ChestSession session;
				bool flag = ChestSessions.TryGet(text, out session);
				ChestSession chestSession = ChestSessions.Acquire(text);
				if (chestSession == null)
				{
					break;
				}
				HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
				foreach (ItemData item3 in chestSession.Inventory.m_inventory)
				{
					hashSet.Add(StackKey(item3));
				}
				List<int> list2 = new List<int>();
				for (int i = 0; i < list.Count; i++)
				{
					ItemData val = list[i];
					if (val.m_shared.m_maxStackSize > 1 && hashSet.Contains(StackKey(val)))
					{
						chestSession.Add(val);
						list2.Add(i);
					}
				}
				Plugin.Log.LogDebug((object)($"Deposit into {text}: {list.Count} offered, {hashSet.Count} distinct types held, " + $"{list2.Count} kept."));
				if (list2.Count > 0)
				{
					ChestSessions.Persist(chestSession);
				}
				ZPackage val2 = new ZPackage();
				val2.Write(11);
				val2.Write(text);
				val2.Write(list2.Count);
				foreach (int item4 in list2)
				{
					val2.Write(item4);
				}
				_rpc.SendPackage(sender, val2);
				if (flag)
				{
					SendPage(sender, chestSession, -1);
				}
				else
				{
					ChestSessions.Release(text);
				}
				break;
			}
			case ChestMessage.Close:
				ChestSessions.Release(text);
				break;
			}
		}

		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 SendPage(long peer, ChestSession session, int scrollRow)
		{
			//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(Serialize(items));
			_rpc.SendPackage(peer, val);
		}

		private static string StackKey(ItemData item)
		{
			return $"{item.m_shared.m_name}|{item.m_quality}|{item.m_variant}";
		}

		private 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_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			Inventory val = new Inventory("page", (Sprite)null, 8, 6);
			val.m_inventory.AddRange(items);
			ZPackage val2 = new ZPackage();
			val.Save(val2);
			return val2.GetArray();
		}

		private static List<ItemData> Deserialize(byte[] bytes)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			Inventory val = new Inventory("page", (Sprite)null, 8, 4096);
			if (bytes != null && bytes.Length != 0 && !FastInventoryReader.TryLoad(val, bytes, out var _))
			{
				val.Load(new ZPackage(bytes));
			}
			return new List<ItemData>(val.m_inventory);
		}

		private static IEnumerator OnClientReceive(long sender, ZPackage package)
		{
			if (package == null || package.Size() == 0)
			{
				yield break;
			}
			ChestMessage chestMessage = (ChestMessage)package.ReadInt();
			string storeId = package.ReadString();
			switch (chestMessage)
			{
			case ChestMessage.PageResult:
			{
				long version = package.ReadLong();
				int total = package.ReadInt();
				int matches = package.ReadInt();
				float weight = package.ReadSingle();
				int scrollRow = package.ReadInt();
				List<ItemData> items = Deserialize(package.ReadByteArray());
				ChestView.ApplyPage(storeId, version, total, matches, weight, scrollRow, items);
				break;
			}
			case ChestMessage.Granted:
				ChestView.ApplyGranted(Deserialize(package.ReadByteArray()));
				break;
			case ChestMessage.Counts:
			{
				long version2 = package.ReadLong();
				int total2 = package.ReadInt();
				int matches2 = package.ReadInt();
				float weight2 = package.ReadSingle();
				ChestView.ApplyCounts(storeId, version2, total2, matches2, weight2);
				break;
			}
			case ChestMessage.Accepted:
				ChestView.ApplyAccepted();
				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(list);
				break;
			}
			}
		}
	}
}
namespace BottomlessChest.Gui
{
	internal sealed class ChestScrollbarBridge : MonoBehaviour
	{
		private const float MinimumHandle = 0.12f;

		private Scrollbar _bar;

		private ScrollRect _detachedFrom;

		private bool _writing;

		internal void Bind(InventoryGrid grid)
		{
			if ((Object)(object)grid == (Object)null)
			{
				return;
			}
			_bar = grid.m_scrollbar;
			if (!((Object)(object)_bar == (Object)null))
			{
				ScrollRect component = ((Component)grid).GetComponent<ScrollRect>();
				if ((Object)(object)component != (Object)null && (Object)(object)component.verticalScrollbar == (Object)(object)_bar)
				{
					_detachedFrom = component;
					component.verticalScrollbar = null;
				}
				((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;
			_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 ((Object)(object)_detachedFrom != (Object)null)
			{
				_detachedFrom.verticalScrollbar = _bar;
				_detachedFrom = null;
			}
		}
	}
	internal sealed class ChestScroller : MonoBehaviour
	{
		private void Update()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if (ChestView.IsOpen)
			{
				ChestView.Tick();
				float y = Input.mouseScrollDelta.y;
				if (!(Mathf.Abs(y) < 0.01f))
				{
					ChestView.Scroll((!(y > 0f)) ? 1 : (-1));
				}
			}
		}
	}
	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_0064: Unknown result type (might be due to invalid IL or missing references)
				//IL_005d: Unknown result type (might be due to invalid IL or missing references)
				RectTransform gridRoot = __instance.m_gridRoot;
				if ((Object)(object)gridRoot == (Object)null)
				{
					return;
				}
				int num = ((ChestView.IsRemote && __instance.m_inventory == ChestView.TargetInventory) ? __instance.m_inventory.m_inventory.Count : (-1));
				for (int i = 0; i < ((Transform)gridRoot).childCount; i++)
				{
					Image component = ((Component)((Transform)gridRoot).GetChild(i)).GetComponent<Image>();
					if (!((Object)(object)component == (Object)null))
					{
						((Graphic)component).color = ((i == num) ? DropTint : Color.white);
					}
				}
			}
		}

		private static readonly Color DropTint = new Color(0.65f, 0.85f, 0.55f, 0.55f);
	}
	internal static class ScrollbarProbe
	{
		[HarmonyPatch(typeof(InventoryGui), "Show")]
		private static class Patch
		{
			private static void Postfix(InventoryGui __instance, Container container)
			{
				//IL_0088: Unknown result type (might be due to invalid IL or missing references)
				if (_reported || (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.LogInfo((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();
				}
			}
		}

		[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 static GameObject _field;

		private static GameObject _hiddenTitle;

		private static int _escapeFrame = -1000;

		private const int EscapeGraceFrames = 3;

		private static bool _hideCancelled;

		private static InputField _input;

		private static Text _status;

		internal static void NoteEscapePressed()
		{
			_escapeFrame = Time.frameCount;
		}

		private static void Build(InventoryGui gui)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: 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_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_023e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Unknown result type (might be due to invalid IL or missing references)
			//IL_028c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a6: 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)
			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);
				_field = 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 ((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 = _field.GetComponent<InputField>() ?? _field.GetComponentInChildren<InputField>();
				if ((Object)(object)_input == (Object)null)
				{
					Plugin.Log.LogError((object)"Search field was created but carries no InputField.");
					DestroyWidgets();
					return;
				}
				((UnityEvent<string>)(object)_input.onValueChanged).AddListener((UnityAction<string>)OnChanged);
				_field.AddComponent<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>();
				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);
				UpdateStatus();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Could not build the chest search box: {arg}");
				DestroyWidgets();
			}
		}

		internal static bool TryConsumeEscape()
		{
			if ((Object)(object)_input == (Object)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.ActivateInputField();
			((Selectable)_input).Select();
			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 = 6;
				_status.text = ((totalRows > num) ? $"{text}   -   rows {ChestView.ScrollRow + 1}-{Mathf.Min(ChestView.ScrollRow + num, totalRows)} of {totalRows}" : text);
			}
		}

		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;
			}
		}
	}
	[DefaultExecutionOrder(-1000)]
	internal sealed class SearchFocusGuard : MonoBehaviour
	{
		private InputField _input;

		private bool _blocking;

		private int _focusAttemptsLeft;

		private bool _wasFocused;

		private void Awake()
		{
			_input = ((Component)this).GetComponent<InputField>();
		}

		internal void TakeFocus()
		{
			_focusAttemptsLeft = 300;
		}

		private static bool UseKeyHeld()
		{
			try
			{
				if (ZInput.instance != null && ZInput.GetButton("Use"))
				{
					return true;
				}
			}
			catch
			{
			}
			return Input.GetKey((KeyCode)101);
		}

		private void Update()
		{
			if ((Object)(object)_input == (Object)null)
			{
				Release();
				return;
			}
			if (_focusAttemptsLeft > 0)
			{
				_focusAttemptsLeft--;
				if (!UseKeyHeld())
				{
					if (!_input.isFocused)
					{
						((Selectable)_input).Select();
						_input.ActivateInputField();
					}
					else
					{
						_focusAttemptsLeft = 0;
					}
				}
			}
			if (Input.GetKeyDown((KeyCode)27))
			{
				SearchBox.NoteEscapePressed();
			}
			if ((_input.isFocused || _wasFocused) && Input.GetKeyDown((KeyCode)27))
			{
				_wasFocused = false;
				HandleEscape();
				return;
			}
			_wasFocused = _input.isFocused;
			if (_input.isFocused != _blocking)
			{
				_blocking = _input.isFocused;
				GUIManager.BlockInput(_blocking);
				Plugin.Log.LogDebug((object)$"Search box focus: {_blocking}");
			}
		}

		private void HandleEscape()
		{
			if (!string.IsNullOrEmpty(_input.text))
			{
				_input.text = string.Empty;
				return;
			}
			Release();
			InventoryGui instance = InventoryGui.instance;
			if (instance != null)
			{
				instance.Hide();
			}
		}

		private void OnDisable()
		{
			Release();
		}

		private void OnDestroy()
		{
			Release();
		}

		private void Release()
		{
			if (_blocking)
			{
				_blocking = false;
				GUIManager.BlockInput(false);
			}
		}
	}
}
namespace BottomlessChest.Filter
{
	internal static class ChestView
	{
		[HarmonyPatch(typeof(InventoryGrid), "UpdateGui")]
		private static class GridRenderScope
		{
			private static Inventory _swappedOut;

			private static void Prefix(InventoryGrid __instance)
			{
				_swappedOut = null;
				if (!_remote && _target != null && __instance.m_inventory == _target)
				{
					_swappedOut = __instance.m_inventory;
					__instance.m_inventory = BuildView();
				}
			}

			private static void Finalizer(InventoryGrid __instance)
			{
				if (_swappedOut != null)
				{
					__instance.m_inventory = _swappedOut;
					_swappedOut = null;
				}
			}
		}

		private static Inventory _target;

		private static Inventory _view;

		private static string _query = string.Empty;

		private static int _matchCount;

		private static int _scrollRow;

		private static int _windowStart;

		private static int _windowEnd;

		private static BottomlessContainer _owner;

		private static bool _remote;

		private static long _version;

		private static int _remoteTotal;

		private static float _remoteWeight;

		private static string _remoteStoreId;

		private static bool _awaitingPage;

		private static ItemData _pendingPut;

		private static float _pendingPutSentAt;

		private static readonly Dictionary<string, int> KnownTotals = new Dictionary<string, int>(StringComparer.Ordinal);

		private static readonly List<ItemData> Matched = new List<ItemData>();

		private static readonly List<ItemData> Rest = new List<ItemData>();

		internal const int Width = 8;

		internal const int VisibleRows = 6;

		private static List<ItemData> _offered;

		private static float _offerSentAt;

		private static float _lastPageRequestAt;

		private static bool _pageRequestPending;

		private const float PageRequestInterval = 0.08f;

		private const float OfferTimeoutSeconds = 5f;

		internal static float RemoteWeight => _remoteWeight;

		internal static int MatchCount => _matchCount;

		internal static int TotalCount
		{
			get
			{
				if (!_remote)
				{
					return _target?.m_inventory.Count ?? 0;
				}
				return _remoteTotal;
			}
		}

		internal static bool IsRemote => _remote;

		internal static Inventory TargetInventory => _target;

		internal static long Version => _version;

		internal static int ScrollRow => _scrollRow;

		internal static bool IsFiltering
		{
			get
			{
				if (_target != null)
				{
					return !string.IsNullOrWhiteSpace(_query);
				}
				return false;
			}
		}

		internal static bool IsOpen => _target != null;

		internal static int TotalRows => GridPacker.RowsNeeded(_matchCount, 8);

		internal static int WidthForSession => 8;

		internal static int WindowSlots => 48;

		internal static int PageSlots => 47;

		internal static bool AwaitingContents
		{
			get
			{
				if (!_remote)
				{
					if ((Object)(object)_owner != (Object)null)
					{
						return _owner.AwaitingContents;
					}
					return false;
				}
				return _awaitingPage;
			}
		}

		internal static int MaxScroll => MaxScrollRow();

		private static int RowsCarried
		{
			get
			{
				if (!_remote)
				{
					return 6;
				}
				return 5;
			}
		}

		internal static int RowsOnScreen => RowsCarried;

		internal static bool PageRequestPending => _pageRequestPending;

		internal static int? KnownTotalFor(string storeId)
		{
			if (!string.IsNullOrEmpty(storeId) && KnownTotals.TryGetValue(storeId, out var value))
			{
				return value;
			}
			return null;
		}

		internal static List<ItemData> MatchingItems()
		{
			List<ItemData> list = new List<ItemData>();
			if (_target == null)
			{
				return list;
			}
			List<ItemData> inventory = _target.m_inventory;
			int num = Mathf.Min(_matchCount, inventory.Count);
			for (int i = 0; i < num; i++)
			{
				list.Add(inventory[i]);
			}
			return list;
		}

		internal static void Begin(Inventory inventory, BottomlessContainer owner)
		{
			_owner = owner;
			_target = inventory;
			_query = string.Empty;
			_scrollRow = 0;
			_remote = (Object)(object)owner != (Object)null && !SidecarStore.IsServerAuthority;
			_version = 0L;
			_remoteTotal = 0;
			_matchCount = 0;
			_remoteStoreId = owner?.CurrentStoreId;
			if (_remote)
			{
				_target.m_inventory.Clear();
				_awaitingPage = true;
				ChestRpc.Open(_remoteStoreId);
			}
			else
			{
				Reapply();
			}
		}

		internal static void End()
		{
			if (_remote && !string.IsNullOrEmpty(_remoteStoreId))
			{
				ChestRpc.Close(_remoteStoreId);
			}
			bool remote = _remote;
			Inventory target = _target;
			_remote = false;
			_awaitingPage = false;
			_remoteStoreId = null;
			_owner = null;
			_target = null;
			_view = null;
			_query = string.Empty;
			_matchCount = 0;
			_scrollRow = 0;
			if (!remote && target != null && !InventoryCapacity.LayoutIsUsable(target, VisibleRowsFor(target)))
			{
				InventoryCapacity.Repack(target);
			}
		}

		internal static void SetQuery(string query)
		{
			if (_remote)
			{
				_query = query ?? string.Empty;
				_scrollRow = 0;
				_awaitingPage = true;
				ChestRpc.RequestPage(_remoteStoreId, _query, 0);
			}
			else
			{
				Stopwatch stopwatch = Stopwatch.StartNew();
				_query = query ?? string.Empty;
				_scrollRow = 0;
				Reapply();
				_ = stopwatch.ElapsedMilliseconds;
				Refresh();
			}
		}

		internal static bool Scroll(int rows)
		{
			if (rows != 0)
			{
				return ScrollTo(_scrollRow + rows);
			}
			return false;
		}

		internal static bool ScrollTo(int row)
		{
			if (_target == null)
			{
				return false;
			}
			int num = Mathf.Clamp(row, 0, MaxScrollRow());
			if (num == _scrollRow)
			{
				return false;
			}
			if (_remote)
			{
				_scrollRow = num;
				RequestPageThrottled();
				return true;
			}
			_scrollRow = num;
			Reapply();
			Refresh();
			return true;
		}

		private static void RequestPageThrottled()
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup - _lastPageRequestAt < 0.08f)
			{
				_pageRequestPending = true;
				return;
			}
			_pageRequestPending = false;
			_lastPageRequestAt = realtimeSinceStartup;
			_awaitingPage = true;
			ChestRpc.RequestPage(_remoteStoreId, _query, _scrollRow);
		}

		internal static void Tick()
		{
			if (_remote && _pageRequestPending)
			{
				RequestPageThrottled();
			}
		}

		internal static void OnInventoryChanged(Inventory inventory)
		{
			if (!_remote && _target != null && _target == inventory)
			{
				Reapply();
			}
		}

		private static int VisibleRowsFor(Inventory inventory)
		{
			return 6;
		}

		private static int MaxScrollRow()
		{
			int num = TotalRows - RowsCarried;
			if (num >= 0)
			{
				return num;
			}
			return 0;
		}

		private static void Reapply()
		{
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			if (_target == null || _remote)
			{
				return;
			}
			List<ItemData> inventory = _target.m_inventory;
			if (string.IsNullOrWhiteSpace(_query))
			{
				_matchCount = inventory.Count;
			}
			else
			{
				ItemQuery val = ItemQuery.Parse(_query);
				List<ItemData> matched = Matched;
				List<ItemData> rest = Rest;
				matched.Clear();
				rest.Clear();
				foreach (ItemData item in inventory)
				{
					if (val.Matches((IStorableItem)(object)new ItemAdapter(item)))
					{
						matched.Add(item);
					}
					else
					{
						rest.Add(item);
					}
				}
				matched.Sort((ItemData left, ItemData right) => ItemOrdering.ByName.Compare((IStorableItem)(object)new ItemAdapter(left), (IStorableItem)(object)new ItemAdapter(right)));
				_matchCount = matched.Count;
				inventory.Clear();
				inventory.AddRange(matched);
				inventory.AddRange(rest);
			}
			_scrollRow = Mathf.Clamp(_scrollRow, 0, MaxScrollRow());
			if (string.IsNullOrWhiteSpace(_query) && _scrollRow <= 0 && InventoryCapacity.LayoutIsUsable(_target, 6))
			{
				_windowStart = 0;
				_windowEnd = inventory.Count;
				InventoryCapacity.Apply(_target);
				return;
			}
			_windowStart = _scrollRow * 8;
			_windowEnd = Mathf.Min(_matchCount, _windowStart + WindowSlots);
			int num = 0;
			for (int num2 = 0; num2 < inventory.Count; num2++)
			{
				GridPos val2 = ((num2 < _windowStart || num2 >= _windowEnd) ? GridPacker.PositionOf(WindowSlots + num++, 8) : GridPacker.PositionOf(num2 - _windowStart, 8));
				inventory[num2].m_gridPos = new Vector2i(((GridPos)(ref val2)).X, ((GridPos)(ref val2)).Y);
			}
			InventoryCapacity.Apply(_target);
		}

		private static void Refresh()
		{
			InventoryGui instance = InventoryGui.instance;
			if ((Object)(object)instance != (Object)null && (Object)(object)instance.m_currentContainer != (Object)null)
			{
				instance.m_containerGrid.UpdateInventory(instance.m_currentContainer.GetInventory(), (Player)null, instance.m_dragItem);
			}
		}

		internal static void ApplyPage(string storeId, long version, int total, int matches, float weight, int scrollRow, List<ItemData> items)
		{
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: 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)
			if (_remote && _target != null && !(storeId != _remoteStoreId))
			{
				_version = version;
				_remoteTotal = total;
				_matchCount = matches;
				_remoteWeight = weight;
				KnownTotals[storeId] = total;
				_scrollRow = scrollRow;
				_awaitingPage = false;
				Plugin.Log.LogDebug((object)($"Page: {items.Count} items at row {scrollRow}, {matches}/{total} match, " + $"max row {MaxScrollRow()}, v{version}."));
				List<ItemData> inventory = _target.m_inventory;
				inventory.Clear();
				inventory.AddRange(items);
				for (int i = 0; i < inventory.Count; i++)
				{
					GridPos val = GridPacker.PositionOf(i, 8);
					inventory[i].m_gridPos = new Vector2i(((GridPos)(ref val)).X, ((GridPos)(ref val)).Y);
				}
				_target.m_width = 8;
				_target.m_height = 6;
				Refresh();
			}
		}

		internal static void ApplyCounts(string storeId, long version, int total, int matches, float weight)
		{
			if (_remote && !(storeId != _remoteStoreId))
			{
				_version = version;
				_remoteTotal = total;
				_matchCount = matches;
				_remoteWeight = weight;
				_awaitingPage = false;
				KnownTotals[storeId] = total;
				Refresh();
			}
		}

		internal static void ApplyGranted(List<ItemData> items)
		{
			//IL_0041: 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)
			Player localPlayer = Player.m_localPlayer;
			Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null);
			if (val == null)
			{
				return;
			}
			foreach (ItemData item in items)
			{
				if (!val.AddItem(item))
				{
					ItemDrop.DropItem(item, item.m_stack, ((Component)Player.m_localPlayer).transform.position, Quaternion.identity);
				}
			}
		}

		internal static void ApplyAccepted()
		{
			if (_pendingPut == null)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != null)
			{
				Inventory inventory = ((Humanoid)localPlayer).GetInventory();
				if (inventory != null)
				{
					inventory.RemoveItem(_pendingPut);
				}
			}
			_pendingPut = null;
		}

		internal static bool RequestStackAll(string storeId)
		{
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Expected O, but got Unknown
			if (string.IsNullOrEmpty(storeId))
			{
				return false;
			}
			Player localPlayer = Player.m_localPlayer;
			Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null);
			if (val == null)
			{
				return false;
			}
			if (_offered != null && Time.realtimeSinceStartup - _offerSentAt < 5f)
			{
				return true;
			}
			_offered = new List<ItemData>();
			foreach (ItemData item in val.m_inventory)
			{
				if (item.m_shared.m_maxStackSize > 1 && !item.m_equipped)
				{
					_offered.Add(item);
				}
			}
			Plugin.Log.LogDebug((object)$"Offering {_offered.Count} stackable item(s) to chest {storeId}.");
			if (_offered.Count == 0)
			{
				return true;
			}
			Inventory val2 = new Inventory("offer", (Sprite)null, 8, 64);
			val2.m_inventory.AddRange(_offered);
			ZPackage val3 = new ZPackage();
			val2.Save(val3);
			_offerSentAt = Time.realtimeSinceStartup;
			ChestRpc.StackAll(storeId, val3.GetArray());
			return true;
		}

		internal static void ApplyStacked(List<int> keptIndices)
		{
			Player localPlayer = Player.m_localPlayer;
			Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null);
			if (val == null || _offered == null)
			{
				return;
			}
			foreach (int keptIndex in keptIndices)
			{
				if (keptIndex >= 0 && keptIndex < _offered.Count)
				{
					val.RemoveItem(_offered[keptIndex]);
				}
			}
			if (keptIndices.Count > 0 && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				((Character)Player.m_localPlayer).Message((MessageType)2, $"$msg_added {keptIndices.Count}", 0, (Sprite)null);
			}
			_offered = null;
		}

		internal static List<int> TrimToCapacity(IReadOnlyList<int> pageSlots)
		{
			Player localPlayer = Player.m_localPlayer;
			int? obj;
			if (localPlayer == null)
			{
				obj = null;
			}
			else
			{
				Inventory inventory = ((Humanoid)localPlayer).GetInventory();
				obj = ((inventory != null) ? new int?(inventory.GetEmptySlots()) : ((int?)null));
			}
			int? num = obj;
			int valueOrDefault = num.GetValueOrDefault();
			List<int> list = new List<int>(Math.Min(valueOrDefault, pageSlots.Count));
			for (int i = 0; i < pageSlots.Count; i++)
			{
				if (list.Count >= valueOrDefault)
				{
					break;
				}
				list.Add(pageSlots[i]);
			}
			return list;
		}

		internal static void RequestTake(IReadOnlyList<int> pageSlots)
		{
			if (!_remote || pageSlots.Count == 0)
			{
				return;
			}
			List<int> list = new List<int>(pageSlots.Count);
			foreach (int pageSlot in pageSlots)
			{
				list.Add(_scrollRow * 8 + pageSlot);
			}
			RemoveSlotsLocally(pageSlots);
			Plugin.Log.LogDebug((object)($"Requesting {list.Count} item(s) from chest {_remoteStoreId} at v{_version}, " + $"row {_scrollRow} (first index {((list.Count > 0) ? list[0] : (-1))})."));
			ChestRpc.Take(_remoteStoreId, _version, list);
		}

		internal static bool RequestPut(ItemData item)
		{
			//IL_002f: 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_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			if (!_remote || item == null)
			{
				return false;
			}
			if (_pendingPut != null && Time.realtimeSinceStartup - _pendingPutSentAt < 5f)
			{
				return false;
			}
			Inventory val = new Inventory("put", (Sprite)null, 8, 1)
			{
				m_inventory = { item }
			};
			ZPackage val2 = new ZPackage();
			val.Save(val2);
			_pendingPut = item;
			_pendingPutSentAt = Time.realtimeSinceStartup;
			ChestRpc.Put(_remoteStoreId, val2.GetArray());
			return true;
		}

		private static void RemoveSlotsLocally(IReadOnlyList<int> pageSlots)
		{
			if (_target == null)
			{
				return;
			}
			HashSet<ItemData> doomed = new HashSet<ItemData>();
			foreach (int pageSlot in pageSlots)
			{
				if (pageSlot >= 0 && pageSlot < _target.m_inventory.Count)
				{
					doomed.Add(_target.m_inventory[pageSlot]);
				}
			}
			_target.m_inventory.RemoveAll((ItemData item) => doomed.Contains(item));
		}

		internal static bool IsRemotePage(Inventory inventory)
		{
			if (_remote && _target != null)
			{
				return _target == inventory;
			}
			return false;
		}

		internal static int PageSlotOf(ItemData item)
		{
			if (_target != null)
			{
				return _target.m_inventory.IndexOf(item);
			}
			return -1;
		}

		private static Inventory BuildView()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			if (_view == null)
			{
				_view = new Inventory(_target.m_name, _target.m_bkg, 8, 6);
			}
			_view.m_width = 8;
			_view.m_height = 6;
			List<ItemData> inventory = _view.m_inventory;
			inventory.Clear();
			List<ItemData> inventory2 = _target.m_inventory;
			int num = Mathf.Min(_windowEnd, inventory2.Count);
			for (int i = _windowStart; i < num; i++)
			{
				inventory.Add(inventory2[i]);
			}
			return _view;
		}
	}
	internal readonly struct ItemAdapter : IStorableItem
	{
		private static readonly Dictionary<string, string> SearchKeys = new Dictionary<string, string>(512, StringComparer.Ordinal);

		private readonly ItemData _item;

		public string ItemId
		{
			get
			{
				if (!((Object)(object)_item.m_dropPrefab != (Object)null))
				{
					return _item.m_shared.m_name;
				}
				return ((Object)_item.m_dropPrefab).name;
			}
		}

		public string DisplayName => Localize(_item.m_shared.m_name);

		public string SearchKey
		{
			get
			{
				string name = _item.m_shared.m_name;
				if (name == null)
				{
					return string.Empty;
				}
				if (SearchKeys.TryGetValue(name, out var value))
				{
					return value;
				}
				value = TextKey.Of(Localize(name));
				SearchKeys[name] = value;
				return value;
			}
		}

		public ItemKind Kind => Classify(_item.m_shared.m_itemType);

		public int Stack => _item.m_stack;

		public int MaxStackSize => _item.m_shared.m_maxStackSize;

		public int Quality => _item.m_quality;

		public int Variant => _item.m_variant;

		public string CustomData => null;

		internal static void ClearCache()
		{
			SearchKeys.Clear();
		}

		internal ItemAdapter(ItemData item)
		{
			_item = item;
		}

		private static string Localize(string token)
		{
			if (string.IsNullOrEmpty(token))
			{
				return string.Empty;
			}
			Localization instance = Localization.instance;
			if (instance == null)
			{
				return token.TrimStart(new char[1] { '$' });
			}
			return instance.Localize(token);
		}

		private static ItemKind Classify(ItemType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected I4, but got Unknown
			switch (type - 1)
			{
			case 0:
				return (ItemKind)1;
			case 1:
			case 20:
				return (ItemKind)2;
			case 2:
			case 3:
			case 13:
			case 14:
			case 19:
			case 21:
				return (ItemKind)3;
			case 4:
			case 5:
			case 6:
			case 10:
			case 11:
			case 16:
				return (ItemKind)4;
			case 8:
			case 22:
				return (ItemKind)5;
			case 18:
				return (ItemKind)6;
			case 12:
				return (ItemKind)7;
			default:
				return (ItemKind)8;
			}
		}
	}
}
namespace BottomlessChest.Core
{
	[DisallowMultipleComponent]
	public class BottomlessContainer : MonoBehaviour
	{
		internal const string StoreIdKey = "BottomlessChest_id";

		private static readonly Dictionary<Container, BottomlessContainer> Registry = new Dictionary<Container, BottomlessContainer>();

		private Container _container;

		private ZNetView _nview;

		private bool _contentsLoaded;

		private bool _warnedAboutUnloadedSave;

		private bool _loadWasPartial;

		internal static IEnumerable<BottomlessContainer> Loaded => Registry.Values;

		internal Vector3 Position => ((Component)this).transform.position;

		internal Inventory Inventory => _container?.m_inventory;

		internal bool AwaitingContents
		{
			get
			{
				if (!_contentsLoaded)
				{
					return SidecarStore.IsServerAuthority;
				}
				return false;
			}
		}

		internal string CurrentStoreId
		{
			get
			{
				ZDO obj = (((Object)(object)_nview != (Object)null && _nview.IsValid()) ? _nview.GetZDO() : null);
				if (obj == null)
				{
					return null;
				}
				return obj.GetString("BottomlessChest_id", string.Empty);
			}
		}

		internal static bool TryResolve(Container container, out BottomlessContainer bottomless)
		{
			return Registry.TryGetValue(container, out bottomless);
		}

		internal static bool TryResolveByStoreId(string storeId, out BottomlessContainer found)
		{
			foreach (BottomlessContainer value in Registry.Values)
			{
				if (value.CurrentStoreId == storeId)
				{
					found = value;
					return true;
				}
			}
			found = null;
			return false;
		}

		internal static void FlushAllPending()
		{
		}

		internal void RefreshFromServer()
		{
		}

		internal void NotifyFilled()
		{
			if (!((Object)(object)_container == (Object)null))
			{
				InventoryCapacity.Repack(_container.m_inventory);
				InventoryCapacity.Apply(_container.m_inventory);
				SaveToStore();
			}
		}

		internal bool Rebind(string storeId)
		{
			ZDO val = (((Object)(object)_nview != (Object)null && _nview.IsValid()) ? _nview.GetZDO() : null);
			if (val == null || !_nview.IsOwner())
			{
				return false;
			}
			val.Set("BottomlessChest_id", storeId);
			_container.m_loading = true;
			_container.m_inventory.RemoveAll();
			_container.m_loading = false;
			_contentsLoaded = false;
			_warnedAboutUnloadedSave = false;
			return LoadFromStore();
		}

		internal void EnsureRegistered()
		{
			if ((Object)(object)_container != (Object)null)
			{
				InventoryCapacity.Register(_container.m_inventory);
			}
		}

		private void Awake()
		{
			_container = ((Component)this).GetComponent<Container>();
			_nview = ((Component)this).GetComponent<ZNetView>();
			if ((Object)(object)_container == (Object)null)
			{
				Plugin.Log.LogError((object)("BottomlessContainer on '" + ((Object)this).name + "' has no Container. Disabling."));
				((Behaviour)this).enabled = false;
			}
			else
			{
				Registry[_container] = this;
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)_container != (Object)null)
			{
				InventoryCapacity.Forget(_container.m_inventory);
				Registry.Remove(_container);
			}
		}

		internal string GetOrCreateStoreId()
		{
			ZDO val = (((Object)(object)_nview != (Object)null && _nview.IsValid()) ? _nview.GetZDO() : null);
			if (val == null)
			{
				return null;
			}
			string text = val.GetString("BottomlessChest_id", string.Empty);
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			if (!_nview.IsOwner())
			{
				return null;
			}
			string text2 = Guid.NewGuid().ToString("N");
			val.Set("BottomlessChest_id", text2);
			Plugin.Log.LogInfo((object)("Minted store id " + text2 + " for a new bottomless chest."));
			return text2;
		}

		private bool AdoptLegacyZdoContents(string storeId)
		{
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Expected O, but got Unknown
			ZDO obj = (((Object)(object)_nview != (Object)null && _nview.IsValid()) ? _nview.GetZDO() : null);
			string text = ((obj != null) ? obj.GetString(ZDOVars.s_items, string.Empty) : null);
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			try
			{
				_container.m_loading = true;
				InventoryCapacity.Suspended = true;
				LoadIntoInventory(Convert.FromBase64String(text));
				InventoryCapacity.Repack(_container.m_inventory);
				InventoryCapacity.Apply(_container.m_inventory);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Could not adopt legacy contents for chest {storeId}: {arg}");
				return false;
			}
			finally
			{
				InventoryCapacity.Suspended = false;
				_container.m_loading = false;
			}
			int num = _container.m_inventory.NrOfItems();
			Plugin.Log.LogInfo((object)$"Adopted {num} item stack(s) from the ZDO into chest store {storeId}.");
			ZPackage val = new ZPackage();
			_container.m_inventory.Save(val);
			SidecarStore.Instance.Put(storeId, val.GetArray());
			return true;
		}

		internal void SaveToStore()
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			string orCreateStoreId = GetOrCreateStoreId();
			if (orCreateStoreId == null)
			{
				return;
			}
			if (_loadWasPartial)
			{
				if (!_warnedAboutUnloadedSave)
				{
					_warnedAboutUnloadedSave = true;
					Plugin.Log.LogError((object)("Refusing to save chest " + orCreateStoreId + ": it only loaded partially, so saving would overwrite the full contents with this truncated copy."));
				}
			}
			else if (!_contentsLoaded)
			{
				if (!_warnedAboutUnloadedSave)
				{
					_warnedAboutUnloadedSave = true;
					Plugin.Log.LogWarning((object)("Refusing to save chest " + orCreateStoreId + ": its contents were never loaded. The stored copy is intact and left untouched."));
				}
			}
			else if (SidecarStore.IsServerAuthority)
			{
				ZPackage val = new ZPackage();
				_container.m_inventory.Save(val);
				SidecarStore.Instance.Put(orCreateStoreId, val.GetArray());
			}
		}

		private static int PeekItemCount(byte[] contents)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if (contents == null || contents.Length < 8)
			{
				return 0;
			}
			try
			{
				ZPackage val = new ZPackage(contents);
				val.ReadInt();
				return val.ReadInt();
			}
			catch
			{
				return 0;
			}
		}

		private void LoadIntoInventory(byte[] contents)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			int expected = PeekItemCount(contents);
			_container.m_inventory.RemoveAll();
			InventoryCapacity.ApplyFor(_container.m_inventory, expected);
			if (!FastInventoryReader.TryLoad(_container.m_inventory, contents, out expected))
			{
				_container.m_inventory.Load(new ZPackage(contents));
			}
			int count = _container.m_inventory.m_inventory.Count;
			_loadWasPartial = count != expected;
			if (_loadWasPartial)
			{
				Plugin.Log.LogError((object)($"Loaded {count} of {expected} stacks - {expected - count} were dropped " + $"because the grid was too small ({_container.m_inventory.m_width}x{_container.m_inventory.m_height})."));
			}
		}

		internal void ApplyRemoteContents(byte[] contents)
		{
			if ((Object)(object)_container == (Object)null)
			{
				return;
			}
			Stopwatch stopwatch = Stopwatch.StartNew();
			long num = 0L;
			long num2 = 0L;
			try
			{
				_container.m_loading = true;
				InventoryCapacity.Suspended = true;
				if (contents != null && contents.Length != 0)
				{
					LoadIntoInventory(contents);
				}
				else
				{
					_container.m_inventory.RemoveAll();
				}
				num = stopwatch.ElapsedMilliseconds;
				InventoryCapacity.Repack(_container.m_inventory);
				InventoryCapacity.Apply(_container.m_inventory);
				num2 = stopwatch.ElapsedMilliseconds;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Could not apply contents received for a chest: {arg}");
				return;
			}
			finally
			{
				InventoryCapacity.Suspended = false;
				_container.m_loading = false;
			}
			int count = _container.m_inventory.m_inventory.Count;
			if (count >= 1000)
			{
				Plugin.Log.LogDebug((object)($"Applied {count} stacks in {stopwatch.ElapsedMilliseconds}ms " + $"(deserialise {num}ms, layout {num2 - num}ms, rest {stopwatch.ElapsedMilliseconds - num2}ms)."));
			}
			_contentsLoaded = true;
			if (!_loadWasPartial)
			{
				_warnedAboutUnloadedSave = false;
			}
		}

		internal bool LoadFromStore()
		{
			if (_contentsLoaded)
			{
				return false;
			}
			string orCreateStoreId = GetOrCreateStoreId();
			if (orCreateStoreId == null)
			{
				return false;
			}
			if (!SidecarStore.IsServerAuthority)
			{
				return false;
			}
			if (!SidecarStore.Instance.IsReady)
			{
				return false;
			}
			_contentsLoaded = true;
			if (!SidecarStore.Instance.TryGet(orCreateStoreId, out var contents) || contents == null || contents.Length == 0)
			{
				return AdoptLegacyZdoContents(orCreateStoreId);
			}
			try
			{
				_container.m_loading = true;
				InventoryCapacity.Suspended = true;
				LoadIntoInventory(contents);
				InventoryCapacity.Repack(_container.m_inventory);
				InventoryCapacity.Apply(_container.m_inventory);
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Could not load contents for chest {orCreateStoreId}: {arg}");
				return false;
			}
			finally
			{
				_container.m_loading = false;
			}
		}
	}
	internal sealed class ChestSession
	{
		private readonly List<ItemData> _ordered = new List<ItemData>();

		private string _query = string.Empty;

		private bool _orderDirty = true;

		private float _totalWeight;

		private bool _weightDirty = true;

		internal string StoreId { get; }

		internal Inventory Inventory { get; }

		internal long Version { get; private set; }

		internal int LastScrollRow { get; private set; }

		internal DateTime LastUsedUtc { get; private set; } = DateTime.UtcNow;

		internal int TotalCount => Inventory.m_inventory.Count;

		internal int MatchCount
		{
			get
			{
				EnsureOrder();
				return _ordered.Count;
			}
		}

		internal float TotalWeight
		{
			get
			{
				if (_weightDirty)
				{
					_weightDirty = false;
					_totalWeight = 0f;
					foreach (ItemData item in Inventory.m_inventory)
					{
						_totalWeight += item.GetWeight(-1);
					}
				}
				return _totalWeight;
			}
		}

		internal ChestSession(string storeId, Inventory inventory)
		{
			StoreId = storeId;
			Inventory = inventory;
		}

		internal void MarkUsed()
		{
			LastUsedUtc = DateTime.UtcNow;
		}

		internal void SetQuery(string query)
		{
			string text = query ?? string.Empty;
			if (!(text == _query))
			{
				_query = text;
				_orderDirty = true;
			}
		}

		internal void Touch()
		{
			Version++;
			_orderDirty = true;
			_weightDirty = true;
		}

		internal List<ItemData> Page(int scrollRow, int count)
		{
			EnsureOrder();
			int num = Mathf.Max(0, GridPacker.RowsNeeded(_ordered.Count, 8) - 5);
			LastScrollRow = Mathf.Clamp(scrollRow, 0, num);
			List<ItemData> list = new List<ItemData>(count);
			for (int i = LastScrollRow * 8; i < _ordered.Count; i++)
			{
				if (list.Count >= count)
				{
					break;
				}
				list.Add(_ordered[i]);
			}
			return list;
		}

		internal List<ItemData> Take(IEnumerable<int> indices)
		{
			EnsureOrder();
			List<ItemData> list = new List<ItemData>();
			foreach (int index in indices)
			{
				if (index >= 0 && index < _ordered.Count)
				{
					ItemData item = _ordered[index];
					if (Inventory.m_inventory.Remove(item))
					{
						list.Add(item);
					}
				}
			}
			if (list.Count > 0)
			{
				Touch();
			}
			return list;
		}

		internal void Add(ItemData item)
		{
			if (item != null)
			{
				Inventory.m_inventory.Add(item);
				Touch();
			}
		}

		private void EnsureOrder()
		{
			if (!_orderDirty)
			{
				return;
			}
			_orderDirty = false;
			_ordered.Clear();
			List<ItemData> inventory = Inventory.m_inventory;
			if (string.IsNullOrWhiteSpace(_query))
			{
				_ordered.AddRange(inventory);
				return;
			}
			ItemQuery val = ItemQuery.Parse(_query);
			foreach (ItemData item in inventory)
			{
				if (val.Matches((IStorableItem)(object)new ItemAdapter(item)))
				{
					_ordered.Add(item);
				}
			}
			_ordered.Sort((ItemData left, ItemData right) => ItemOrdering.ByName.Compare((IStorableItem)(object)new ItemAdapter(left), (IStorableItem)(object)new ItemAdapter(right)));
		}
	}
	internal static class ChestSessions
	{
		private static readonly Dictionary<string, ChestSession> Open = new Dictionary<string, ChestSession>(StringComparer.Ordinal);

		internal static ChestSession Acquire(string storeId)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			if (string.IsNullOrEmpty(storeId))
			{
				return null;
			}
			if (Open.TryGetValue(storeId, out var value))
			{
				value.MarkUsed();
				return value;
			}
			if (!SidecarStore.Instance.IsReady)
			{
				return null;
			}
			Inventory val = new Inventory("bottomless", (Sprite)null, ChestView.WidthForSession, 1);
			if (SidecarStore.Instance.TryGet(storeId, out var contents) && contents != null && contents.Length != 0 && !FastInventoryReader.TryLoad(val, contents, out var _))
			{
				val.Load(new ZPackage(contents));
			}
			ChestSession chestSession = new ChestSession(storeId, val);
			Open[storeId] = chestSession;
			Plugin.Log.LogDebug((object)$"Opened session for chest {storeId} with {chestSession.TotalCount} stacks.");
			return chestSession;
		}

		internal static bool TryGet(string storeId, out ChestSession session)
		{
			return Open.TryGetValue(storeId ?? string.Empty, out session);
		}

		internal static void Release(string storeId)
		{
			if (Open.TryGetValue(storeId ?? string.Empty, out var value))
			{
				Persist(value);
				Open.Remove(storeId);
			}
		}

		internal static void Persist(ChestSession session)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Expected O, but got Unknown
			if (session != null)
			{
				ZPackage val = new ZPackage();
				session.Inventory.Save(val);
				SidecarStore.Instance.Put(session.StoreId, val.GetArray());
			}
		}

		internal static void ReleaseIdle(TimeSpan olderThan)
		{
			DateTime dateTime = DateTime.UtcNow - olderThan;
			List<string> list = null;
			foreach (KeyValuePair<string, ChestSession> item in Open)
			{
				if (item.Value.LastUsedUtc < dateTime)
				{
					list = list ?? new List<string>();
					list.Add(item.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (string item2 in list)
			{
				Plugin.Log.LogInfo((object)("Releasing idle chest session " + item2 + "."));
				Release(item2);
			}
		}

		internal static void PersistAll()
		{
			foreach (ChestSession value in Open.Values)
			{
				Persist(value);
			}
		}

		internal static void Clear()
		{
			Open.Clear();
		}
	}
	internal static class ContainerPersistencePatches
	{
		[HarmonyPatch(typeof(Container), "Save")]
		private static class SavePatch
		{
			private static bool Prefix(Container __instance)
			{
				if (Plugin.Degraded || !BottomlessContainer.TryResolve(__instance, out var bottomless))
				{
					return true;
				}
				bottomless.EnsureRegistered();
				bottomless.SaveToStore();
				return false;
			}
		}

		[HarmonyPatch(typeof(Container), "AddDefaultItems")]
		private static class NoDefaultItems
		{
			private static bool Prefix(Container __instance)
			{
				BottomlessContainer bottomless;
				if (!Plugin.Degraded)
				{
					return !BottomlessContainer.TryResolve(__instance, out bottomless);
				}
				return true;
			}
		}

		[HarmonyPatch(typeof(Container), "Load")]
		private static class LoadPatch
		{
			private static bool Prefix(Container __instance, ref bool __result)
			{
				if (Plugin.Degraded || !BottomlessContainer.TryResolve(__instance, out var bottomless))
				{
					return true;
				}
				bottomless.EnsureRegistered();
				__result = bottomless.LoadFromStore();
				return false;
			}
		}
	}
	internal static class DestructionGuards
	{
		[HarmonyPatch(typeof(Container), "CanBeRemoved")]
		private static class RemovalGuard
		{
			private static bool Prefix(Container __instance, ref bool __result)
			{
				if (Plugin.Degraded || !BottomlessContainer.TryResolve(__instance, out var bottomless))
				{
					return true;
				}
				int? num = StackCount(bottomless);
				__result = num.HasValue && num.Value == 0;
				return false;
			}
		}

		[HarmonyPatch(typeof(Container), "DropAllItems", new Type[] { })]
		private static class DropGuard
		{
			private static bool Prefix(Container __instance)
			{
				return SuppressDrop(__instance);
			}
		}

		[HarmonyPatch(typeof(Container), "DropAllItems", new Type[] { typeof(GameObject) })]
		private static class DropIntoContainerGuard
		{
			private static bool Prefix(Container __instance)
			{
				return SuppressDrop(__instance);
			}
		}

		private static int? StackCount(BottomlessContainer bottomless)
		{
			string currentStoreId = bottomless.CurrentStoreId;
			if (string.IsNullOrEmpty(currentStoreId))
			{
				return 0;
			}
			if (SidecarStore.IsServerAuthority)
			{
				if (ChestSessions.TryGet(currentStoreId, out var session))
				{
					return session.TotalCount;
				}
				return bottomless.Inventory?.m_inventory.Count ?? 0;
			}
			return ChestView.KnownTotalFor(currentStoreId);
		}

		private static bool SuppressDrop(Container container)
		{
			if (Plugin.Degraded || !BottomlessContainer.TryResolve(container, out var bottomless))
			{
				return true;
			}
			Plugin.Log.LogWarning((object)("A bottomless chest was destroyed. Its contents were NOT dropped; store " + bottomless.CurrentStoreId + " is intact and can be reattached with 'bottomless rebind'."));
			return false;
		}
	}
	internal static class HoverTextPatch
	{
		[HarmonyPatch(typeof(Container), "GetHoverText")]
		private static class Patch
		{
			private static bool Prefix(Container __instance, ref string __result)
			{
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				if (Plugin.Degraded || !BottomlessContainer.TryResolve(__instance, out var _))
				{
					return true;
				}
				if (__instance.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, false, false))
				{
					return true;
				}
				__result = Localization.instance.Localize(__instance.m_name + "\n[<color=yellow><b>$KEY_Use</b></color>] $piece_container_open");
				return false;
			}
		}
	}
	internal static class InventoryCapacity
	{
		[HarmonyPatch(typeof(Inventory), "TopFirst")]
		private static class TopFirstPatch
		{
			private static bool Prefix(Inventory __instance, ref bool __result)
			{
				if (!Unbounded.Contains(__instance))
				{
					return true;
				}
				__result = true;
				return false;
			}
		}

		[HarmonyPatch(typeof(Inventory), "Changed")]
		private static class ChangedPatch
		{
			private static void Postfix(Inventory __instance)
			{
				if (Unbounded.Contains(__instance) && !Suspended)
				{
					Apply(__instance);
					ChestView.OnInventoryChanged(__instance);
				}
			}
		}

		private static readonly HashSet<Inventory> Unbounded = new HashSet<Inventory>();

		internal static bool Suspended { get; set; }

		internal static void Register(Inventory inventory)
		{
			if (inventory != null && Unbounded.Add(inventory))
			{
				Apply(inventory);
			}
		}

		internal static void Forget(Inventory inventory)
		{
			if (inventory != null)
			{
				Unbounded.Remove(inventory);
			}
		}

		internal static bool IsUnbounded(Inventory inventory)
		{
			return Unbounded.Contains(inventory);
		}

		internal static bool LayoutIsUsable(Inventory inventory, int rows)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			int num = 8;
			if (inventory.m_inventory.Count > num * rows)
			{
				return false;
			}
			List<GridPos> list = new List<GridPos>(inventory.m_inventory.Count);
			foreach (ItemData item in inventory.m_inventory)
			{
				list.Add(new GridPos(item.m_gridPos.x, item.m_gridPos.y));
			}
			return GridPacker.LayoutFitsWindow((IReadOnlyList<GridPos>)list, num, rows);
		}

		internal static void Repack(Inventory inventory)
		{
			//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_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			int num = 8;
			List<ItemData> inventory2 = inventory.m_inventory;
			for (int i = 0; i < inventory2.Count; i++)
			{
				GridPos val = GridPacker.PositionOf(i, num);
				inventory2[i].m_gridPos = new Vector2i(((GridPos)(ref val)).X, ((GridPos)(ref val)).Y);
			}
		}

		internal static void Apply(Inventory inventory)
		{
			ApplyFor(inventory, inventory.m_inventory.Count);
		}

		internal static void ApplyFor(Inventory inventory, int count)
		{
			int num = 8;
			int windowSlots = ChestView.WindowSlots;
			int num2 = GridPacker.RowsForChest(count + windowSlots, num, 6);
			foreach (ItemData item in inventory.m_inventory)
			{
				if (item.m_gridPos.y >= num2)
				{
					num2 = item.m_gridPos.y + 1;
				}
			}
			inventory.m_width = num;
			inventory.m_height = num2;
		}
	}
	internal static class RemoteMovePatches
	{
		[HarmonyPatch(typeof(Inventory), "MoveItemToThis", new Type[]
		{
			typeof(Inventory),
			typeof(ItemData)
		})]
		private static class MoveWhole
		{
			private static bool Prefix(Inventory __instance, Inventory fromInventory, ItemData item)
			{
				return Intercept(__instance, fromInventory, item);
			}
		}

		[HarmonyPatch(typeof(Inventory), "MoveItemToThis", new Type[]
		{
			typeof(Inventory),
			typeof(ItemData),
			typeof(int),
			typeof(int),
			typeof(int)
		})]
		private static class MovePart
		{
			private static bool Prefix(Inventory __instance, Inventory fromInventory, ItemData item, ref bool __result)
			{
				if (!Intercept(__instance, fromInventory, item))
				{
					__result = true;
					return false;
				}
				return true;
			}
		}

		private static readonly List<int> OneSlot = new List<int>(1);

		private static bool Intercept(Inventory destination, Inventory source, ItemData item)
		{
			if (Plugin.Degraded || item == null)
			{
				return true;
			}
			if (ChestView.IsRemotePage(source))
			{
				int num = ChestView.PageSlotOf(item);
				if (num < 0)
				{
					return true;
				}
				OneSlot.Clear();
				OneSlot.Add(num);
				ChestView.RequestTake(OneSlot);
				return false;
			}
			if (ChestView.IsRemotePage(destination))
			{
				return !ChestView.RequestPut(item);
			}
			return true;
		}
	}
	internal static class StackAllButtonPatch
	{
		[HarmonyPatch(typeof(InventoryGui), "OnStackAll")]
		private static class Patch
		{
			private static bool Prefix(InventoryGui __instance)
			{
				if (Plugin.Degraded || (Object)(object)__instance.m_currentContainer == (Object)null || (Object)(object)Player.m_loc