Decompiled source of BestAutoSort StabilityFix v0.4.2

plugins/BestAutoSort.dll

Decompiled 9 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
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 BestAutoSort;
using BestAutoSort.Core;
using BestAutoSort.Patches;
using BestAutoSort.Runtime;
using BestAutoSort.Tx;
using BestAutoSort.TxCore;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("maks2204")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.4.0.0")]
[assembly: AssemblyInformationalVersion("0.4.0")]
[assembly: AssemblyProduct("BestAutoSort")]
[assembly: AssemblyTitle("BestAutoSort")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.4.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
[HarmonyPatch(typeof(Container), "RPC_OpenResponse")]
internal static class TxContainerOpenResponsePatch
{
	private static void Postfix(Container __instance, long uid, bool granted)
	{
		if (!granted && Plugin.IsActive)
		{
			Plugin.LogInstance.LogInfo((object)"[ChestTX] open denied by manager (busy/lease/access?)");
		}
	}
}
namespace BestAutoSort
{
	[BepInPlugin("dev.maks2204.bestautosort", "BestAutoSort", "0.4.0")]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	[BepInIncompatibility("goldenrevolver.quick_stack_store")]
	[BepInIncompatibility("com.maxsch.valheim.MultiUserChest")]
	[BepInIncompatibility("Narolith.AutoFeed")]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal const string PluginGuid = "dev.maks2204.bestautosort";

		internal const string PluginName = "BestAutoSort";

		internal const string PluginVersion = "0.4.0";

		private Harmony? _harmony;

		private bool _shutDown;

		private static readonly Stopwatch UpdateTimer = new Stopwatch();

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource LogInstance { get; private set; }

		internal QuickStackService QuickStackService { get; private set; }

		internal static bool IsActive { get; private set; }

		private void Awake()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			IsActive = false;
			_shutDown = false;
			Instance = this;
			LogInstance = ((BaseUnityPlugin)this).Logger;
			try
			{
				ModConfig.Bind(ConfigFileMigration.Open((BaseUnityPlugin)this, Paths.ConfigPath, ((BaseUnityPlugin)this).Logger));
				QuickStackService = new QuickStackService(this);
				StorageCommands.Register();
				_harmony = new Harmony("dev.maks2204.bestautosort");
				PatchAllSafely();
				IsActive = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"BestAutoSort 0.4.0 (f0f1576) loaded.");
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)string.Format("{0} failed to initialize safely: {1}", "BestAutoSort", arg));
				Shutdown();
				throw;
			}
		}

		private void PatchAllSafely()
		{
			Type[] array = (from type2 in typeof(Plugin).Assembly.GetTypes()
				where type2.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0
				select type2).OrderBy<Type, string>((Type type2) => type2.FullName, StringComparer.Ordinal).ToArray();
			int num = 0;
			Type[] array2 = array;
			foreach (Type type in array2)
			{
				try
				{
					_harmony.PatchAll(type);
					num++;
				}
				catch (Exception arg)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)$"Failed to apply patch class {type.FullName}; other BestAutoSort features will remain available: {arg}");
				}
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Applied {num} of {array.Length} BestAutoSort patch classes.");
			if (TxRenderPatch.ReplacedCount == 0)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"TxRenderPatch transpiler found no Container.IsOwner call in InventoryGui.UpdateContainer — multi-viewer rendering is DISABLED, game update likely changed.");
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"TxRenderPatch replaced {TxRenderPatch.ReplacedCount} owner-check(s) for shared rendering.");
			}
		}

		private void Update()
		{
			//IL_00cf: 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_00eb: Unknown result type (might be due to invalid IL or missing references)
			if (!IsActive)
			{
				return;
			}
			Timed("ConfigSync.Pump", ConfigSync.Pump);
			Timed("ChestTxService.Pump", ChestTxService.Pump);
			Timed("AutoFeedService.Update", AutoFeedService.Update);
			Timed("NearbyPlaceIntent.Pump", NearbyPlaceIntent.Pump);
			NearbyResourceService.RepairInvalidPlayerInventoryPositions();
			if (ChestUpgradeService.UpdateLegacyMigration())
			{
				return;
			}
			ChestRuleEditor.Update();
			InventoryButtons.UpdateVisibility();
			if (ModConfig.Enabled.Value && CanAcceptShortcut())
			{
				if (IsShortcutDownAllowingOtherKeys(ModConfig.QuickStackShortcut.Value))
				{
					QuickStackNearby();
				}
				KeyboardShortcut value = ModConfig.SortChestShortcut.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					SortOpenChest();
				}
			}
		}

		private void Timed(string name, Action step)
		{
			UpdateTimer.Restart();
			step();
			UpdateTimer.Stop();
			if (UpdateTimer.ElapsedMilliseconds > 100)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)$"Slow frame step {name}: {UpdateTimer.ElapsedMilliseconds} ms");
			}
		}

		internal void QuickStackNearby()
		{
			if (!IsActive)
			{
				return;
			}
			try
			{
				QuickStackService.BeginNearbyQuickStack(RestockProfileService.RestockNearby);
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Nearby quick stack failed: {arg}");
			}
		}

		internal void SortOpenChest()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			if (!IsActive)
			{
				return;
			}
			try
			{
				Container val = InventoryAccess.CurrentContainer(InventoryGui.instance);
				if ((Object)val == (Object)null)
				{
					Player localPlayer = Player.m_localPlayer;
					if ((Object)(object)localPlayer != (Object)null)
					{
						((Character)localPlayer).Message((MessageType)2, "Open a chest to sort it.", 0, (Sprite)null, false);
					}
				}
				else if (!ChestTxService.IsShared(val))
				{
					if (val.IsOwner())
					{
						SortLocal(val);
					}
				}
				else
				{
					ChestTxService.RequestSort(val, (int)ModConfig.ChestSortMode.Value, ModConfig.SortDescending.Value);
				}
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Chest sort failed: {arg}");
			}
		}

		internal static void SortLocal(Container container)
		{
			int num = InventorySorter.Sort(container.GetInventory(), ModConfig.ChestSortMode.Value, ModConfig.SortDescending.Value);
			TxReflect.UpdateRows(container);
			TxReflect.SaveContainer(container);
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				((Character)localPlayer).Message((MessageType)2, string.Format("Sorted {0} item stack{1} by {2}.", num, (num == 1) ? string.Empty : "s", ModConfig.ChestSortMode.Value), 0, (Sprite)null, false);
			}
		}

		internal void UpgradeOpenChest(int targetTier)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			if (!IsActive)
			{
				return;
			}
			try
			{
				Container val = InventoryAccess.CurrentContainer(InventoryGui.instance);
				if ((Object)val == (Object)null)
				{
					return;
				}
				if (!ChestTxService.IsShared(val) || val.IsOwner())
				{
					ChestUpgradeService.UpgradeOpenChest(targetTier);
					return;
				}
				if (ChestTxService.AcquireForStructural(val))
				{
					ChestUpgradeService.UpgradeOpenChest(targetTier);
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null)
				{
					((Character)localPlayer).Message((MessageType)2, "Could not acquire the chest for upgrade. Try again.", 0, (Sprite)null, false);
				}
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Chest upgrade failed: {arg}");
				Player localPlayer2 = Player.m_localPlayer;
				if ((Object)(object)localPlayer2 != (Object)null)
				{
					((Character)localPlayer2).Message((MessageType)2, "Chest upgrade failed. See the log for details.", 0, (Sprite)null, false);
				}
			}
		}

		private static bool CanAcceptShortcut()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			if ((Object)Player.m_localPlayer == (Object)null)
			{
				return false;
			}
			if (ChestRuleEditor.IsOpen)
			{
				return false;
			}
			if ((Object)Chat.instance != (Object)null && Chat.instance.HasFocus())
			{
				return false;
			}
			if (Console.IsVisible() || Menu.IsVisible() || TextInput.IsVisible())
			{
				return false;
			}
			if ((Object)Minimap.instance != (Object)null && Minimap.InTextInput())
			{
				return false;
			}
			return true;
		}

		private static bool IsShortcutDownAllowingOtherKeys(KeyboardShortcut shortcut)
		{
			//IL_0009: 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_0014: 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)
			KeyCode mainKey = ((KeyboardShortcut)(ref shortcut)).MainKey;
			if ((int)mainKey == 0 || !UnityInput.Current.GetKeyDown(mainKey))
			{
				return false;
			}
			return ((KeyboardShortcut)(ref shortcut)).Modifiers.All((KeyCode modifier) => modifier == mainKey || UnityInput.Current.GetKey(modifier));
		}

		private void OnDestroy()
		{
			Shutdown();
		}

		private void Shutdown()
		{
			if (!_shutDown)
			{
				_shutDown = true;
				IsActive = false;
				QuickStackService?.Reset();
				ChestTxService.Reset();
				TxNet.Reset();
				ConfigSync.Shutdown();
				AutoFeedService.Reset();
				InventoryButtons.Detach();
				TransferContext.End();
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
				_harmony = null;
			}
		}
	}
	internal static class BuildInfo
	{
		internal const string Commit = "f0f1576";
	}
}
namespace BestAutoSort.Tx
{
	internal static class ChestTxService
	{
		internal const int BatchChunkSize = 4;

		private const float RequestTimeout = 3f;

		private const int PayloadAttempts = 4;

		private const int QueryAttempts = 2;

		private const float FailDeadline = 16f;

		private const float PresenceHeartbeat = 5f;

		private const float PresenceTimeout = 15f;

		private const float RefreshPoll = 0.25f;

		private const float SlowPump = 0.5f;

		private const int DrainPerFrame = 64;

		internal const string RingKey = "BestAutoSort_TxRing";

		internal const string ItemsKey = "items";

		private static readonly Dictionary<int, ChestState> States = new Dictionary<int, ChestState>();

		private static readonly Dictionary<long, PendingTx> Pending = new Dictionary<long, PendingTx>();

		private static uint _clientCounter = 1u;

		private static float _nextSlowPump;

		private static float _nextRefreshPoll;

		private static int _openInstanceId;

		private static float _nextPresenceAt;

		private static readonly HashSet<ItemData> InFlightAdds = new HashSet<ItemData>();

		private static void Respond(Container container, long peer, long txId, TxStatus status, uint revision, ZPackage body, bool totalsOnly, TxOp op)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Expected O, but got Unknown
			ZNetView netView = TxReflect.GetNetView(container);
			if ((Object)(object)netView == (Object)null || !netView.IsValid())
			{
				return;
			}
			if (peer == ZNet.GetUID())
			{
				TxLog.Info("tx=" + txId + " respond loopback (manager==requester)");
			}
			ZPackage val = new ZPackage();
			TxCodec.WriteResponseHeader(val, txId, status, revision, totalsOnly);
			val.Write(body);
			if (body != null)
			{
				try
				{
					int num = val.GetArray().Length;
					if (num > 4096)
					{
						TxLog.Info("tx=" + txId + " respond bytes=" + num + " (large)");
					}
				}
				catch
				{
				}
			}
			try
			{
				netView.InvokeRPC(peer, "BestAutoSort_TxResponse", new object[1] { val });
			}
			catch (Exception ex)
			{
				TxLog.Warn("tx=" + txId + " respond failed: " + ex.Message);
			}
		}

		private static ZPackage EncodeResultBody(TxOpCall call, StoredResult r)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected O, but got Unknown
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			switch (call.Op)
			{
			case TxOp.Add:
			case TxOp.AddBatch:
			case TxOp.Move:
			case TxOp.Sort:
			case TxOp.Upgrade:
			case TxOp.SetRule:
			{
				val.Write(r.Accepted.Count);
				for (int j = 0; j < r.Accepted.Count; j++)
				{
					val.Write(r.Accepted[j]);
				}
				break;
			}
			case TxOp.Take:
			case TxOp.TakeBatch:
			{
				val.Write(r.Takes.Count);
				for (int i = 0; i < r.Takes.Count; i++)
				{
					TakeEntry takeEntry = r.Takes[i];
					if (takeEntry == null)
					{
						val.Write(0);
						val.Write(new ZPackage());
						val.Write(0);
					}
					else
					{
						ZPackage val2 = new ZPackage();
						takeEntry.Item.Save(val2);
						val.Write(takeEntry.PrefabHash);
						val.Write(val2);
						val.Write(takeEntry.Accepted);
					}
				}
				break;
			}
			}
			return val;
		}

		private static void RespondQuery(Container container, long sender, long txId)
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			ChestState state = GetState(container);
			if (state != null)
			{
				if (state.Processed.TryGetValue(txId, out var value))
				{
					TxLog.Info("container=" + TxLog.Zid(state.ZdoId) + " tx=" + txId + " QUERY hit");
					Respond(container, sender, txId, TxStatus.Duplicate, value.Revision, EncodeCachedBody(value), value.TotalsOnly, value.Op);
				}
				else
				{
					Respond(container, sender, txId, TxStatus.UnknownTx, CurrentRevision(container), new ZPackage(), totalsOnly: true, TxOp.Query);
				}
			}
		}

		private static ZPackage EncodeCachedBody(StoredResult cached)
		{
			//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();
			if (cached.TotalsOnly)
			{
				if (cached.Op == TxOp.Take || cached.Op == TxOp.TakeBatch)
				{
					val.Write(0);
					return val;
				}
				val.Write(1);
				val.Write(cached.AcceptedTotal());
				return val;
			}
			return EncodeResultBody(new TxOpCall
			{
				Op = cached.Op
			}, cached);
		}

		private static void OnTxResponse(Container container, long sender, ZPackage pkg)
		{
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Expected O, but got Unknown
			if (!Plugin.IsActive)
			{
				return;
			}
			if (!TxCodec.ReadResponseHeader(pkg, out var txId, out var status, out var revision, out var totalsOnly))
			{
				TxLog.Warn("bad response packet from " + sender);
				return;
			}
			if (!Pending.TryGetValue(txId, out var value))
			{
				RefreshNow(container);
				TxLog.Info("tx=" + txId + " late-duplicate ignored (already completed)");
				return;
			}
			TxLog.Info("tx=" + txId + " response status=" + status.ToString() + " rev=" + revision + (totalsOnly ? " totals-only" : ""));
			ZPackage val;
			try
			{
				val = pkg.ReadPackage();
			}
			catch (Exception ex)
			{
				TxLog.Warn("tx=" + txId + " response body unreadable: " + ex.Message);
				RefreshNow(value.Container);
				if (!ForceRefetch(txId))
				{
					FinalizeUnknownTx(txId);
				}
				return;
			}
			pkg = val;
			if (totalsOnly && status == TxStatus.Rejected)
			{
				Pending.Remove(txId);
				ReleaseClaimed(value.Claimed);
				TxLog.Info("tx=" + txId + " rejected by manager (nothing applied)");
				try
				{
					if (value.OnResponse != null)
					{
						value.OnResponse(new ZPackage(), TxStatus.Rejected, revision);
					}
				}
				catch (Exception ex2)
				{
					TxLog.Error("tx=" + txId + " reject completion failed: " + ex2.Message);
				}
				RefreshNow(container);
				return;
			}
			if (totalsOnly && IsTakeOp(value.Op))
			{
				Pending.Remove(txId);
				ReleaseClaimed(value.Claimed);
				TxLog.Warn("tx=" + txId + " totals-only Take: applied but items unrecoverable from cache");
				TellPlayer("Chest applied the move but the items cannot be recovered. Check the chest.");
				RefreshNow(value.Container);
				return;
			}
			if (totalsOnly && !IsTakeOp(value.Op) && value.ExpectedItems > 1)
			{
				Pending.Remove(txId);
				ReleaseClaimed(value.Claimed);
				TxLog.Warn("tx=" + txId + " totals-only multi-add: applied but per-item counts unknown");
				TellPlayer("Chest applied the move but per-item counts are unknown. Check the chest before retrying.");
				RefreshNow(value.Container);
				return;
			}
			if (!totalsOnly && IsTakeOp(value.Op) && !TakeBodyReadable(pkg))
			{
				TxLog.Warn("tx=" + txId + " take body corrupt: re-querying instead of voiding");
				RefreshNow(value.Container);
				if (!ForceRefetch(txId))
				{
					FinalizeUnknownTx(txId);
				}
				return;
			}
			if (!totalsOnly && !IsTakeOp(value.Op) && value.ExpectedItems >= 0 && !AddBodyMatchesArity(pkg, value.ExpectedItems))
			{
				TxLog.Warn("tx=" + txId + " add body corrupt: re-querying instead of misattributing");
				RefreshNow(value.Container);
				if (!ForceRefetch(txId))
				{
					FinalizeUnknownTx(txId);
				}
				return;
			}
			Pending.Remove(txId);
			ReleaseClaimed(value.Claimed);
			try
			{
				if (value.OnResponse != null)
				{
					value.OnResponse(pkg, status, revision);
				}
			}
			catch (Exception ex3)
			{
				TxLog.Error("tx=" + txId + " completion failed: " + ex3.Message);
			}
			RefreshNow(container);
		}

		internal static bool ForceRefetch(long txId)
		{
			if (!Pending.TryGetValue(txId, out var value) || value.FinalQuerySent)
			{
				return false;
			}
			if ((Object)(object)value.Container == (Object)null)
			{
				return false;
			}
			value.FinalQuerySent = true;
			value.QuerySent = true;
			SendQuery(value);
			value.Attempts++;
			value.Deadline = Time.realtimeSinceStartup + 3f;
			value.NextTryAt = Time.realtimeSinceStartup + 3f;
			TxLog.Warn("tx=" + txId + " forced re-query (decode/timeout), deadline extended once");
			return true;
		}

		internal static void FinalizeUnknownTx(long txId)
		{
			if (!Pending.TryGetValue(txId, out var value))
			{
				return;
			}
			Pending.Remove(txId);
			ReleaseClaimed(value.Claimed);
			RefreshNow(value.Container);
			TxLog.Warn("tx=" + value.TxId + " UNKNOWN op=" + value.Op.ToString() + " (applied-or-not indeterminate, no queries left)");
			TellPlayer("Chest request outcome unknown. Check the chest before retrying.");
			try
			{
				if (value.OnResponse != null)
				{
					value.OnResponse(null, TxStatus.UnknownTx, 0u);
				}
			}
			catch (Exception ex)
			{
				TxLog.Error("tx=" + value.TxId + " unknown completion failed: " + ex.Message);
			}
		}

		private static bool TakeBodyReadable(ZPackage body)
		{
			try
			{
				if (body == null)
				{
					return false;
				}
				int num = body.ReadInt();
				if (num < 0 || num > 4096)
				{
					return false;
				}
				for (int i = 0; i < num; i++)
				{
					body.ReadInt();
					body.ReadPackage();
					body.ReadInt();
				}
				body.SetPos(0);
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static bool AddBodyMatchesArity(ZPackage body, int expected)
		{
			try
			{
				if (body == null)
				{
					return false;
				}
				int num = body.ReadInt();
				if (num != expected)
				{
					return false;
				}
				for (int i = 0; i < num; i++)
				{
					body.ReadInt();
				}
				body.SetPos(0);
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static int ReadAcceptedAt(ZPackage pkg, int index)
		{
			try
			{
				if (pkg == null)
				{
					return 0;
				}
				int num = pkg.ReadInt();
				int result = 0;
				for (int i = 0; i < num; i++)
				{
					int num2 = pkg.ReadInt();
					if (i == index)
					{
						result = num2;
					}
				}
				return result;
			}
			catch (Exception)
			{
				return 0;
			}
		}

		private static List<int> ReadAcceptedList(ZPackage pkg, int count)
		{
			List<int> list = new List<int>();
			try
			{
				if (pkg == null)
				{
					return list;
				}
				int num = pkg.ReadInt();
				for (int i = 0; i < num && i < count; i++)
				{
					list.Add(pkg.ReadInt());
				}
			}
			catch (Exception)
			{
			}
			while (list.Count < count)
			{
				list.Add(0);
			}
			return list;
		}

		private static void CompleteAdd(Inventory srcInv, ItemData itemRef, TxOpItem sent, int accepted, TxStatus status, uint rev, Container container, Action<ZPackage, TxStatus, uint> onDone)
		{
			CompleteAdd(srcInv, itemRef, sent, accepted, status, rev, container, onDone, alreadyRemoved: false);
		}

		private static void LogNonMainInventory(string op, Inventory inv)
		{
			try
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null && inv != null && inv != ((Humanoid)localPlayer).GetInventory())
				{
					TxLog.Info(op + " uses non-main inventory (dedicated slots?)");
				}
			}
			catch
			{
			}
		}

		private static void CompleteAdd(Inventory srcInv, ItemData itemRef, TxOpItem sent, int accepted, TxStatus status, uint rev, Container container, Action<ZPackage, TxStatus, uint> onDone, bool alreadyRemoved)
		{
			LogNonMainInventory("add", srcInv);
			if (alreadyRemoved)
			{
				RestoreDragRemainder(srcInv, itemRef, accepted);
				if (status == TxStatus.Rejected || status == TxStatus.UnknownTx)
				{
					TellPlayer("The shared chest refused the move. Try again.");
				}
				onDone?.Invoke(null, status, rev);
				return;
			}
			if (accepted > 0 && srcInv != null && sent != null)
			{
				string name = ((sent.Snapshot != null && sent.Snapshot.m_shared != null) ? sent.Snapshot.m_shared.m_name : null);
				int quality = ((sent.Snapshot != null) ? sent.Snapshot.m_quality : (-1));
				int variant = ((sent.Snapshot != null) ? sent.Snapshot.m_variant : (-1));
				float world = ((sent.Snapshot != null) ? ((float)sent.Snapshot.m_worldLevel) : (-1f));
				int num = TxInventory.RemoveForTake(srcInv, itemRef, name, quality, accepted, variant, world);
				TxLog.Info("add-remove accepted=" + accepted + " removed=" + num);
				if (num < accepted)
				{
					int amount = accepted - num;
					TxLog.Warn("tx short-removal: accepted=" + accepted + " removed=" + num + " compensating " + amount);
					CompensateAddBack(container, sent, amount);
				}
			}
			if (status == TxStatus.Rejected || status == TxStatus.UnknownTx)
			{
				TellPlayer("The shared chest refused the move. Try again.");
			}
			onDone?.Invoke(null, status, rev);
		}

		private static void RestoreDragRemainder(Inventory srcInv, ItemData itemRef, int accepted)
		{
			if (srcInv == null || itemRef == null)
			{
				return;
			}
			CustomDataTags.StripBenign(itemRef);
			TxLog.Info("drag-restore stack=" + itemRef.m_stack + " accepted=" + accepted);
			int num = itemRef.m_stack - accepted;
			if (num > 0)
			{
				itemRef.m_stack = num;
				if (srcInv.AddItem(itemRef))
				{
					srcInv.m_onChanged?.Invoke();
				}
				else
				{
					DropAtFeet(itemRef, "drag restore");
				}
			}
		}

		private static void DropAtFeet(ItemData item, string what)
		{
			//IL_0047: 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_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: 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)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			if (item != null && item.m_stack > 0)
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					TxLog.Error(what + " failed: no player, items lost: " + item.m_stack);
					return;
				}
				Transform transform = ((Component)localPlayer).transform;
				ItemDrop.DropItem(item, item.m_stack, transform.position + transform.forward + transform.up, transform.rotation);
			}
		}

		private static void CompensateAddBack(Container container, TxOpItem sent, int amount)
		{
			if (amount <= 0 || sent == null || sent.Snapshot == null || sent.Snapshot.m_shared == null)
			{
				return;
			}
			TxOpItem txOpItem = SnapshotAuto(sent.Snapshot, amount);
			if (txOpItem == null)
			{
				return;
			}
			List<TxOpItem> list = new List<TxOpItem>();
			list.Add(txOpItem);
			RequestTakeCustom(container, list, respectReserves: false, delegate(List<DecodedTake> decoded, TxStatus status, uint rev)
			{
				int num = 0;
				if (decoded != null)
				{
					foreach (DecodedTake item in decoded)
					{
						if (item != null && item.Accepted > 0)
						{
							num += item.Accepted;
						}
					}
				}
				if (status != TxStatus.Accepted && status != TxStatus.Partial && status != TxStatus.Duplicate)
				{
					TxLog.Warn("short-removal burn rejected (" + status.ToString() + "), excess stays in chest");
					TellPlayer("Chest could not correct an over-deposit. Check the chest.");
				}
				else if (num < amount)
				{
					TxLog.Warn("short-removal burn partial: burned=" + num + " of " + amount);
					TellPlayer("Chest could not correct an over-deposit. Check the chest.");
				}
				else
				{
					TxLog.Info("short-removal burn ok: " + num);
				}
				RefreshNow(container);
			}, 0L);
		}

		private static void CompleteTake(Inventory dstInv, ZPackage pkg, TxStatus status, uint rev, Container container, Action<ZPackage, TxStatus, uint> onDone, int wantDstX = -1, int wantDstY = -1, Action<Dictionary<string, int>> onPlaced = null)
		{
			LogNonMainInventory("take", dstInv);
			if ((status == TxStatus.Accepted || status == TxStatus.Partial || status == TxStatus.Duplicate) && dstInv != null && pkg != null)
			{
				int num = 0;
				try
				{
					num = pkg.ReadInt();
				}
				catch (Exception ex)
				{
					int num2 = -1;
					try
					{
						num2 = pkg.GetArray().Length;
					}
					catch
					{
					}
					TxLog.Error("take completion decode failed: " + ex.Message + " (body bytes=" + num2 + ")");
				}
				Dictionary<string, int> dictionary = null;
				for (int i = 0; i < num; i++)
				{
					int num3 = 0;
					ZPackage val = null;
					int num4 = 0;
					try
					{
						num3 = pkg.ReadInt();
						val = pkg.ReadPackage();
						num4 = pkg.ReadInt();
					}
					catch (Exception ex2)
					{
						TxLog.Error("take completion entry " + i + " unreadable (" + ex2.Message + "), aborting rest");
						break;
					}
					if (num4 <= 0)
					{
						continue;
					}
					try
					{
						ItemData val2 = TxCodec.ResolvePrefab(num3, val);
						CustomDataTags.StripBenign(val2);
						if (val2 == null)
						{
							TxLog.Error("take completion: prefab " + num3 + " missing, compensating " + num4);
							CompensateTakeBack(container, num3, val, num4);
							continue;
						}
						int num5 = TxInventory.AddTakeAndCount(dstInv, val2, wantDstX, wantDstY);
						if (num5 > 0 && onPlaced != null && val2.m_shared != null)
						{
							if (dictionary == null)
							{
								dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
							}
							dictionary.TryGetValue(val2.m_shared.m_name, out var value);
							dictionary[val2.m_shared.m_name] = value + num5;
						}
						if (num5 < num4)
						{
							int stack = num4 - num5;
							TxLog.Warn("take completion: player full, compensating " + stack);
							ItemData val3 = val2.Clone();
							val3.m_stack = stack;
							CompensateTakeBackItem(container, num3, val3);
						}
					}
					catch (Exception ex3)
					{
						TxLog.Error("take completion entry " + i + " failed (" + ex3.Message + "), compensating " + num4);
						try
						{
							CompensateTakeBack(container, num3, val, num4);
						}
						catch (Exception ex4)
						{
							TxLog.Error("take completion entry " + i + " compensation failed: " + ex4.Message);
						}
					}
				}
				if (dictionary != null && dictionary.Count > 0 && onPlaced != null)
				{
					try
					{
						onPlaced(dictionary);
					}
					catch
					{
					}
				}
			}
			if (status == TxStatus.Rejected || status == TxStatus.UnknownTx)
			{
				TellPlayer("The shared chest changed. Try again.");
			}
			onDone?.Invoke(pkg, status, rev);
		}

		private static void CompensateTakeBack(Container container, int prefabHash, ZPackage inner, int amount)
		{
			ItemData val = TxCodec.ResolvePrefab(prefabHash, inner);
			if (val == null)
			{
				TxLog.Error("compensation failed: prefab missing");
			}
			else
			{
				CompensateTakeBackItem(container, prefabHash, val);
			}
		}

		internal static void CompensateTakeBackItem(Container container, int prefabHash, ItemData item)
		{
			int wanted = ((item != null) ? item.m_stack : 0);
			TxOpItem txOpItem = SnapshotAuto(item, item.m_stack);
			if (txOpItem == null)
			{
				return;
			}
			TxOpCall txOpCall = new TxOpCall();
			txOpCall.Op = TxOp.Add;
			txOpCall.Items.Add(txOpItem);
			Submit(container, txOpCall, delegate(ZPackage pkg, TxStatus status, uint rev)
			{
				int num = ReadAcceptedAt(pkg, 0);
				TxLog.Info("compensate-add status=" + status.ToString() + " accepted=" + num + " of " + wanted);
				if (num < wanted)
				{
					RestoreCompensateLeftover(item, num);
				}
				RefreshNow(container);
			}, 0L);
		}

		private static void RestoreCompensateLeftover(ItemData item, int accepted)
		{
			if (item == null)
			{
				return;
			}
			int num = item.m_stack - accepted;
			if (num <= 0)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			Inventory val = (((Object)(object)localPlayer != (Object)null) ? ((Humanoid)localPlayer).GetInventory() : null);
			if (val == null)
			{
				TxLog.Error("compensate restore failed: no player inventory, items lost: " + num);
				return;
			}
			ItemData val2 = item.Clone();
			val2.m_stack = num;
			CustomDataTags.StripBenign(val2);
			int num2 = TxInventory.AddAndCount(val, val2);
			if (num2 < num)
			{
				val2.m_stack = num - num2;
				DropAtFeet(val2, "compensate restore");
				TxLog.Warn("compensate leftover did not fit, dropped at feet: " + (num - num2));
			}
			else
			{
				TxLog.Warn("compensate-add short, restored to player: " + num);
			}
			TellPlayer("Chest could not take back some items. They were returned to you.");
		}

		internal static void SubmitTakePrefetch(Container container, TxOpCall call, Inventory dstInv, Action<Dictionary<string, int>> onLanded = null)
		{
			Submit(container, call, delegate(ZPackage pkg, TxStatus status, uint rev)
			{
				CompleteTake(dstInv, pkg, status, rev, container, null, -1, -1, onLanded);
			}, 0L);
		}

		internal static void RequestTakeCustom(Container container, List<TxOpItem> items, bool respectReserves, Action<List<DecodedTake>, TxStatus, uint> onDone, long playerId = 0L, Vector3? actorPos = null)
		{
			if (items == null || items.Count == 0)
			{
				return;
			}
			TxOpCall txOpCall = new TxOpCall();
			txOpCall.Op = TxOp.TakeBatch;
			txOpCall.Items.AddRange(items);
			txOpCall.RespectReserves = respectReserves;
			if (container.IsOwner())
			{
				MutateLocal(container, txOpCall, delegate(StoredResult r)
				{
					RefreshNow(container);
					List<DecodedTake> list = new List<DecodedTake>();
					if (r != null && r.Takes != null)
					{
						foreach (TakeEntry take in r.Takes)
						{
							if (take != null && take.Accepted > 0)
							{
								list.Add(new DecodedTake
								{
									PrefabHash = take.PrefabHash,
									Item = ((take.Item != null) ? take.Item.Clone() : null),
									Accepted = take.Accepted
								});
							}
						}
					}
					TxStatus arg = r?.Status ?? TxStatus.UnknownTx;
					uint arg2 = r?.Revision ?? CurrentRevision(container);
					if (onDone != null)
					{
						onDone(list, arg, arg2);
					}
				}, playerId, actorPos);
				return;
			}
			Submit(container, txOpCall, delegate(ZPackage pkg, TxStatus status, uint rev)
			{
				List<DecodedTake> list = TxCodec.ReadTakeResults(pkg);
				if (list == null)
				{
					TxLog.Warn("take-custom completion decode failed");
					if (onDone != null)
					{
						onDone(new List<DecodedTake>(), TxStatus.Rejected, rev);
					}
				}
				else if (onDone != null)
				{
					onDone(list, status, rev);
				}
			}, playerId, actorPos);
		}

		internal static void SubmitCall(Container container, TxOpCall call, Inventory srcInv, Action<List<TransferRecord>> onDone)
		{
			SubmitCallDetailed(container, call, srcInv, delegate(List<TransferRecord> records, List<int> accepted, TxStatus status)
			{
				if (onDone != null)
				{
					onDone(records);
				}
			});
		}

		internal static void SubmitCallDetailed(Container container, TxOpCall call, Inventory srcInv, Action<List<TransferRecord>, List<int>, TxStatus> onDone)
		{
			if (call != null && call.Items.Count > 4)
			{
				SubmitChunked(container, call, srcInv, onDone);
				return;
			}
			if (container.IsOwner())
			{
				MutateLocal(container, call, delegate(StoredResult r)
				{
					List<int> list = new List<int>();
					for (int i = 0; i < call.Items.Count; i++)
					{
						list.Add((r != null && r.Accepted != null && i < r.Accepted.Count) ? r.Accepted[i] : 0);
					}
					TxStatus arg = r?.Status ?? TxStatus.UnknownTx;
					List<TransferRecord> arg2 = FinishBatchCompletion(container, srcInv, call, r);
					if (onDone != null)
					{
						onDone(arg2, list, arg);
					}
				}, 0L);
				return;
			}
			Submit(container, call, delegate(ZPackage pkg, TxStatus status, uint rev)
			{
				List<int> list = ReadAcceptedList(pkg, call.Items.Count);
				List<TransferRecord> list2 = new List<TransferRecord>();
				for (int i = 0; i < call.Items.Count && i < list.Count; i++)
				{
					TxOpItem txOpItem = call.Items[i];
					CompleteAdd(srcInv, txOpItem.SourceRef, txOpItem, list[i], status, rev, container, null);
					if (list[i] > 0 && txOpItem.Snapshot != null && txOpItem.Snapshot.m_shared != null)
					{
						list2.Add(new TransferRecord(txOpItem.Snapshot.m_shared.m_name, txOpItem.Snapshot.GetIcon(), list[i], txOpItem.Snapshot.m_shared.m_maxStackSize));
					}
				}
				RefreshNow(container);
				if (onDone != null)
				{
					onDone(list2, list, status);
				}
			}, 0L, null, call.Items.Count);
		}

		private static void SubmitChunked(Container container, TxOpCall call, Inventory srcInv, Action<List<TransferRecord>, List<int>, TxStatus> onDone)
		{
			List<TxOpCall> parts = new List<TxOpCall>();
			for (int i = 0; i < call.Items.Count; i += 4)
			{
				TxOpCall txOpCall = new TxOpCall();
				txOpCall.Op = call.Op;
				txOpCall.EnforceRule = call.EnforceRule;
				txOpCall.RespectReserves = call.RespectReserves;
				for (int j = i; j < call.Items.Count && j < i + 4; j++)
				{
					txOpCall.Items.Add(call.Items[j]);
				}
				parts.Add(txOpCall);
			}
			List<int>[] partAccepted = new List<int>[parts.Count];
			List<TransferRecord> allRecords = new List<TransferRecord>();
			int left = parts.Count;
			bool anyUnknown = false;
			for (int k = 0; k < parts.Count; k++)
			{
				int index = k;
				SubmitCallDetailed(container, parts[index], srcInv, delegate(List<TransferRecord> records, List<int> accepted, TxStatus status)
				{
					if (partAccepted[index] == null)
					{
						partAccepted[index] = accepted ?? new List<int>();
						if (records != null)
						{
							allRecords.AddRange(records);
						}
						if (status == TxStatus.UnknownTx)
						{
							anyUnknown = true;
						}
						int num = left;
						left = num - 1;
						if (left <= 0)
						{
							call.Items.Clear();
							List<int> list = new List<int>();
							int num2 = 0;
							bool flag = true;
							for (int l = 0; l < parts.Count; l++)
							{
								for (int m = 0; m < parts[l].Items.Count; m++)
								{
									TxOpItem txOpItem = parts[l].Items[m];
									int num3 = ((m < partAccepted[l].Count) ? partAccepted[l][m] : 0);
									call.Items.Add(txOpItem);
									list.Add(num3);
									num2 += num3;
									if (num3 < txOpItem.Amount)
									{
										flag = false;
									}
								}
							}
							TxStatus arg = (anyUnknown ? TxStatus.UnknownTx : ((num2 == 0) ? TxStatus.Rejected : ((!flag) ? TxStatus.Partial : TxStatus.Accepted)));
							if (onDone != null)
							{
								onDone(allRecords, list, arg);
							}
						}
					}
				});
			}
		}

		private static List<TransferRecord> FinishBatchCompletion(Container container, Inventory srcInv, TxOpCall call, StoredResult r)
		{
			List<TransferRecord> list = new List<TransferRecord>();
			for (int i = 0; i < call.Items.Count && i < r.Accepted.Count; i++)
			{
				TxOpItem txOpItem = call.Items[i];
				int num = r.Accepted[i];
				if (num > 0 && srcInv != null)
				{
					string name = ((txOpItem.Snapshot != null && txOpItem.Snapshot.m_shared != null) ? txOpItem.Snapshot.m_shared.m_name : null);
					int quality = ((txOpItem.Snapshot != null) ? txOpItem.Snapshot.m_quality : (-1));
					int variant = ((txOpItem.Snapshot != null) ? txOpItem.Snapshot.m_variant : (-1));
					float world = ((txOpItem.Snapshot != null) ? ((float)txOpItem.Snapshot.m_worldLevel) : (-1f));
					int num2 = TxInventory.RemoveForTake(srcInv, txOpItem.SourceRef, name, quality, num, variant, world);
					if (num2 < num)
					{
						int amount = num - num2;
						TxLog.Warn("local batch short-removal: compensating " + amount);
						CompensateAddBack(container, txOpItem, amount);
					}
					if (txOpItem.Snapshot != null && txOpItem.Snapshot.m_shared != null)
					{
						list.Add(new TransferRecord(txOpItem.Snapshot.m_shared.m_name, txOpItem.Snapshot.GetIcon(), num, txOpItem.Snapshot.m_shared.m_maxStackSize));
					}
				}
			}
			RefreshNow(container);
			return list;
		}

		private static void PumpPending()
		{
			if (Pending.Count == 0)
			{
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			List<long> list = null;
			foreach (KeyValuePair<long, PendingTx> item in new List<KeyValuePair<long, PendingTx>>(Pending))
			{
				PendingTx value = item.Value;
				if (!Pending.ContainsKey(item.Key))
				{
					continue;
				}
				if ((Object)(object)value.Container == (Object)null)
				{
					if (list == null)
					{
						list = new List<long>();
					}
					list.Add(item.Key);
					ReleaseClaimed(value.Claimed);
				}
				else if (realtimeSinceStartup >= value.Deadline)
				{
					if (!value.FinalQuerySent && (Object)(object)value.Container != (Object)null)
					{
						value.FinalQuerySent = true;
						value.QuerySent = true;
						SendQuery(value);
						value.Attempts++;
						value.Deadline = realtimeSinceStartup + 3f;
						value.NextTryAt = realtimeSinceStartup + 3f;
						TxLog.Warn("tx=" + value.TxId + " TIMEOUT op=" + value.Op.ToString() + " final query sent");
					}
					else
					{
						if (list == null)
						{
							list = new List<long>();
						}
						list.Add(item.Key);
						FinalizeUnknownTx(item.Key);
					}
				}
				else if (!(realtimeSinceStartup < value.NextTryAt) && value.Attempts < 4 + ((!value.QuerySent) ? 2 : 0))
				{
					if (!value.QuerySent && value.Attempts >= 4)
					{
						value.QuerySent = true;
						SendQuery(value);
					}
					else
					{
						SendPayload(value.Container, value.Payload);
					}
					value.Attempts++;
					value.NextTryAt = realtimeSinceStartup + 3f;
				}
			}
			if (list == null)
			{
				return;
			}
			for (int i = 0; i < list.Count; i++)
			{
				if (!Pending.TryGetValue(list[i], out var value2))
				{
					continue;
				}
				Pending.Remove(list[i]);
				if ((Object)(object)value2.Container == (Object)null && value2.OnResponse != null)
				{
					try
					{
						value2.OnResponse(null, TxStatus.UnknownTx, 0u);
					}
					catch (Exception ex)
					{
						TxLog.Error("tx=" + value2.TxId + " orphan completion failed: " + ex.Message);
					}
				}
			}
		}

		private static void SendQuery(PendingTx p)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_0028: 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
			ZPackage val = new ZPackage();
			val.Write(2);
			val.Write(102);
			val.Write(0u);
			val.Write(LocalPlayerId());
			val.Write(LocalActorPos());
			val.Write(false);
			val.Write(false);
			ZPackage val2 = new ZPackage();
			val2.Write(p.TxId);
			val2.Write(val);
			TxLog.Info("tx=" + p.TxId + " QUERY (response was lost)");
			SendPayload(p.Container, val2);
		}

		internal static void RefreshNow(Container container)
		{
			ChestState state = GetState(container);
			if (state != null)
			{
				state.SeenRev = 0u;
				state.SeenOnce = false;
				state.SeenBytes = null;
			}
		}

		private static void PumpViewerRefresh()
		{
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0252: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
			if (Time.realtimeSinceStartup < _nextRefreshPoll)
			{
				return;
			}
			_nextRefreshPoll = Time.realtimeSinceStartup + 0.25f;
			InventoryGui instance = InventoryGui.instance;
			Container val = InventoryAccess.CurrentContainer(instance);
			int num = (((Object)(object)val != (Object)null) ? ((Object)val).GetInstanceID() : 0);
			if (num != _openInstanceId)
			{
				ChestState value = null;
				if (_openInstanceId != 0)
				{
					States.TryGetValue(_openInstanceId, out value);
				}
				if (value != null && (Object)(object)value.Container != (Object)null)
				{
					SendPresence(value.Container, TxOp.ViewerClose);
				}
				_openInstanceId = num;
				if ((Object)(object)val != (Object)null)
				{
					RefreshNow(val);
					SendPresence(val, TxOp.ViewerOpen);
					_nextPresenceAt = Time.realtimeSinceStartup + 5f;
				}
			}
			if ((Object)(object)val == (Object)null || val.IsOwner() || !IsShared(val))
			{
				return;
			}
			if (Time.realtimeSinceStartup >= _nextPresenceAt)
			{
				SendPresence(val, TxOp.ViewerOpen);
				_nextPresenceAt = Time.realtimeSinceStartup + 5f;
			}
			ChestState state = GetState(val);
			if (state == null)
			{
				return;
			}
			ZNetView netView = TxReflect.GetNetView(val);
			if ((Object)(object)netView == (Object)null || !netView.IsValid())
			{
				return;
			}
			uint dataRevision = netView.GetZDO().DataRevision;
			if (state.SeenOnce && dataRevision <= state.SeenRev)
			{
				if (dataRevision < state.SeenRev)
				{
					TxLog.Info("container=" + TxLog.Zid(state.ZdoId) + " viewer stale rev=" + dataRevision + " (seen " + state.SeenRev + "), skipped");
				}
				return;
			}
			byte[] byteArray;
			try
			{
				byteArray = netView.GetZDO().GetByteArray(ZDOVars.s_items, (byte[])null);
			}
			catch (Exception)
			{
				return;
			}
			if (SameBytes(byteArray, state.SeenBytes))
			{
				state.SeenRev = dataRevision;
				return;
			}
			if ((Object)(object)instance != (Object)null)
			{
				TxReflect.CancelDragFrom(instance, val.GetInventory());
			}
			TxLog.Info("container=" + TxLog.Zid(state.ZdoId) + " viewer dims before=" + val.GetInventory().GetWidth() + "x" + val.GetInventory().GetHeight());
			try
			{
				ZPackage pkg = (ZPackage)((byteArray == null) ? ((object)EmptyInventoryPackage(val)) : ((object)new ZPackage(byteArray)));
				TxReflect.LoadInventoryQuiet(val, pkg);
				TxReflect.SetLastRevision(val, dataRevision);
				TxReflect.UpdateRows(val);
			}
			catch (Exception ex2)
			{
				TxLog.Warn("viewer refresh failed: " + ex2.Message);
				return;
			}
			state.SeenRev = dataRevision;
			state.SeenOnce = true;
			state.SeenBytes = byteArray;
			TxLog.Info("container=" + TxLog.Zid(state.ZdoId) + " viewer refresh rev=" + dataRevision);
		}

		private static ZPackage EmptyInventoryPackage(Container container)
		{
			//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_0015: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(109);
			val.Write((ushort)0);
			return val;
		}

		private static bool SameBytes(byte[] a, byte[] b)
		{
			if (a == null || b == null)
			{
				return a == b;
			}
			if (a.Length != b.Length)
			{
				return false;
			}
			for (int i = 0; i < a.Length; i++)
			{
				if (a[i] != b[i])
				{
					return false;
				}
			}
			return true;
		}

		private static void SendPresence(Container container, TxOp op)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			if (IsShared(container) && !container.IsOwner())
			{
				ZNetView netView = TxReflect.GetNetView(container);
				if (!((Object)(object)netView == (Object)null) && netView.IsValid())
				{
					long num = TxIdGen.Next(ZNet.GetUID(), ref _clientCounter);
					ZPackage val = new ZPackage();
					val.Write(2);
					val.Write((int)op);
					val.Write(0u);
					val.Write(LocalPlayerId());
					val.Write(LocalActorPos());
					val.Write(false);
					val.Write(false);
					ZPackage val2 = new ZPackage();
					val2.Write(num);
					val2.Write(val);
					SendPayload(container, val2);
				}
			}
		}

		private static void HandlePresence(Container container, long sender, TxOp op)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			ChestState state = GetState(container);
			if (state != null)
			{
				if (op == TxOp.ViewerOpen)
				{
					state.Viewers[sender] = Time.realtimeSinceStartup;
					TxLog.Info("container=" + TxLog.Zid(state.ZdoId) + " viewer open peer=" + sender);
				}
				else
				{
					state.Viewers.Remove(sender);
					TxLog.Info("container=" + TxLog.Zid(state.ZdoId) + " viewer close peer=" + sender);
				}
			}
		}

		private static void PumpManagerSlow()
		{
			if (Time.realtimeSinceStartup < _nextSlowPump)
			{
				return;
			}
			_nextSlowPump = Time.realtimeSinceStartup + 0.5f;
			List<int> list = null;
			foreach (KeyValuePair<int, ChestState> item in new List<KeyValuePair<int, ChestState>>(States))
			{
				ChestState value = item.Value;
				if ((Object)(object)value.Container == (Object)null)
				{
					if (list == null)
					{
						list = new List<int>();
					}
					list.Add(item.Key);
					continue;
				}
				ZNetView netView = TxReflect.GetNetView(value.Container);
				if ((Object)(object)netView == (Object)null || !netView.IsValid())
				{
					continue;
				}
				long owner = netView.GetZDO().GetOwner();
				if (owner == ZNet.GetUID() && value.LastOwner != owner)
				{
					Takeover(value);
				}
				value.LastOwner = owner;
				if (owner == ZNet.GetUID())
				{
					Drain(value);
					try
					{
						TxReflect.UpdateRows(value.Container);
					}
					catch (Exception)
					{
					}
					PruneViewers(value);
					EnforceLid(value);
				}
			}
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					States.Remove(list[i]);
				}
			}
		}

		private static void Takeover(ChestState state)
		{
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			try
			{
				ZNetView netView = TxReflect.GetNetView(state.Container);
				byte[] byteArray = netView.GetZDO().GetByteArray(ZDOVars.s_items, (byte[])null);
				if (byteArray != null)
				{
					if ((Object)(object)InventoryGui.instance != (Object)null)
					{
						TxReflect.CancelDragFrom(InventoryGui.instance, state.Container.GetInventory());
					}
					TxReflect.LoadInventoryQuiet(state.Container, new ZPackage(byteArray));
					TxReflect.SetLastRevision(state.Container, netView.GetZDO().DataRevision);
					TxReflect.UpdateRows(state.Container);
				}
				RejectQueued(state);
				state.Queue.Clear();
				state.Processed.Clear();
				state.ProcOrder.Clear();
				List<RingEntry> ring = ReadRing(state.Container);
				SeedFromRing(state, ring);
				state.Viewers.Clear();
				state.SeenRev = 0u;
				state.SeenOnce = false;
				state.SeenBytes = null;
				TxLog.Info("container=" + TxLog.Zid(state.ZdoId) + " manager changed old=" + state.LastOwner + " new=" + ZNet.GetUID() + " revision=" + netView.GetZDO().DataRevision);
			}
			catch (Exception ex)
			{
				TxLog.Error("takeover failed: " + ex.Message);
			}
		}

		internal static void RejectQueued(ChestState state)
		{
			if (state == null)
			{
				return;
			}
			while (state.Queue.Count > 0)
			{
				TxJob txJob = state.Queue.Dequeue();
				try
				{
					StoredResult storedResult = new StoredResult();
					storedResult.Op = ((txJob.Call != null) ? txJob.Call.Op : TxOp.Query);
					storedResult.Status = TxStatus.Rejected;
					try
					{
						ZNetView netView = TxReflect.GetNetView(state.Container);
						storedResult.Revision = (((Object)(object)netView != (Object)null && netView.IsValid()) ? netView.GetZDO().DataRevision : 0u);
					}
					catch
					{
						storedResult.Revision = 0u;
					}
					if (txJob.Complete != null)
					{
						txJob.Complete(storedResult);
					}
				}
				catch (Exception ex)
				{
					TxLog.Error("tx=" + txJob.TxId + " reject-queued failed: " + ex.Message);
				}
			}
		}

		private static void PruneViewers(ChestState state)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			List<long> list = null;
			foreach (KeyValuePair<long, float> viewer in state.Viewers)
			{
				if (realtimeSinceStartup - viewer.Value > 15f)
				{
					if (list == null)
					{
						list = new List<long>();
					}
					list.Add(viewer.Key);
				}
			}
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					state.Viewers.Remove(list[i]);
				}
			}
		}

		private static void EnforceLid(ChestState state)
		{
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				return;
			}
			try
			{
				bool inUse = state.Viewers.Count > 0 || IsOpenByMe(state.Container);
				state.Container.SetInUse(inUse);
			}
			catch (Exception)
			{
			}
		}

		private static bool IsOpenByMe(Container container)
		{
			InventoryGui instance = InventoryGui.instance;
			if ((Object)(object)instance == (Object)null || !InventoryGui.IsVisible())
			{
				return false;
			}
			return (Object)(object)InventoryAccess.CurrentContainer(instance) == (Object)(object)container;
		}

		internal static void Pump()
		{
			if (Plugin.IsActive)
			{
				TxNet.PumpHello();
				PumpManagerSlow();
				PumpPending();
				PumpViewerRefresh();
			}
		}

		private static uint CurrentRevision(Container container)
		{
			ZNetView netView = TxReflect.GetNetView(container);
			if ((Object)(object)netView == (Object)null || !netView.IsValid())
			{
				return 0u;
			}
			return netView.GetZDO().DataRevision;
		}

		private static Vector3 LocalActorPos()
		{
			//IL_001b: 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)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return Vector3.zero;
			}
			return ((Component)localPlayer).transform.position;
		}

		private static long LocalPlayerId()
		{
			Game instance = Game.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return 0L;
			}
			PlayerProfile playerProfile = instance.GetPlayerProfile();
			if (playerProfile == null)
			{
				return 0L;
			}
			return playerProfile.GetPlayerID();
		}

		private static void TellPlayer(string message)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				((Character)localPlayer).Message((MessageType)2, message, 0, (Sprite)null, false);
			}
		}

		internal static void Reset()
		{
			States.Clear();
			Pending.Clear();
			_clientCounter = 1u;
			_nextSlowPump = 0f;
			_nextRefreshPoll = 0f;
			_openInstanceId = 0;
			_nextPresenceAt = 0f;
		}

		internal static void OnContainerAwake(Container container)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)container == (Object)null)
			{
				return;
			}
			ZNetView netView = TxReflect.GetNetView(container);
			if ((Object)(object)netView == (Object)null || !netView.IsValid())
			{
				return;
			}
			try
			{
				netView.Register<ZPackage>("BestAutoSort_TxRequest", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
				{
					OnTxRequest(container, sender, pkg);
				});
				netView.Register<ZPackage>("BestAutoSort_TxResponse", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
				{
					OnTxResponse(container, sender, pkg);
				});
			}
			catch (Exception ex)
			{
				TxLog.Warn("container " + TxLog.Zid(netView.GetZDO().m_uid) + " RPC register failed: " + ex.Message);
			}
			int instanceID = ((Object)container).GetInstanceID();
			if (!States.ContainsKey(instanceID))
			{
				ChestState chestState = new ChestState();
				chestState.Container = container;
				chestState.ZdoId = netView.GetZDO().m_uid;
				chestState.LastOwner = netView.GetZDO().GetOwner();
				States[instanceID] = chestState;
			}
			AutoFeedService.Attach(container);
		}

		internal static bool IsSharedVerbose(Container container, out string why)
		{
			why = "ok";
			if (!ModConfig.AllowConcurrentChestUse.Value)
			{
				why = "AllowConcurrentChestUse disabled";
				return false;
			}
			if ((Object)(object)container == (Object)null)
			{
				why = "null container";
				return false;
			}
			if (!((Behaviour)container).isActiveAndEnabled)
			{
				why = "behaviour disabled";
				return false;
			}
			if (AutoFeedService.IsLocked(container))
			{
				why = "autofeed lease/block active";
				return false;
			}
			if ((Object)(object)container.m_wagon != (Object)null)
			{
				why = "wagon cart";
				return false;
			}
			if ((Object)(object)((Component)container).GetComponentInParent<Ship>() != (Object)null)
			{
				why = "ship";
				return false;
			}
			if ((Object)(object)((Component)container).GetComponent<Player>() != (Object)null)
			{
				why = "player inventory";
				return false;
			}
			if ((Object)(object)((Component)container).GetComponent<TombStone>() != (Object)null)
			{
				why = "tombstone";
				return false;
			}
			ZNetView netView = TxReflect.GetNetView(container);
			if ((Object)(object)netView == (Object)null)
			{
				why = "no netview";
				return false;
			}
			if (!netView.IsValid())
			{
				why = "netview invalid";
				return false;
			}
			return true;
		}

		internal static bool IsShared(Container container)
		{
			string why;
			return IsSharedVerbose(container, out why);
		}

		internal static bool IsManager(Container container)
		{
			return container.IsOwner();
		}

		internal static ChestState GetState(Container container)
		{
			if ((Object)(object)container == (Object)null)
			{
				return null;
			}
			States.TryGetValue(((Object)container).GetInstanceID(), out var value);
			return value;
		}

		internal static void RequestAdd(Container container, Inventory srcInv, ItemData item, int amount, int wantX, int wantY, Action<ZPackage, TxStatus, uint> onDone)
		{
			RequestAdd(container, srcInv, item, amount, wantX, wantY, onDone, alreadyRemoved: false);
		}

		internal static void RequestAdd(Container container, Inventory srcInv, ItemData item, int amount, int wantX, int wantY, Action<ZPackage, TxStatus, uint> onDone, bool alreadyRemoved)
		{
			Inventory val = (((Object)(object)container != (Object)null) ? container.GetInventory() : null);
			if (val != null && wantX >= 0 && wantY >= 0 && (wantX >= val.GetWidth() || wantY >= val.GetHeight()))
			{
				TxLog.Info("want-clamped (" + wantX + "," + wantY + ") vs " + val.GetWidth() + "x" + val.GetHeight());
				wantX = -1;
				wantY = -1;
			}
			TxOpItem opItem = SnapshotItem(item, amount, wantX, wantY);
			if (opItem == null)
			{
				TellPlayer("Item cannot be sent (no prefab).");
				return;
			}
			if (wantX < 0 || wantY < 0)
			{
				opItem.X = -1;
				opItem.Y = -1;
			}
			TxOpCall txOpCall = new TxOpCall();
			txOpCall.Op = TxOp.Add;
			txOpCall.Items.Add(opItem);
			Submit(container, txOpCall, delegate(ZPackage pkg, TxStatus status, uint rev)
			{
				int accepted = ReadAcceptedAt(pkg, 0);
				CompleteAdd(srcInv, item, opItem, accepted, status, rev, container, onDone, alreadyRemoved);
			}, 0L, null, 1);
		}

		internal static void RequestAddBatch(Container container, Inventory srcInv, List<TxOpItem> items, Action<ZPackage, TxStatus, uint> onDone)
		{
			if (items == null || items.Count == 0)
			{
				return;
			}
			TxOpCall txOpCall = new TxOpCall();
			txOpCall.Op = TxOp.AddBatch;
			txOpCall.Items.AddRange(items);
			Submit(container, txOpCall, delegate(ZPackage pkg, TxStatus status, uint rev)
			{
				List<int> list = ReadAcceptedList(pkg, items.Count);
				for (int i = 0; i < items.Count && i < list.Count; i++)
				{
					CompleteAdd(srcInv, null, items[i], list[i], status, rev, container, null);
				}
				RefreshNow(container);
				if (onDone != null)
				{
					onDone(pkg, status, rev);
				}
			}, 0L, null, items.Count);
		}

		internal static TxOpItem SnapshotAuto(ItemData item, int amount)
		{
			TxOpItem txOpItem = SnapshotItem(item, amount, -1, -1);
			if (txOpItem != null)
			{
				txOpItem.X = -1;
				txOpItem.Y = -1;
			}
			return txOpItem;
		}

		internal static TxOpItem SnapshotItem(ItemData item, int amount, int wantX, int wantY)
		{
			if (item == null || item.m_shared == null || (Object)(object)item.m_dropPrefab == (Object)null || amount <= 0)
			{
				return null;
			}
			ItemData val = item.Clone();
			val.m_stack = amount;
			TxOpItem txOpItem = new TxOpItem();
			txOpItem.Snapshot = val;
			txOpItem.SourceRef = item;
			txOpItem.PrefabHash = StringExtensionMethods.GetStableHashCode(((Object)item.m_dropPrefab).name);
			txOpItem.Amount = amount;
			txOpItem.X = item.m_gridPos.x;
			txOpItem.Y = item.m_gridPos.y;
			txOpItem.MaxStack = item.m_shared.m_maxStackSize;
			if (wantX >= 0 && wantY >= 0)
			{
				txOpItem.X = wantX;
				txOpItem.Y = wantY;
			}
			return txOpItem;
		}

		internal static void RequestTake(Container container, Inventory dstInv, ItemData snapshot, int amount, Action<ZPackage, TxStatus, uint> onDone, int wantDstX = -1, int wantDstY = -1)
		{
			TxOpItem txOpItem = SnapshotItem(snapshot, amount, -1, -1);
			if (txOpItem != null)
			{
				TxOpCall txOpCall = new TxOpCall();
				txOpCall.Op = TxOp.Take;
				txOpCall.Items.Add(txOpItem);
				Submit(container, txOpCall, delegate(ZPackage pkg, TxStatus status, uint rev)
				{
					CompleteTake(dstInv, pkg, status, rev, container, onDone, wantDstX, wantDstY);
				}, 0L);
			}
		}

		internal static void RequestTakeBatchChunked(Container container, Inventory dstInv, List<TxOpItem> items, Action<ZPackage, TxStatus, uint> onDone)
		{
			if (items == null || items.Count == 0)
			{
				return;
			}
			if (items.Count <= 4)
			{
				RequestTakeBatch(container, dstInv, items, onDone);
				return;
			}
			for (int i = 0; i < items.Count; i += 4)
			{
				List<TxOpItem> list = new List<TxOpItem>();
				for (int j = i; j < items.Count && j < i + 4; j++)
				{
					list.Add(items[j]);
				}
				RequestTakeBatch(container, dstInv, list, onDone);
			}
		}

		internal static void RequestTakeBatch(Container container, Inventory dstInv, List<TxOpItem> items, Action<ZPackage, TxStatus, uint> onDone)
		{
			if (items != null && items.Count != 0)
			{
				TxOpCall txOpCall = new TxOpCall();
				txOpCall.Op = TxOp.TakeBatch;
				txOpCall.Items.AddRange(items);
				Submit(container, txOpCall, delegate(ZPackage pkg, TxStatus status, uint rev)
				{
					CompleteTake(dstInv, pkg, status, rev, container, onDone);
				}, 0L);
			}
		}

		internal static void RequestMove(Container container, ItemData snapshot, int amount, int dstX, int dstY, Action<ZPackage, TxStatus, uint> onDone)
		{
			TxOpItem txOpItem = SnapshotItem(snapshot, amount, -1, -1);
			if (txOpItem == null)
			{
				return;
			}
			TxOpCall txOpCall = new TxOpCall();
			txOpCall.Op = TxOp.Move;
			txOpCall.Items.Add(txOpItem);
			txOpCall.DstX = dstX;
			txOpCall.DstY = dstY;
			Submit(container, txOpCall, delegate(ZPackage pkg, TxStatus status, uint rev)
			{
				RefreshNow(container);
				if (status == TxStatus.Rejected)
				{
					TellPlayer("The shared chest changed. Try that item move again.");
				}
				if (onDone != null)
				{
					onDone(pkg, status, rev);
				}
			}, 0L);
		}

		internal static void RequestSort(Container container, int mode, bool desc)
		{
			TxOpCall txOpCall = new TxOpCall();
			txOpCall.Op = TxOp.Sort;
			txOpCall.Mode = mode;
			txOpCall.Desc = desc;
			Submit(container, txOpCall, delegate(ZPackage pkg, TxStatus status, uint rev)
			{
				RefreshNow(container);
				if (status != TxStatus.Accepted && status != TxStatus.Duplicate)
				{
					TellPlayer("Chest sort failed. Try again.");
				}
			}, 0L);
		}

		internal static void RequestSetRule(Container container, string rule)
		{
			TxOpCall txOpCall = new TxOpCall();
			txOpCall.Op = TxOp.SetRule;
			txOpCall.Rule = rule ?? string.Empty;
			Submit(container, txOpCall, delegate(ZPackage pkg, TxStatus status, uint rev)
			{
				RefreshNow(container);
				if (status != TxStatus.Accepted && status != TxStatus.Duplicate)
				{
					TellPlayer("Chest rule was not saved. Try again.");
				}
			}, 0L);
		}

		internal static void MutateLocal(Container container, TxOpCall call, Action<StoredResult> onDone, long playerId = 0L, Vector3? actorPos = null)
		{
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			ChestState state = GetState(container);
			if (state == null || !container.IsOwner())
			{
				if (onDone != null)
				{
					StoredResult storedResult = new StoredResult();
					storedResult.Status = TxStatus.Rejected;
					onDone(storedResult);
				}
				return;
			}
			if (!TryClaimAddItems(call, out var claimed))
			{
				TxLog.Info("local add skipped: all items already in flight");
				if (onDone != null)
				{
					StoredResult storedResult2 = new StoredResult();
					storedResult2.Op = call.Op;
					storedResult2.Status = TxStatus.Accepted;
					storedResult2.Revision = CurrentRevision(container);
					onDone(storedResult2);
				}
				return;
			}
			try
			{
				TxJob txJob = new TxJob();
				txJob.Container = container;
				txJob.IsLocal = true;
				txJob.TxId = NextLocalTxId();
				txJob.Sender = ZNet.GetUID();
				txJob.PlayerId = ((playerId != 0L) ? playerId : LocalPlayerId());
				txJob.ActorPos = (actorPos.HasValue ? actorPos.Value : LocalActorPos());
				txJob.BaseRev = CurrentRevision(container);
				txJob.Call = call;
				txJob.Complete = onDone;
				state.Queue.Enqueue(txJob);
				Drain(state);
			}
			finally
			{
				ReleaseClaimed(claimed);
			}
		}

		internal static void DrainForLocal(Container container)
		{
			ChestState state = GetState(container);
			if (state != null && container.IsOwner())
			{
				Drain(state);
			}
		}

		internal static bool AcquireForStructural(Container container)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected O, but got Unknown
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			if (!IsShared(container))
			{
				return false;
			}
			if (container.IsOwner())
			{
				return true;
			}
			ZNetView netView = TxReflect.GetNetView(container);
			if ((Object)(object)netView == (Object)null || !netView.IsValid())
			{
				return false;
			}
			try
			{
				netView.ClaimOwnership();
				if (!container.IsOwner())
				{
					return false;
				}
				byte[] byteArray = netView.GetZDO().GetByteArray(ZDOVars.s_items, (byte[])null);
				if (byteArray != null)
				{
					TxReflect.LoadInventoryQuiet(container, new ZPackage(byteArray));
					TxReflect.SetLastRevision(container, netView.GetZDO().DataRevision);
					TxReflect.UpdateRows(container);
				}
				ChestState state = GetState(container);
				if (state != null)
				{
					RejectQueued(state);
					state.Queue.Clear();
					state.Processed.Clear();
					state.ProcOrder.Clear();
					SeedFromRing(state, ReadRing(container));
					state.LastOwner = ZNet.GetUID();
				}
				TxLog.Info("container=" + TxLog.Zid(netView.GetZDO().m_uid) + " structural acquire by " + ZNet.GetUID());
				return true;
			}
			catch (Exception ex)
			{
				TxLog.Warn("structural acquire failed: " + ex.Message);
				return false;
			}
		}

		private static long NextLocalTxId()
		{
			return TxIdGen.Next(ZNet.GetUID(), ref _clientCounter);
		}

		private static bool TryClaimAddItems(TxOpCall call, out List<ItemData> claimed)
		{
			claimed = null;
			if (call == null || (call.Op != TxOp.Add && call.Op != TxOp.AddBatch))
			{
				return true;
			}
			for (int num = call.Items.Count - 1; num >= 0; num--)
			{
				ItemData val = call.Items[num]?.SourceRef;
				if (val != null)
				{
					if (InFlightAdds.Add(val))
					{
						if (claimed == null)
						{
							claimed = new List<ItemData>();
						}
						claimed.Add(val);
					}
					else
					{
						call.Items.RemoveAt(num);
						TxLog.Info("add submit pruned duplicate in-flight item (already sending)");
					}
				}
			}
			return call.Items.Count > 0;
		}

		private static void ReleaseClaimed(List<ItemData> claimed)
		{
			if (claimed == null)
			{
				return;
			}
			for (int i = 0; i < claimed.Count; i++)
			{
				if (claimed[i] != null)
				{
					InFlightAdds.Remove(claimed[i]);
				}
			}
		}

		private static void Submit(Container container, TxOpCall call, Action<ZPackage, TxStatus, uint> onDone, long playerId = 0L, Vector3? actorPos = null, int expectedItems = -1)
		{
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Expected O, but got Unknown
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Expected O, but got Unknown
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.IsActive || !IsShared(container))
			{
				if (AutoFeedService.IsLocked(container))
				{
					TellPlayer("Chest is busy (animal feeding in progress). Try again shortly.");
				}
				else
				{
					TellPlayer("Shared chest is not available.");
				}
				RejectEarly(container, onDone);
				return;
			}
			if (container.IsOwner())
			{
				MutateLocal(container, call, delegate(StoredResult r)
				{
					RefreshNow(container);
					if (onDone != null)
					{
						ZPackage val3 = null;
						try
						{
							val3 = EncodeResultBody(call, r);
							if (val3 != null)
							{
								val3.SetPos(0);
							}
						}
						catch
						{
						}
						onDone(val3, r.Status, r.Revision);
					}
				}, playerId, actorPos);
				return;
			}
			if (!TryClaimAddItems(call, out var claimed))
			{
				TxLog.Info("add submit skipped: all items already in flight");
				if (onDone == null)
				{
					return;
				}
				try
				{
					onDone(new ZPackage(), TxStatus.Accepted, CurrentRevision(container));
					return;
				}
				catch (Exception ex)
				{
					TxLog.Error("pruned-add completion failed: " + ex.Message);
					return;
				}
			}
			long num = TxIdGen.Next(ZNet.GetUID(), ref _clientCounter);
			ZPackage val = EncodeCall(call, CurrentRevision(container), playerId, actorPos);
			ZPackage val2 = new ZPackage();
			val2.Write(num);
			val2.Write(val);
			ZNetView netView = TxReflect.GetNetView(container);
			if ((Object)(object)netView == (Object)null || !netView.IsValid())
			{
				ReleaseClaimed(claimed);
				TellPlayer("Shared chest is not available.");
				RejectEarly(container, onDone);
				return;
			}
			PendingTx pendingTx = new PendingTx();
			pendingTx.TxId = num;
			pendingTx.Container = container;
			pendingTx.Payload = val2;
			pendingTx.Op = call.Op;
			pendingTx.Claimed = claimed;
			pendingTx.Attempts = 0;
			pendingTx.ExpectedItems = expectedItems;
			pendingTx.NextTryAt = 0f;
			pendingTx.Deadline = Time.realtimeSinceStartup + 16f;
			pendingTx.OnResponse = onDone;
			Pending[num] = pendingTx;
			TxLog.Info("container=" + TxLog.Zid(netView.GetZDO().m_uid) + " tx=" + num + " peer=" + ZNet.GetUID() + " op=" + call.Op.ToString() + " " + DescribeCall(call) + " SEND");
			SendPayload(container, val2);
			pendingTx.Attempts = 1;
			pendingTx.NextTryAt = Time.realtimeSinceStartup + 3f;
		}

		private static void RejectEarly(Container container, Action<ZPackage, TxStatus, uint> onDone)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			if (onDone == null)
			{
				return;
			}
			try
			{
				onDone(new ZPackage(), TxStatus.Rejected, CurrentRevision(container));
			}
			catch (Exception ex)
			{
				TxLog.Error("early-reject completion failed: " + ex.Message);
			}
		}

		private static void SendPayload(Container container, ZPackage request)
		{
			ZNetView netView = TxReflect.GetNetView(container);
			if ((Object)(object)netView == (Object)null || !netView.IsValid())
			{
				return;
			}
			try
			{
				netView.InvokeRPC("BestAutoSort_TxRequest", new object[1] { request });
			}
			catch (Exception ex)
			{
				TxLog.Warn("send failed: " + ex.Message);
			}
		}

		private static string DescribeCall(TxOpCall call)
		{
			try
			{
				if (call.Items.Count == 0)
				{
					return "";
				}
				TxOpItem txOpItem = call.Items[0];
				string text = ((txOpItem.Snapshot != null && txOpItem.Snapshot.m_shared != null) ? txOpItem.Snapshot.m_shared.m_name : "?");
				string text2 = "item=" + text + " x" + txOpItem.Amount + " want=(" + txOpItem.X + "," + txOpItem.Y + ")";
				if (call.Items.Count > 1)
				{
					text2 = text2 + " (+" + (call.Items.Count - 1) + " more)";
				}
				return text2;
			}
			catch (Exception)
			{
				return "";
			}
		}

		private static ZPackage EncodeCall(TxOpCall call, uint baseRev, long playerId, Vector3? actorPos)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = new ZPackage();
			val.Write(2);
			val.Write((int)call.Op);
			val.Write(baseRev);
			val.Write((playerId != 0L) ? playerId : LocalPlayerId());
			val.Write(actorPos.HasValue ? actorPos.Value : LocalActorPos());
			val.Write(call.EnforceRule);
			val.Write(call.RespectReserves);
			switch (call.Op)
			{
			case TxOp.Add:
			case TxOp.AddBatch:
			case TxOp.Take:
			case TxOp.TakeBatch:
			{
				val.Write(call.Items.Count);
				for (int i = 0; i < call.Items.Count; i++)
				{
					WriteOpItem(val, call.Items[i]);
				}
				break;
			}
			case TxOp.Move:
				WriteOpItem(val, call.Items[0]);
				val.Write(call.DstX);
				val.Write(call.DstY);
				break;
			case TxOp.Sort:
				val.Write(call.Mode);
				val.Write(call.Desc);
				break;
			case TxOp.Upgrade:
				val.Write(call.Tier);
				break;
			case TxOp.SetRule:
				val.Write(call.Rule ?? string.Empty);
				break;
			}
			return val;
		}

		private static void WriteOpItem(ZPackage pkg, TxOpItem item)
		{
			//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();
			item.Snapshot.Save(val);
			pkg.Write(item.PrefabHash);
			pkg.Write(val);
			pkg.Write(item.Amount);
			pkg.Write(item.X);
			pkg.Write(item.Y);
			pkg.Write(item.MaxStack);
		}

		private static void OnTxRequest(Container container, long sender, ZPackage request)
		{
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Expected O, but got Unknown
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Expected O, but got Unknown
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.IsActive || !IsShared(container) || !container.IsOwner())
			{
				return;
			}
			long txId;
			ZPackage payload;
			try
			{
				txId = request.ReadLong();
				payload = request.ReadPackage();
			}
			catch (Exception ex)
			{
				TxLog.Warn("bad request packet from " + sender + ": " + ex.Message);
				return;
			}
			if (!DecodeCall(payload, out var call, out var baseRev, out var playerId, out var actorPos))
			{
				TxLog.Warn("tx=" + txId + " undecodable request from " + sender);
				return;
			}
			if (!TxNet.IsCompatiblePeer(sender) && sender != ZNet.GetUID())
			{
				TxLog.Warn("tx=" + txId + " REJECT incompatible peer " + sender);
				Respond(container, sender, txId, TxStatus.Rejected, CurrentRevision(container), new ZPackage(), totalsOnly: true, call.Op);
				return;
			}
			if (call.Op == TxOp.ViewerOpen || call.Op == TxOp.ViewerClose)
			{
				HandlePresence(container, sender, call.Op);
				return;
			}
			if (call.Op == TxOp.Query)
			{
				RespondQuery(container, sender, txId);
				return;
			}
			if (!ChestAuthority.CanUse(container, sender, playerId, actorPos, out var why))
			{
				TxLog.Warn("tx=" + txId + " REJECT access(" + why + ") peer=" + sender);
				Respond(container, sender, txId, TxStatus.Rejected, CurrentRevision(container), new ZPackage(), totalsOnly: true, call.Op);
				return;
			}
			ChestState state = GetState(container);
			if (state != null)
			{
				TxJob txJob = new TxJob();
				txJob.Container = container;
				txJob.TxId = txId;
				txJob.Sender = sender;
				txJob.PlayerId = playerId;
				txJob.BaseRev = baseRev;
				txJob.ActorPos = actorPos;
				txJob.Call = call;
				txJob.Complete = delegate(StoredResult r)
				{
					ZPackage body = EncodeResultBody(call, r);
					Respond(container, sender, txId, r.Status, r.Revision, body, r.TotalsOnly, call.Op);
				};
				state.Queue.Enqueue(txJob);
				Drain(state);
			}
		}

		private static bool DecodeCall(ZPackage payload, out TxOpCall call, out uint baseRev, out long playerId, out Vector3 actorPos)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: 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_0045: Unknown result type (might be due to invalid IL or missing references)
			call = null;
			baseRev = 0u;
			playerId = 0L;
			actorPos = Vector3.zero;
			try
			{
				if (payload.ReadInt() != 2)
				{
					return false;
				}
				TxOp txOp = (TxOp)payload.ReadInt();
				baseRev = payload.ReadUInt();
				playerId = payload.ReadLong();
				actorPos = payload.ReadVector3();
				call = new TxOpCall();
				call.Op = txOp;
				call.EnforceRule = payload.ReadBool();
				call.RespectReserves = payload.ReadBool();
				switch (txOp)
				{
				case TxOp.Add:
				case TxOp.AddBatch:
				case TxOp.Take:
				case TxOp.TakeBatch:
				{
					int num = payload.ReadInt();
					if (num < 0 || num > 64)
					{
						return false;
					}
					for (int i = 0; i < num; i++)
					{
						if (!ReadOpItem(payload, out var item2))
						{
							return false;
						}
						call.Items.Add(item2);
					}
					break;
				}
				case TxOp.Move:
				{
					if (!ReadOpItem(payload, out var item))
					{
						return false;
					}
					call.Items.Add(item);
					call.DstX = payload.ReadInt();
					call.DstY = payload.ReadInt();
					break;
				}
				case TxOp.Sort:
					call.Mode = payload.ReadInt();
					call.Desc = payload.ReadBool();
					break;
				case TxOp.Upgrade:
					call.Tier = payload.ReadInt();
					break;
				case TxOp.SetRule:
					call.Rule = payload.ReadString();
					break;
				default:
					return false;
				case TxOp.ViewerOpen:
				case TxOp.ViewerClose:
				case TxOp.Query:
					break;
				}
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static bool ReadOpItem(ZPackage payload, out TxOpItem item)
		{
			item = null;
			try
			{
				int num = payload.ReadInt();
				ZPackage inner = payload.ReadPackage();
				int num2 = payload.ReadInt();
				int x = payload.ReadInt();
				int y = payload.ReadInt();
				int num3 = payload.ReadInt();
				if (num == 0 || num2 <= 0 || num2 > 9999)
				{
					return false;
				}
				ItemData val = TxCodec.ResolvePrefab(num, inner);
				if (val == null)
				{
					return false;
				}
				item = new TxOpItem();
				item.Snapshot = val;
				item.PrefabHash = num;
				item.Amount = num2;
				item.X = x;
				item.Y = y;
				item.MaxStack = ((num3 <= 0) ? 50 : num3);
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static void Drain(ChestState state)
		{
			if (state == null || state.Draining || (Object)(object)state.Container == (Object)null || !state.Container.IsOwner())
			{
				return;
			}
			state.Draining = true;
			try
			{
				int num = 0;
				while (state.Queue.Count > 0 && num < 64)
				{
					num++;
					TxJob txJob = state.Queue.Dequeue();
					Stopwatch stopwatch = Stopwatch.StartNew();
					StoredResult storedResult;
					try
					{
						storedResult = ApplyJob(state, txJob);
						stopwatch.Stop();
						if (stopwatch.ElapsedMilliseconds > 100)
						{
							TxLog.Warn("tx=" + txJob.TxId + " op=" + ((txJob.Call != null) ? txJob.Call.Op.ToString() : "?") + " slow apply: " + stopwatch.ElapsedMilliseconds + " ms");
						}
					}
					catch (Exception ex)
					{
						TxLog.Error("tx=" + txJob.TxId + " apply failed: " + ex.Message);
						storedResult = new StoredResult();
						storedResult.Op = ((txJob.Call != null) ? txJob.Call.Op : TxOp.Query);
						storedResult.Status = TxStatus.Rejected;
						storedResult.Revision = CurrentRevision(state.Container);
					}
					try
					{
						if (txJob.Complete != null)
						{
							txJob.Complete(storedResult);
						}
					}
					catch (Exception ex2)
					{
						TxLog.Error("tx=" + txJob.TxId + " completion failed: " + ex2.Message);
					}
					if ((txJob.Call.Op == TxOp.Add || txJob.Call.Op == TxOp.AddBatch || txJob.Call.Op == TxOp.TakeBatch) && storedResult.AcceptedTotal() > 0)
					{
						TxFlights.BroadcastFlights(state, txJob, storedResult);
					}
				}
			}
			finally
			{
				state.Draining = false;
			}
		}

		private static StoredResult ApplyJob(ChestState state, TxJob job)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			if (state.Processed.TryGetValue(job.TxId, out var value))
			{
				TxLog.Info("container=" + TxLog.Zid(state.ZdoId) + " tx=" + job.TxId + " DUPLICATE returning cached result");
				return CloneStored(value, TxStatus.Duplicate);
			}
			Inventory inventory = job.Container.GetInventory();
			if (inventory == null)
			{
				return Reject(state, job);
			}
			StoredResult storedResult = ExecuteCall(state, job, inventory);
			CommitResult(state, job, storedResult);
			uint baseRev = job.BaseRev;
			uint num = CurrentRevision(job.Container);
			if (baseRev != 0 && baseRev != num && baseRev != storedResult.Revision)
			{
				TxLog.Info("container=" + TxLog.Zid(state.ZdoId) + " tx=" + job.TxId + " stale_revision client=" + baseRev + " server=" + num);
			}
			return storedResult;
		}

		private static StoredResult ExecuteCall(ChestState state, TxJob job, Inventory inv)
		{
			switch (job.Call.Op)
			{
			case TxOp.Add:
			case TxOp.AddBatch:
				return ExecuteAdd(inv, job.Call, state);
			case TxOp.Take:
			case TxOp.TakeBatch:
				return ExecuteTake(inv, job.Call, state);
			case TxOp.Move:
				return ExecuteMove(inv, job.Call);
			case TxOp.Sort:
				return ExecuteSort(inv, job.Call);
			case TxOp.Upgrade:
				return ExecuteUpgrade(state, job);
			case TxOp.SetRule:
				return ExecuteSetRule(state, job);
			default:
				return RejectNew();
			}
		}

		private static StoredResult ExecuteAdd(Inventory inv, TxOpCall call, ChestState state)
		{
			StoredResult storedResult = new StoredResult();
			storedResult.Op = call.Op;
			HashSet<string> hashSet = null;
			HashSet<ItemCategory> hashSet2 = null;
			ChestStorageRule rule = null;
			if (call.EnforceRule)
			{
				hashSet = new HashSet<string>();
				hashSet2 = new HashSet<ItemCategory>();
				QuickStackTransfer.DestSeeds(inv, hashSet, hashSet2);
				rule = ChestRuleStore.Read(state.Container);
			}
			int num = 0;
			for (int i = 0; i < call.Items.Count; i++)
			{
				TxOpItem txOpItem = call.Items[i];
				string name = ((txOpItem.Snapshot.m_shared != null) ? txOpItem.Snapshot.m_shared.m_name : "?");
				if (txOpItem.X >= inv.GetWidth() || txOpItem.Y >= inv.GetHeight())
				{
					TxLog.Info("op=ADD item=" + TxCodec.Describe(name, txOpItem.Amount) + " want=(" + txOpItem.X + "," + txOpItem.Y + ") oob vs " + inv.GetWidth() + "x" + inv.GetHeight() + ", auto-place");
					txOpItem.X = -1;
					txOpItem.Y = -1;
				}
				ItemData val = txOpItem.Snapshot.Clone();
				val.m_stack = txOpItem.Amount;
				int num2 = 0;
				string text = "";
				if (!call.EnforceRule || QuickStackTransfer.CanAcceptFromRule(rule, val, hashSet, hashSet2))
				{
					if (txOpItem.X >= 0 && txOpItem.Y >= 0)
					{
						num2 = TxInventory.AddPositional(inv, val, txOpItem.Amount, txOpItem.X, txOpItem.Y);
						if (num2 < txOpItem.Amount)
						{
							ItemData val2 = null;
							string text2 = "oob";
							if (txOpItem.X < inv.GetWidth() && txOpItem.Y < inv.GetHeight())
							{
								val2 = inv.GetItemAt(txOpItem.X, txOpItem.Y);
								text2 = ((val2 != null && val2.m_shared != null) ? (val2.m_shared.m_name + " x" + val2.m_stack) : "empty");
							}
							text = ((num2 > 0) ? "partial-pos" : "pos-blocked") + " chest=" + inv.GetWidth() + "x" + inv.GetHeight() + " cell=" + text2;
						}
					}
					else
					{
						num2 = TxInventory.AddAndCount(inv, val);
						if (num2 < txOpItem.Amount)
						{
							text = ((num2 > 0) ? "partial-full" : "full");
						}
					}
					if (num2 > 0 && call.EnforceRule)
					{
						hashSet.Add(val.m_shared.m_name);
						hashSet2.Add(ValheimItemCategoryClassifier.Classify(val));
					}
				}
				else
				{
					text = "rule";
				}
				TxLog.Info("op=ADD item=" + TxCodec.Describe(name, txOpItem.Amount) + " want=(" + txOpItem.X + "," + txOpItem.Y + ") accepted=" + num2 + ((text.Length > 0) ? (" why=" + text) : ""));
				storedResult.Accepted.Add(num2);
				if (num2 == txOpItem.Amount)
				{
					num++;
				}
			}
			storedResult.Status = ((num != storedResult.Accepted.Count || storedResult.Accepted.Count <= 0) ? ((storedResult.AcceptedTotal() > 0) ? TxStatus.Partial : TxStatus.Rejected) : TxStatus.Accepted);
			return storedResult;
		}

		private static StoredResult ExecuteTake(Inventory inv, TxOpCall call, ChestState state)
		{
			StoredResult storedResult = new StoredResult();
			storedResult.Op = call.Op;
			int num = 0;
			for (int i = 0; i < call.Items.Count; i++)
			{
				TxOpItem txOpItem = call.Items[i];
				TakeEntry entry;
				int num2 = ((!call.RespectReserves) ? TakeSingle(inv, txOpItem, out entry) : TakeRespectingReserves(state, inv, txOpItem, out entry));
				storedResult.Accepted.Add(num2);
				storedResult.Takes.Add(entry);
				if (num2 == txOpItem.Amount)
				{
					num++;
				}
				TxLog.Info("op=TAKE item=" + TxCodec.Describe(txOpItem.Snapshot.m_shared.m_name, txOpItem.Amount) + " requested=" + txOpItem.Amount + " taken=" + num2);
			}
			storedResult.Status = ((num != storedResult.Accepted.Count || storedResult.Accepted.Count <= 0) ? ((storedResult.AcceptedTotal() > 0) ? TxStatus.Partial : TxStatus.Rejected) : TxStatus.Accepted);
			return storedResult;
		}

		private static int TakeSingle(Inventory inv, TxOpItem it, out TakeEntry entry)
		{
			entry = null;
			ItemData val = TxCodec.ResolveIn(inv, it.Snapshot.m_shared.m_name, it.Snapshot.m_quality, it.Snapshot.m_variant, it.Snapshot.m_worldLevel, it.X, it.Y);
			if (val == null)
			{
				return 0;
			}
			int num = ((it.Amount < val.m_stack) ? it.Amount : val.m_stack);
			ItemData val2 = val.Clone();
			val2.m_stack = num;
			int num2 = TxInventory.RemoveExact(inv, val, num);
			if (num2 <= 0)
			{
				return 0;
			}
			val2.m_stack = num2;
			entry = new TakeEntry();
			entry.PrefabHash = it.PrefabHash;
			entry.Item = val2;
			entry.Accepted = num2;
			return num2;
		}

		private static int TakeRespectingReserves(ChestState state, Inventory inv, TxOpItem it, out TakeEntry entry)
		{
			entry = null;
			List<ItemData> list = TxCodec.ResolveAllIn(inv, it.Snapshot.m_shared.m_name, it.Snapshot.m_quality, it.Snapshot.m_variant, it.Snapshot.m_worldLevel);
			if (list.Count == 0)
			{
				return 0;
			}
			int num = it.Amount;
			int num2 = 0;
			ItemData val = null;
			foreach (ItemData item in list)
			{
				if (num <= 0)
				{
					break;
				}
				int num3 = ChestReserveStore.Available(state.Container, item);
				if (num3 > 0)
				{
					int num4 = ((num < item.m_stack) ? num : item.m_stack);
					if (num4 > num3)
					{
						num4 = num3;
					}
					if (val == null)
					{
						val = item.Clone();
					}
					int num5 = TxInventory.RemoveExact(inv, item, num4);
					num2 += num5;
					num -= num5;
				}
			}
			if (num2 <= 0)
			{
				return 0;
			}
			val.m_stack = num2;
			entry = new TakeEntry();
			entry.PrefabHash = it.PrefabHash;
			entry.Item = val;
			entry.Accepted = num2;
			return num2;
		}

		private static StoredResult ExecuteMove(Inventory inv, TxOpCall call)
		{
			StoredResult storedResult = new StoredResult();
			storedResult.Op = call.Op;
			if (call.Items.Count == 0)
			{
				storedResult.Status = TxStatus.Rejected;
				storedResult.Accepted.Add(0);
				return storedResult;
			}
			TxOpItem txOpItem = call.Items[0];
			ItemData val = TxCodec.ResolveIn(inv, txOpItem.Snapshot.m_shared.m_name, txOpItem.Snapshot.m_quality, txOpItem.Snapshot.m_variant, txOpItem.Snapshot.m_worldLevel, txOpItem.X, txOpItem.Y);
			int num = 0;
			if (val != null)
			{
				num = TxInventory.MoveWithin(inv, val, txOpItem.Amount, call.DstX, call.DstY);
			}
			storedResult.Status = ((num <= 0) ? TxStatus.Rejected : TxStatus.Accepted);
			storedResult.Accepted.Add(num);
			TxLog.Info("op=MOVE item=" + TxCodec.Describe(txOpItem.Snapshot.m_shared.m_name, txOpItem.Amount) + " dst=(" + call.DstX + "," + call.DstY + ") moved=" + num);
			return storedResult;
		}

		private static StoredResult ExecuteSort(Inventory inv, TxOpCall call)
		{
			StoredResult storedResult = new StoredResult();
			storedResult.Op = call.Op;
			int item = InventorySorter.Sort(inv, (SortMode)call.Mode, call.Desc);
			storedResult.Status = TxStatus.Accepted;
			storedResult.Accepted.Add(item);
			TxLog.Info("op=SORT mode=" + call.Mode + " desc=" + call.Desc + " items=" + item);
			return storedResult;
		}

		private static StoredResult ExecuteUpgrade(ChestState state, TxJob job)
		{
			StoredResult storedResult = new StoredResult();
			storedResult.Op = job.Call.Op;
			if ((Object)(object)InventoryAccess.CurrentContainer(InventoryGui.instance) != (Object)(object)state.Container)
			{
				storedResult.Status = TxStatus.Rejected;
				storedResult.Accepted.Add(0);
				TxLog.Info("op=UPGRADE REJECT manager-gui-mismatch tier=" + job.Call.Tier);
				return storedResult;
			}
			ChestUpgradeService.UpgradeOpenChest(job.Call.Tier);
			storedResult.Status = TxStatus.Accepted;
			storedResult.Accepted.Add(1);
			return storedResult;
		}

		private static StoredResult ExecuteSetRule(ChestState state, TxJob job)
		{
			StoredResult storedResult = new StoredResult();
			storedResult.Op = job.Call.Op;
			if (!ChestStorageRuleCodec.TryDeserialize(job.Call.Rule ?? string.Empty, out var rule))
			{
				storedResult.Status = TxStatus.Rejected;
				storedResult.Accepted.Add(0);
				return storedResult;
			}
			if (!ChestRuleStore.TryWrite(state.Container, rule, out var error))
			{
				storedResult.Status = TxStatus.Rejected;
				storedResult.Accepted.Add(0);
				TxLog.Info("op=SETRULE REJECT " + error);
				return storedResult;
			}
			storedResult.Status = TxStatus.Accepted;
			storedResult.Accepted.Add(1);
			return storedResult;
		}

		private static void CommitResult(ChestState state, TxJob job, StoredResult result)
		{
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			bool flag = result.Status == TxStatus.Accepted || result.Status == TxStatus.Partial;
			if (flag)
			{
				TxReflect.UpdateRows(state.Container);
				TxReflect.SaveContainer(state.Container);
			}
			result.Revision = CurrentRevision(state.Container);
			state.Processed[job.TxId] = CloneStored(result, result.Status);
			state.ProcOrder.AddLast(job.TxId);
			while (state.ProcOrder.Count > 128)
			{
				long value = state.ProcOrder.First.Value;
				state.ProcOrder.RemoveFirst();
				state.Processed.Remove(value);
			}
			if (flag)
			{
				WriteRing(state);
				TxReflect.SetLastRevision(state.Container, CurrentRevision(state.Container));
			}
			TxLog.Info("container=" + TxLog.Zid(state.ZdoId) + " tx=" + job.TxId + " peer=" + job.Sender + " op=" + job.Call.Op.ToString() + " accepted=" + result.AcceptedTotal() + " revision=" + result.Revision);
		}

		private static StoredResult Reject(ChestState state, TxJob job)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			StoredResult storedResult = new StoredResult();
			storedResult.Op = job.Call.Op;
			storedResult.Status = TxStatus.Rejected;
			storedResult.Revision = CurrentRevision(job.Container);
			TxLog.Info("container=" + TxLog.Zid(state.ZdoId) + " tx=" + job.TxId + " REJECT op=" + job.Call.Op);
			return storedResult;
		}

		private static StoredResult RejectNew()
		{
			return new StoredResult
			{
				Status = TxStatus.Rejected
			};
		}

		private static StoredResult CloneStored(StoredResult src, TxStatus status)
		{
			return new StoredResult
			{
				Op = src.Op,
				Status = status,
				Revision = src.Revision,
				Accepted = new List<int>(src.Accepted),
				Takes = new List<TakeEntry>(src.Takes),
				TotalsOnly = src.TotalsOnly
			};
		}

		private static bool IsTakeOp(TxOp op)
		{
			if (op != TxOp.Take)
			{
				return op == TxOp.TakeBatch;
			}
			return true;
		}

		private static void WriteRing(ChestState state)
		{
			try
			{
				ZNetView netView = TxReflect.GetNetView(state.Container);
				if ((Object)(object)netView == (Object)null || !netView.IsValid())
				{
					return;
				}
				List<RingEntry> list = new List<RingEntry>();
				LinkedListNode<long> linkedListNode = state.ProcOrder.Last;
				int num = 0;
				RingEntry item = default(RingEntry);
				while (linkedListNode != null && num < 32)
				{
					if (state.Processed.TryGetValue(linkedListNode.Value, out var value))
					{
						item.TxId = linkedListNode.Value;
						item.AcceptedTotal = value.AcceptedTotal();
						item.Revision = value.Revision;
						list.Add(item);
						num++;
					}
					linkedListNode = linkedListNode.Previous;
				}
				list.Reverse();
				netView.GetZDO().Set("BestAutoSort_TxRing", BestAutoSort.TxCore.TxCore.EncodeRing(list));
			}
			catch (Exception ex)
			{
				TxLog.Warn("ring persist failed: " + ex.Message);
			}
		}

		private static List<RingEntry> ReadRing(Container container)
		{
			try
			{
				ZNetView netView = TxReflect.GetNetView(container);
				if ((Object)(object)netView == (Object)null || !netView.IsValid())
				{
					return new List<RingEntry>();
				}
				byte[] byteArray = netView.GetZDO().GetByteArray(StringExtensionMethods.GetStableHashCode("BestAutoSort_TxRing"), (byte[])null);
				if (byteArray == null)
				{
					return new List<RingEntry>();
				}
				return BestAutoSort.TxCore.TxCore.DecodeRing(byteArray);
			}
			catch (Exception)
			{
				return new List<RingEntry>();
			}
		}

		private static void SeedFromRing(ChestState state, List<RingEntry> ring)
		{
			state.Processed.Clear();
			state.ProcOrder.Clear();
			for (int i = 0; i < ring.Count && i < 32; i++)
			{
				StoredResult storedResult = new StoredResult();
				storedResult.Status = TxStatus.Duplicate;
				storedResult.Revision = ring[i].Revision;
				storedResult.Accepted = new List<int> { ring[i].AcceptedTotal };
				storedResult.TotalsOnly = true;
				state.Processed[ring[i].TxId] = storedResult;
				state.ProcOrder.AddLast(ring[i].TxId);
			}
		}
	}
	internal static class TxCodec
	{
		public const int ProtoVersion = 2;

		public static void WriteHeader(ZPackage pkg, long txId, TxOp op, uint baseRev, long playerId, Vector3 actorPos)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			pkg.Write(2);
			pkg.Write(txId);
			pkg.Write((int)op);
			pkg.Write(baseRev);
			pkg.Write(playerId);
			pkg.Write(actorPos);
		}

		public static bool ReadHeader(ZPackage pkg, out long txId, out TxOp op, out uint baseRev, out long playerId, out Vector3 actorPos)
		{
			//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_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			txId = 0L;
			op = (TxOp)0;
			baseRev = 0u;
			playerId = 0L;
			actorPos = Vector3.zero;
			try
			{
				if (pkg.ReadInt() != 2)
				{
					return false;
				}
				txId = pkg.ReadLong();
				op = (TxOp)pkg.ReadInt();
				baseRev = pkg.ReadUInt();
				playerId = pkg.ReadLong();
				actorPos = pkg.ReadVector3();
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		public static void WriteResponseHeader(ZPackage pkg, long txId, TxStatus status, uint revision, bool totalsOnly)
		{
			pkg.Write(txId);
			pkg.Write((int)status);
			pkg.Write(revision);
			pkg.Write(totalsOnly);
		}

		public static bool ReadResponseHeader(ZPackage pkg, out long txId, out TxStatus status, out uint revision, out bool totalsOnly)
		{
			txId = 0L;
			status = TxStatus.Rejected;
			revision = 0u;
			totalsOnly = false;
			try
			{
				txId = pkg.ReadLong();
				status = (TxStatus)pkg.ReadInt();
				revision = pkg.ReadUInt();
				totalsOnly = pkg.ReadBool();
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		public static bool WriteItem(ZPackage pkg, ItemData item, int amount)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			if (item == null || item.m_shared == null || (Object)(object)item.m_dropPrefab == (Object)null || amount <= 0)
			{
				return false;
			}
			ItemData val = item.Clone();
			val.m_stack = amount;
			ZPackage val2 = new ZPackage();
			val.Save(val2);
			pkg.Write(StringExtensionMethods.GetStableHashCode(((Object)item.m_dropPrefab).name));
			pkg.Write(val2);
			pkg.Write(amount);
			pkg.Write(val.m_gridPos.x);
			pkg.Write(val.m_gridPos.y);
			pkg.Write(item.m_shared.m_maxStackSize);
			return true;
		}

		public static bool ReadItem(ZPackage pkg, out int prefabHash, out ItemData item, out int amount, out int x, out int y, out int maxStack)
		{
			prefabHash = 0;
			item = null;
			amount = 0;
			x = -1;
			y = -1;
			maxStack = 50;
			try
			{
				prefabHash = pkg.ReadInt();
				ZPackage inner = pkg.ReadPackage();
				amount = pkg.ReadInt();
				x = pkg.ReadInt();
				y = pkg.ReadInt();
				maxStack = pkg.ReadInt();
				if (prefabHash == 0 || amount <= 0)
				{
					return false;
				}
				item = ResolvePrefab(prefabHash, inner);
				return item != null;
			}
			catch (Exception)
			{
				item = null;
				return false;
			}
		}

		public static ItemData ResolvePrefab(int prefabHash, ZPackage inner)
		{
			try
			{
				ObjectDB instance = ObjectDB.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return null;
				}
				GameObject itemPrefab = instance.GetItemPrefab(prefabHash);
				if ((Object)(object)itemPrefab == (Object)null)
				{
					return null;
				}
				ItemDrop component = itemPrefab.GetComponent<ItemDrop>();
				if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null)
				{
					return null;
				}
				ItemData val = component.m_itemData.Clone();
				ItemData.Load(inner, val, (Item)109);
				val.m_dropPrefab = itemPrefab;
				return val;
			}
			catch (Exception)
			{
				return null;
			}
		}

		public static ItemData ResolveIn(Inventory inv, string name, int quality, int variant, int world, int x, int y)
		{
			if (inv == null || string.IsNullOrEmpty(name))
			{
				return null;
			}
			if (x >= 0 && y >= 0)
			{
				ItemData itemAt = inv.GetItemAt(x, y);
				if (itemAt != null && itemAt.m_shared != null && string.Equals(itemAt.m_shared.m_name, name, StringComparison.Ordinal) && itemAt.m_quality == quality)
				{
					return itemAt;
				}
			}
			foreach (ItemData allItem in inv.GetAllItems())
			{
				if (allItem != null && allItem.m_shared != null && string.Equals(allItem.m_shared.m_name, name, StringComparison.Ordinal) && allItem.m_quality == quality && allItem.m_variant == variant && allItem.m_worldLevel == world)
				{
					return allItem;
				}
			}
			return null;
		}

		public static List<ItemData> ResolveAllIn(Inventory inv, string name, int quality, int variant, int world)
		{
			List<ItemData> list = new List<ItemData>();
			if (inv == null || string.IsNullOrEmpty(name))
			{
				return list;
			}
			foreach (ItemData allItem in inv.GetAllItems())
			{
				if (allItem != null && allItem.m_shared != null && string.Equals(allItem.m_shared.m_name, name, StringComparison.Ordinal) && allItem.m_quality == quality && allItem.m_variant == variant && allItem.m_worldLevel == world)
				{
					list.Add(allItem);
				}
			}
			return list;
		}

		public static string Describe(ItemData item)
		{
			if (item == null || item.m_shared == null)
			{
				return "null";
			}
			return item.m_shared.m_name + " x" + item.m_stack;