Decompiled source of Enchanting Satchel v1.0.0

plugins/EnchantingSatchel/EnchantingSatchel.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using AzuEPI;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using EpicLoot;
using EpicLoot.ShardStones;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("Devil_In_Law")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 Devil_In_Law")]
[assembly: AssemblyDescription("An equippable satchel for Epic Loot enchanting materials, with a dedicated AzuEPI slot and configurable storage.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("EnchantingSatchel")]
[assembly: AssemblyTitle("EnchantingSatchel")]
[assembly: AssemblyMetadata("License", "MIT")]
[assembly: AssemblyVersion("1.0.0.0")]
[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;
		}
	}
}
namespace DevilInLaw.EnchantingSatchel
{
	internal static class MaterialPolicy
	{
		private static readonly HashSet<string> Allowed = new HashSet<string>(StringComparer.Ordinal);

		internal static bool Ready { get; private set; }

		internal static void Refresh()
		{
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			Allowed.Clear();
			string[] array = new string[6] { "Shard", "Runestone", "Dust", "Essence", "Reagent", "EtchedRunestone" };
			foreach (string text in array)
			{
				string[] array2 = new string[6] { "Magic", "Rare", "Epic", "Legendary", "Mythic", "Ancient" };
				foreach (string text2 in array2)
				{
					AddRegistered(text + text2);
				}
			}
			foreach (KeyValuePair<ShardType, ShardDefinition> shardEffect in ShardDefinitions.ShardEffects)
			{
				if (shardEffect.Value.Rarities == null)
				{
					continue;
				}
				foreach (ItemRarity rarity in shardEffect.Value.Rarities)
				{
					AddRegistered($"{shardEffect.Key}_{rarity}_ShardStone");
				}
			}
			Ready = Allowed.Count > 0;
			Plugin.Trace($"Material policy refreshed: {Allowed.Count} registered material prefabs accepted.");
		}

		private static void AddRegistered(string name)
		{
			GameObject prefab = PrefabManager.Instance.GetPrefab(name);
			if (Object.op_Implicit((Object)(object)prefab) && Object.op_Implicit((Object)(object)prefab.GetComponent<ItemDrop>()))
			{
				Allowed.Add(name);
			}
			else
			{
				Plugin.Trace("Material prefab unavailable and excluded: " + name + ".");
			}
		}

		internal static bool Accepts(ItemData item)
		{
			if (item != null && Object.op_Implicit((Object)(object)item.m_dropPrefab) && !Plugin.IsSatchel(item))
			{
				return Allowed.Contains(((Object)item.m_dropPrefab).name);
			}
			return false;
		}
	}
	internal static class PickupRouting
	{
		internal sealed class Context
		{
			internal readonly Player Player;

			internal readonly GameObject WorldObject;

			internal SatchelStorage.PickupReceipt? Receipt;

			internal Context(Player player, GameObject worldObject)
			{
				Player = player;
				WorldObject = worldObject;
			}
		}

		[HarmonyPatch(typeof(Player), "AutoPickup")]
		private static class AutoScope
		{
			private static void Prefix(Player __instance, out Player? __state)
			{
				__state = AutoPlayer;
				AutoPlayer = ((Enabled && (Object)(object)__instance == (Object)(object)Player.m_localPlayer) ? __instance : null);
			}

			private static void Finalizer(Player? __state)
			{
				AutoPlayer = __state;
			}
		}

		[HarmonyPatch(typeof(Humanoid), "Pickup")]
		private static class PickupScope
		{
			private static void Prefix(Humanoid __instance, GameObject go, out Context? __state)
			{
				__state = Current;
				Current = ((Enabled && (Object)(object)__instance == (Object)(object)Player.m_localPlayer && Object.op_Implicit((Object)(object)go)) ? new Context(Player.m_localPlayer, go) : null);
			}

			private static void Finalizer(bool __result, Exception? __exception, Context? __state)
			{
				Context current = Current;
				Current = __state;
				if (current?.Receipt == null)
				{
					return;
				}
				if ((!__result || __exception != null) && Object.op_Implicit((Object)(object)current.WorldObject))
				{
					ZNetView component = current.WorldObject.GetComponent<ZNetView>();
					if (!Object.op_Implicit((Object)(object)component) || component.IsValid())
					{
						current.Receipt.Rollback();
					}
				}
				Plugin.Trace($"World pickup completed after satchel routing: success={__result}, exception={__exception != null}.");
			}
		}

		[HarmonyPatch(typeof(Inventory), "CanAddItem", new Type[]
		{
			typeof(ItemData),
			typeof(int)
		})]
		private static class AutoPickupCapacity
		{
			private static void Postfix(Inventory __instance, ItemData item, int stack, ref bool __result)
			{
				if (!__result && Enabled && Object.op_Implicit((Object)(object)AutoPlayer) && __instance == ((Humanoid)AutoPlayer).GetInventory() && MaterialPolicy.Accepts(item) && (stack <= 0 || stack == item.m_stack))
				{
					BagState bagState = SatchelStorage.EquippedBag(AutoPlayer);
					if (bagState != null && SatchelStorage.CanStorePickup(bagState, item))
					{
						__result = true;
					}
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData) })]
		private static class RouteNativePickup
		{
			[HarmonyPriority(800)]
			private static bool Prefix(Inventory __instance, ItemData item, ref bool __result)
			{
				Context current = Current;
				if (!Enabled || current == null || current.Receipt != null || !Object.op_Implicit((Object)(object)current.WorldObject) || __instance != ((Humanoid)current.Player).GetInventory() || !MaterialPolicy.Accepts(item))
				{
					return true;
				}
				ItemDrop component = current.WorldObject.GetComponent<ItemDrop>();
				if (!Object.op_Implicit((Object)(object)component) || component.m_itemData != item)
				{
					return true;
				}
				BagState bagState = SatchelStorage.EquippedBag(current.Player);
				if (bagState == null)
				{
					return true;
				}
				current.Receipt = SatchelStorage.StorePickup(bagState, item);
				if (current.Receipt == null)
				{
					Plugin.Trace("Pickup uses normal inventory: " + ((Object)item.m_dropPrefab).name + "; satchel has insufficient capacity or is unavailable.");
					return true;
				}
				__result = true;
				return false;
			}
		}

		internal static bool Enabled = true;

		[ThreadStatic]
		internal static Context? Current;

		[ThreadStatic]
		internal static Player? AutoPlayer;
	}
	[BepInPlugin("DevilInLaw.EnchantingSatchel", "Enchanting Satchel", "1.0.0")]
	[BepInDependency("com.jotunn.jotunn", "2.30.2")]
	[BepInDependency("Azumatt.AzuExtendedPlayerInventory", "2.6.3")]
	[BepInDependency("randyknapp.mods.epicloot", "0.14.13")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(Humanoid), "EquipItem")]
		private static class EquipDiagnostics
		{
			private static void Postfix(ItemData item, bool __result)
			{
				if (IsSatchel(item))
				{
					Trace($"Equip completed: success={__result}; equipped={item.m_equipped}; stack={item.m_stack}.");
				}
			}
		}

		[HarmonyPatch(typeof(Humanoid), "UnequipItem")]
		private static class UnequipDiagnostics
		{
			private static void Postfix(ItemData item)
			{
				if (IsSatchel(item))
				{
					Trace($"Unequip completed: equipped={item.m_equipped}; stack={item.m_stack}.");
				}
			}
		}

		[HarmonyPatch(typeof(Humanoid), "DropItem")]
		private static class DropDiagnostics
		{
			private static void Postfix(ItemData item, int amount, bool __result)
			{
				if (IsSatchel(item))
				{
					Trace($"Drop completed: success={__result}; requested amount={amount}; equipped={item.m_equipped}.");
				}
			}
		}

		public const string PluginGuid = "DevilInLaw.EnchantingSatchel";

		public const string PluginName = "Enchanting Satchel";

		public const string PluginVersion = "1.0.0";

		public const string Author = "Devil_In_Law";

		public const string PrefabName = "dl_EnchantingSatchel";

		public const string SlotName = "Enchanting Satchel";

		private bool itemRegistered;

		private Harmony? harmony;

		private static Plugin? instance;

		internal ConfigEntry<bool> DebugLogging { get; private set; }

		internal ConfigEntry<int> Columns { get; private set; }

		internal ConfigEntry<int> Rows { get; private set; }

		internal ConfigEntry<bool> AutoStorePickups { get; private set; }

		internal static bool IsHeadless
		{
			get
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Invalid comparison between Unknown and I4
				if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsDedicated())
				{
					return (int)SystemInfo.graphicsDeviceType == 4;
				}
				return true;
			}
		}

		private void Awake()
		{
			//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_005a: Expected O, but got Unknown
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Expected O, but got Unknown
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Expected O, but got Unknown
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Expected O, but got Unknown
			instance = this;
			DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Debug", false, "Enable detailed diagnostics in the BepInEx console/log. Disabled by default. Errors and essential startup messages remain visible.");
			Columns = ((BaseUnityPlugin)this).Config.Bind<int>("Inventory", "Columns", 6, new ConfigDescription("Columns for new/empty satchels. A non-empty bag keeps its saved dimensions. Synchronized by Jotunn from the server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			Rows = ((BaseUnityPlugin)this).Config.Bind<int>("Inventory", "Rows", 4, new ConfigDescription("Rows for new/empty satchels. A non-empty bag keeps its saved dimensions. Synchronized by Jotunn from the server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			((BaseUnityPlugin)this).Config.Bind<KeyCode>("Controls", "OpenKey", (KeyCode)98, "Obsolete; now uses hover plus the game's Interact action.");
			((BaseUnityPlugin)this).Config.Remove(new ConfigDefinition("Controls", "OpenKey"));
			AutoStorePickups = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AutoStorePickups", true, "Route eligible world pickups into the equipped satchel when the entire stack fits. Otherwise use normal inventory. Honors native automatic-pickup and weight rules. Local preference.");
			ApplySettings();
			((BaseUnityPlugin)this).Config.Save();
			((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged;
			CustomLocalization localization = LocalizationManager.Instance.GetLocalization();
			string text = "English";
			localization.AddTranslation(ref text, new Dictionary<string, string>
			{
				["dl_enchanting_satchel_name"] = "Enchanting Satchel",
				["dl_enchanting_satchel_description"] = "A modest pouch of blue leather, its depths far greater than its size suggests. Alas, its magic makes room only for enchanting materials.\n\nEquip the satchel, open your inventory, hover over it, and press Interact (E by default) to open."
			});
			PrefabManager.OnVanillaPrefabsAvailable += RegisterItem;
			ItemManager.OnItemsRegistered += VerifyRegistration;
			harmony = new Harmony("DevilInLaw.EnchantingSatchel");
			harmony.PatchAll(typeof(Plugin).Assembly);
			RecyclerCompatibility.LogRegistration();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Enchanting Satchel 1.0.0 by Devil_In_Law.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Configured dimensions: {Columns.Value} columns x {Rows.Value} rows ({Columns.Value * Rows.Value} slots). Open: hover equipped satchel + Interact (E by default).");
			Trace($"Configuration file: {((BaseUnityPlugin)this).Config.ConfigFilePath}. Debug={DebugLogging.Value}.");
			string[] array = new string[3] { "com.jotunn.jotunn", "Azumatt.AzuExtendedPlayerInventory", "randyknapp.mods.epicloot" };
			foreach (string text2 in array)
			{
				if (Chainloader.PluginInfos.TryGetValue(text2, out var value))
				{
					Trace($"Dependency {text2}: {value.Metadata.Version}.");
				}
			}
			Trace("Lifecycle hooks and satchel-only action diagnostics installed. Waiting for vanilla prefabs.");
		}

		private void RegisterItem()
		{
			if (itemRegistered)
			{
				Trace("Ignored repeated vanilla-prefab callback: satchel already registered.");
				return;
			}
			try
			{
				RegisterItemCore();
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)string.Format("Satchel registration failed. Prefab={0}; slot={1}. {2}", "dl_EnchantingSatchel", "Enchanting Satchel", arg));
				throw;
			}
		}

		private void RegisterItemCore()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Expected O, but got Unknown
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Expected O, but got Unknown
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Expected O, but got Unknown
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Expected O, but got Unknown
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Expected O, but got Unknown
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Expected O, but got Unknown
			Trace("Creating dl_EnchantingSatchel from LeatherScraps; configuring a non-stackable Misc item.");
			CustomItem val = new CustomItem("dl_EnchantingSatchel", "LeatherScraps");
			if (!Object.op_Implicit((Object)(object)val.ItemPrefab) || !Object.op_Implicit((Object)(object)val.ItemDrop))
			{
				throw new InvalidOperationException("Could not create satchel from the LeatherScraps placeholder.");
			}
			ItemData itemData = val.ItemDrop.m_itemData;
			itemData.m_shared.m_name = "$dl_enchanting_satchel_name";
			itemData.m_shared.m_description = "$dl_enchanting_satchel_description";
			itemData.m_shared.m_maxStackSize = 1;
			itemData.m_shared.m_maxQuality = 1;
			itemData.m_shared.m_weight = 1f;
			itemData.m_shared.m_value = 0;
			itemData.m_shared.m_useDurability = false;
			itemData.m_shared.m_itemType = (ItemType)16;
			itemData.m_customData = new Dictionary<string, string>();
			itemData.m_equipped = false;
			itemData.m_dropPrefab = val.ItemPrefab;
			SatchelAppearance.Apply(val.ItemPrefab, itemData);
			if (!ItemManager.Instance.AddItem(val))
			{
				throw new InvalidOperationException("Jotunn rejected the satchel prefab registration.");
			}
			RecipeConfig val2 = new RecipeConfig();
			val2.Name = "dl_Recipe_EnchantingSatchel";
			val2.Item = "dl_EnchantingSatchel";
			val2.Amount = 1;
			val2.CraftingStation = "piece_workbench";
			val2.MinStationLevel = 1;
			val2.Requirements = (RequirementConfig[])(object)new RequirementConfig[4]
			{
				new RequirementConfig("Raspberry", 2, 0, true),
				new RequirementConfig("Blueberries", 5, 0, true),
				new RequirementConfig("LeatherScraps", 10, 0, true),
				new RequirementConfig("DustEpic", 1, 0, true)
			};
			CustomRecipe val3 = new CustomRecipe(val2);
			if (!ItemManager.Instance.AddRecipe(val3))
			{
				throw new InvalidOperationException("Jotunn rejected the satchel crafting recipe.");
			}
			Trace("Registered dl_Recipe_EnchantingSatchel: 1 satchel at workbench level 1; Raspberry x2, Blueberries x5, LeatherScraps x10, DustEpic x1. Jotunn resolves ingredient references after item registration.");
			if (IsHeadless)
			{
				Trace("Headless server: equipment slot registration skipped; slots are registered on player clients.");
			}
			else
			{
				Trace("Jotunn accepted the item. Registering the AzuEPI equipment slot by exact prefab name.");
				if (!API.AddSlot("Enchanting Satchel", "dl_EnchantingSatchel", -1))
				{
					throw new InvalidOperationException("AzuEPI rejected the dedicated satchel equipment slot.");
				}
			}
			itemRegistered = true;
			PrefabManager.OnVanillaPrefabsAvailable -= RegisterItem;
			((BaseUnityPlugin)this).Logger.LogInfo((object)(IsHeadless ? "Registered dl_EnchantingSatchel and crafting recipe on headless server." : "Registered dl_EnchantingSatchel and dedicated slot 'Enchanting Satchel'. Appearance: original blue pouch and pixelated icon."));
		}

		private void VerifyRegistration()
		{
			GameObject prefab = PrefabManager.Instance.GetPrefab("dl_EnchantingSatchel");
			if (!Object.op_Implicit((Object)(object)prefab) || !Object.op_Implicit((Object)(object)prefab.GetComponent<ItemDrop>()))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Item registration verification failed: dl_EnchantingSatchel missing or has no ItemDrop.");
				return;
			}
			Trace($"Item registration verified: {((Object)prefab).name}, stack limit={prefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_maxStackSize}.");
			int num = default(int);
			if (IsHeadless)
			{
				Trace("Headless server: equipment slot verification skipped (client UI only).");
			}
			else if (!API.TryGetSlotIndexByName("Enchanting Satchel", ref num, true))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Slot verification failed: 'Enchanting Satchel' not found. Check AzuEPI configuration/removal settings.");
			}
			else
			{
				Trace($"Dedicated equipment slot verified at index {num}. In-game equip behavior still needs testing.");
			}
			MaterialPolicy.Refresh();
			string[] array = new string[4] { "Raspberry", "Blueberries", "LeatherScraps", "DustEpic" };
			foreach (string text in array)
			{
				if (!Object.op_Implicit((Object)(object)PrefabManager.Instance.GetPrefab(text)))
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Satchel recipe ingredient prefab unavailable: " + text + "."));
				}
				else
				{
					Trace("Recipe ingredient resolved: " + text + ".");
				}
			}
		}

		private void OnSettingChanged(object? sender, SettingChangedEventArgs args)
		{
			if ((object)args.ChangedSetting == DebugLogging)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Detailed debug logging " + (DebugLogging.Value ? "enabled" : "disabled") + "."));
				return;
			}
			ApplySettings();
			Trace($"Config changed: {args.ChangedSetting.Definition.Section}.{args.ChangedSetting.Definition.Key}. Dimensions={Columns.Value}x{Rows.Value}; new/empty bags use these dimensions.");
		}

		private void ApplySettings()
		{
			SatchelStorage.Columns = Columns.Value;
			SatchelStorage.Rows = Rows.Value;
			PickupRouting.Enabled = AutoStorePickups.Value;
		}

		internal static void Error(string message)
		{
			if ((Object)(object)instance != (Object)null)
			{
				((BaseUnityPlugin)instance).Logger.LogError((object)message);
			}
		}

		internal static bool IsSatchel(ItemData? item)
		{
			if (item != null && Object.op_Implicit((Object)(object)item.m_dropPrefab))
			{
				return ((Object)item.m_dropPrefab).name == "dl_EnchantingSatchel";
			}
			return false;
		}

		internal static void Trace(string message)
		{
			if ((Object)(object)instance != (Object)null && instance.DebugLogging.Value)
			{
				((BaseUnityPlugin)instance).Logger.LogInfo((object)("[DEBUG] " + message));
			}
		}

		private void OnDestroy()
		{
			Trace("Plugin shutting down: removing lifecycle hooks and diagnostic patches.");
			SatchelUI.Close("plugin shutdown");
			PrefabManager.OnVanillaPrefabsAvailable -= RegisterItem;
			ItemManager.OnItemsRegistered -= VerifyRegistration;
			((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged;
			Harmony? obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)instance == (Object)(object)this)
			{
				instance = null;
			}
		}
	}
	[HarmonyPatch]
	internal static class RecyclerCompatibility
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type type = AccessTools.TypeByName("Recycle_N_Reclaim.GamePatches.Utils");
			if (!(type == null))
			{
				MethodInfo methodInfo = AccessTools.Method(type, "InventoryRecycleItem", (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					Plugin.Error("Recycle_N_Reclaim is present but its inventory-recycling hook was not found. Filled satchel recycling is not verified.");
				}
				else
				{
					yield return methodInfo;
				}
			}
		}

		internal static void LogRegistration()
		{
			Type type = AccessTools.TypeByName("Recycle_N_Reclaim.GamePatches.Utils");
			MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "InventoryRecycleItem", (Type[])null, (Type[])null));
			if (methodInfo != null)
			{
				Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo);
				if (patchInfo != null && patchInfo.Owners.Contains("DevilInLaw.EnchantingSatchel"))
				{
					Plugin.Trace("Registered filled-satchel protection for Recycle_N_Reclaim inventory recycling.");
				}
			}
		}

		private static bool Prefix(ItemData __0)
		{
			if (!Plugin.IsSatchel(__0))
			{
				return true;
			}
			try
			{
				BagState bagState = SatchelStorage.Get(__0);
				if (bagState == null || bagState.Inventory.NrOfItems() == 0)
				{
					return true;
				}
			}
			catch (Exception ex)
			{
				Plugin.Error("Blocked recycling a satchel with unreadable contents: " + ex.Message);
			}
			SatchelUI.Message("Empty the Enchanting Satchel before recycling it.");
			Plugin.Trace("Blocked inventory recycling of a filled or unreadable satchel.");
			return false;
		}
	}
	internal static class SatchelAppearance
	{
		private static string Text(string name)
		{
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name) ?? throw new InvalidOperationException("Missing embedded asset: " + name);
			using StreamReader streamReader = new StreamReader(stream);
			return streamReader.ReadToEnd();
		}

		internal static void Apply(GameObject prefab, ItemData item)
		{
			bool flag = !Plugin.IsHeadless;
			if (flag)
			{
				ApplyIcon(item);
			}
			else
			{
				Plugin.Trace("Headless server: skipping satchel icon, shaders, and rendering; retaining model bounds and drop collider.");
			}
			ApplyModel(prefab, flag);
		}

		private static void ApplyIcon(ItemData item)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: 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_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("dl_satchel_icon.png") ?? throw new InvalidOperationException("Missing embedded satchel icon.");
			using MemoryStream memoryStream = new MemoryStream();
			stream.CopyTo(memoryStream);
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false)
			{
				name = "dl_satchel_icon",
				filterMode = (FilterMode)0,
				wrapMode = (TextureWrapMode)1
			};
			if (!ImageConversion.LoadImage(val, memoryStream.ToArray(), false))
			{
				throw new InvalidDataException("Satchel icon PNG could not be decoded.");
			}
			item.m_shared.m_icons = (Sprite[])(object)new Sprite[1] { Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f) };
			Plugin.Trace($"Loaded blue satchel icon: {((Texture)val).width}x{((Texture)val).height}, transparent PNG, point sampling.");
		}

		private static void ApplyModel(GameObject prefab, bool renderVisuals)
		{
			//IL_038a: Unknown result type (might be due to invalid IL or missing references)
			//IL_038f: Unknown result type (might be due to invalid IL or missing references)
			//IL_039c: Expected O, but got Unknown
			//IL_03a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_0440: Unknown result type (might be due to invalid IL or missing references)
			//IL_0445: Unknown result type (might be due to invalid IL or missing references)
			//IL_045e: Expected O, but got Unknown
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Expected O, but got Unknown
			//IL_05d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_05da: Unknown result type (might be due to invalid IL or missing references)
			//IL_05e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0350: Unknown result type (might be due to invalid IL or missing references)
			//IL_0667: Unknown result type (might be due to invalid IL or missing references)
			//IL_066c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0670: Unknown result type (might be due to invalid IL or missing references)
			//IL_067c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0681: Unknown result type (might be due to invalid IL or missing references)
			//IL_0685: Unknown result type (might be due to invalid IL or missing references)
			//IL_0699: Unknown result type (might be due to invalid IL or missing references)
			//IL_069e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0512: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_06ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0531: Unknown result type (might be due to invalid IL or missing references)
			//IL_0586: Unknown result type (might be due to invalid IL or missing references)
			Renderer[] componentsInChildren = prefab.GetComponentsInChildren<Renderer>(true);
			Material val = null;
			Renderer[] array;
			if (renderVisuals)
			{
				array = componentsInChildren;
				foreach (Renderer val2 in array)
				{
					Plugin.Trace("Source renderer " + ((Object)val2).name + ": " + ((object)val2).GetType().Name + "; materials=" + string.Join(", ", from m in val2.sharedMaterials
						where Object.op_Implicit((Object)(object)m)
						select ((Object)m).name + " [" + ((Object)m.shader).name + "]") + ".");
				}
				val = componentsInChildren.Where((Renderer r) => r is MeshRenderer || r is SkinnedMeshRenderer).SelectMany((Renderer r) => r.sharedMaterials).FirstOrDefault((Func<Material, bool>)((Material m) => Object.op_Implicit((Object)(object)m) && Object.op_Implicit((Object)(object)m.shader) && m.HasProperty("_Color") && m.HasProperty("_MainTex") && ((Object)m.shader).name.IndexOf("particle", StringComparison.OrdinalIgnoreCase) < 0));
				if (!Object.op_Implicit((Object)(object)val))
				{
					Shader obj = Shader.Find("Custom/Creature") ?? Shader.Find("Standard");
					if (!Object.op_Implicit((Object)(object)obj))
					{
						throw new InvalidOperationException("No suitable lit item shader available for the satchel mesh.");
					}
					val = new Material(obj)
					{
						name = "dl_satchel_lit_fallback"
					};
					if (!val.HasProperty("_Color") || !val.HasProperty("_MainTex"))
					{
						throw new InvalidOperationException("Satchel fallback shader lacks color/texture support.");
					}
				}
				Plugin.Trace(string.Format("Satchel material template: {0}; shader={1}; supports color={2}.", ((Object)val).name, ((Object)val.shader).name, val.HasProperty("_Color")));
			}
			Dictionary<string, Color> dictionary = ParseMaterials(Text("dl_satchel_model.mtl"));
			List<Vector3> list = new List<Vector3>();
			List<Vector3> list2 = new List<Vector3>();
			Dictionary<string, List<int>> dictionary2 = new Dictionary<string, List<int>>(StringComparer.Ordinal);
			string key = "cloth";
			string[] array2 = Text("dl_satchel_model.obj").Split('\n');
			for (int i = 0; i < array2.Length; i++)
			{
				string[] array3 = array2[i].Trim().Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
				if (array3.Length == 0 || array3[0].StartsWith("#"))
				{
					continue;
				}
				if (array3[0] == "v")
				{
					list.Add(new Vector3(Number(array3[1]), Number(array3[2]), Number(array3[3])));
				}
				else if (array3[0] == "usemtl")
				{
					key = array3[1];
				}
				else
				{
					if (!(array3[0] == "f"))
					{
						continue;
					}
					if (array3.Length != 4)
					{
						throw new InvalidDataException("Satchel model must be triangulated.");
					}
					if (!dictionary2.TryGetValue(key, out var value))
					{
						dictionary2.Add(key, value = new List<int>());
					}
					for (int num = 1; num <= 3; num++)
					{
						int num2 = int.Parse(array3[num].Split('/')[0], CultureInfo.InvariantCulture) - 1;
						if (num2 < 0 || num2 >= list.Count)
						{
							throw new InvalidDataException("Invalid model vertex reference.");
						}
						value.Add(list2.Count);
						list2.Add(list[num2]);
					}
				}
			}
			if (list2.Count == 0)
			{
				throw new InvalidDataException("Satchel mesh is empty.");
			}
			Mesh val3 = new Mesh
			{
				name = "dl_EnchantingSatchel_mesh"
			};
			val3.SetVertices(list2);
			val3.SetColors(Enumerable.Repeat<Color>(Color.white, list2.Count).ToList());
			val3.SetUVs(0, ((IEnumerable<Vector3>)list2).Select((Func<Vector3, Vector2>)((Vector3 v) => new Vector2(v.x * 3f + 0.5f, v.y * 3f))).ToList());
			val3.subMeshCount = dictionary2.Count;
			List<Material> list3 = new List<Material>();
			int num3 = 0;
			foreach (KeyValuePair<string, List<int>> item in dictionary2)
			{
				val3.SetTriangles(item.Value, num3++);
				if (renderVisuals)
				{
					Material val4 = new Material(val)
					{
						name = "dl_satchel_" + item.Key
					};
					if (val4.HasProperty("_MainTex"))
					{
						val4.SetTexture("_MainTex", (Texture)(object)Texture2D.whiteTexture);
					}
					if (val4.HasProperty("_BumpMap"))
					{
						val4.SetTexture("_BumpMap", (Texture)null);
					}
					if (val4.HasProperty("_Color"))
					{
						val4.SetColor("_Color", dictionary[item.Key]);
					}
					if (val4.HasProperty("_Glossiness"))
					{
						val4.SetFloat("_Glossiness", 0.04f);
					}
					if (val4.HasProperty("_Metallic"))
					{
						val4.SetFloat("_Metallic", 0f);
					}
					if (val4.HasProperty("_EmissionColor"))
					{
						val4.SetColor("_EmissionColor", Color.black);
					}
					if (val4.HasProperty("_EmissiveColor"))
					{
						val4.SetColor("_EmissiveColor", Color.black);
					}
					if (val4.HasProperty("_EmissionMap"))
					{
						val4.SetTexture("_EmissionMap", (Texture)(object)Texture2D.blackTexture);
					}
					val4.DisableKeyword("_EMISSION");
					Plugin.Trace($"Surface {item.Key}: shader={((Object)val4.shader).name}; tint={dictionary[item.Key]}; emission disabled.");
					list3.Add(val4);
				}
			}
			val3.RecalculateNormals();
			val3.RecalculateBounds();
			if (renderVisuals)
			{
				GameObject val5 = new GameObject("dl_satchel_visual")
				{
					layer = prefab.layer
				};
				val5.transform.SetParent(prefab.transform, false);
				val5.AddComponent<MeshFilter>().sharedMesh = val3;
				((Renderer)val5.AddComponent<MeshRenderer>()).sharedMaterials = list3.ToArray();
			}
			array = componentsInChildren;
			for (int i = 0; i < array.Length; i++)
			{
				array[i].enabled = false;
			}
			Collider[] componentsInChildren2 = prefab.GetComponentsInChildren<Collider>(true);
			for (int i = 0; i < componentsInChildren2.Length; i++)
			{
				componentsInChildren2[i].enabled = false;
			}
			BoxCollider obj2 = prefab.AddComponent<BoxCollider>();
			Bounds bounds = val3.bounds;
			obj2.center = ((Bounds)(ref bounds)).center;
			bounds = val3.bounds;
			obj2.size = ((Bounds)(ref bounds)).size + new Vector3(0.01f, 0.01f, 0.01f);
			Rigidbody component = prefab.GetComponent<Rigidbody>();
			if (Object.op_Implicit((Object)(object)component))
			{
				component.ResetCenterOfMass();
			}
			object arg = list2.Count / 3;
			object arg2 = list3.Count;
			bounds = val3.bounds;
			Plugin.Trace($"Applied original low-poly pouch: {arg} triangles, {arg2} materials, bounds {((Bounds)(ref bounds)).size}. Native drop/network components retained.");
		}

		private static float Number(string value)
		{
			return float.Parse(value, CultureInfo.InvariantCulture);
		}

		private static Dictionary<string, Color> ParseMaterials(string text)
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, Color> dictionary = new Dictionary<string, Color>(StringComparer.Ordinal);
			string key = "";
			string[] array = text.Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Trim().Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
				if (array2.Length != 0)
				{
					if (array2[0] == "newmtl")
					{
						key = array2[1];
					}
					else if (array2[0] == "Kd")
					{
						dictionary[key] = new Color(Number(array2[1]), Number(array2[2]), Number(array2[3]), 1f);
					}
				}
			}
			return dictionary;
		}
	}
	internal sealed class BagState
	{
		internal readonly ItemData Owner;

		internal readonly Inventory Inventory;

		internal readonly StorageEnvelope Envelope;

		internal string Payload;

		internal BagState(ItemData owner, Inventory inventory, StorageEnvelope envelope, string payload)
		{
			Owner = owner;
			Inventory = inventory;
			Envelope = envelope;
			Payload = payload;
		}
	}
	internal static class SatchelStorage
	{
		private sealed class Cache
		{
			internal bool Initialized;

			internal string Raw = "";

			internal BagState? State;

			internal string? Error;
		}

		internal sealed class PickupReceipt
		{
			internal readonly BagState Bag;

			internal readonly string Before;

			internal readonly string After;

			internal readonly ItemData[] Items;

			internal readonly int[] Counts;

			internal readonly bool[] PickedUp;

			internal PickupReceipt(BagState bag, string before, ItemData[] items, int[] counts, bool[] pickedUp)
			{
				Bag = bag;
				Before = before;
				After = bag.Payload;
				Items = items;
				Counts = counts;
				PickedUp = pickedUp;
			}

			internal void Rollback()
			{
				if (Bag.Payload != After)
				{
					Plugin.Error("Pickup rollback refused because the bag changed after routing; inspect the inventory totals.");
					return;
				}
				List<ItemData> allItems = Bag.Inventory.GetAllItems();
				allItems.Clear();
				for (int i = 0; i < Items.Length; i++)
				{
					Items[i].m_stack = Counts[i];
					Items[i].m_pickedUp = PickedUp[i];
					allItems.Add(Items[i]);
				}
				ApplyPayload(Bag, Before);
				Notify(Bag.Inventory);
				if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
				{
					Notify(((Humanoid)Player.m_localPlayer).GetInventory());
				}
				Plugin.Trace("Rolled back pickup into bag " + Bag.Envelope.Id + "; original world item remains.");
			}
		}

		internal const string DataKey = "DevilInLaw.EnchantingSatchel.storage";

		private static readonly ConditionalWeakTable<ItemData, Cache> CacheByItem = new ConditionalWeakTable<ItemData, Cache>();

		private static readonly ConditionalWeakTable<Inventory, BagState> Owners = new ConditionalWeakTable<Inventory, BagState>();

		private static readonly MethodInfo ChangedMethod = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null);

		private static readonly MethodInfo FindSlotMethod = AccessTools.Method(typeof(Inventory), "FindEmptySlot", (Type[])null, (Type[])null);

		private static bool committing;

		internal static int Columns = 6;

		internal static int Rows = 4;

		internal static BagState? Get(ItemData item, bool create = false)
		{
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Expected O, but got Unknown
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Expected O, but got Unknown
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Expected O, but got Unknown
			if (!Plugin.IsSatchel(item))
			{
				return null;
			}
			item.m_customData.TryGetValue("DevilInLaw.EnchantingSatchel.storage", out var value);
			if (value == null && !create)
			{
				return null;
			}
			Cache value2 = CacheByItem.GetValue(item, (ItemData _) => new Cache());
			if (value != null && value2.Initialized && value2.Raw == value)
			{
				if (value2.Error != null)
				{
					throw new InvalidDataException(value2.Error);
				}
				return value2.State;
			}
			if (value2.State != null)
			{
				Owners.Remove(value2.State.Inventory);
			}
			value2.State = null;
			value2.Initialized = true;
			value2.Raw = value ?? "";
			value2.Error = null;
			try
			{
				StorageEnvelope storageEnvelope = ((value == null) ? new StorageEnvelope
				{
					Columns = Columns,
					Rows = Rows
				} : StorageEnvelope.Decode(value));
				Inventory val = new Inventory("Enchanting Satchel", (Sprite)null, storageEnvelope.Columns, storageEnvelope.Rows);
				if (value != null)
				{
					ZPackage val2 = new ZPackage(storageEnvelope.InventoryBytes);
					if (val2.ReadInt() != 110 || val2.ReadUShort() != storageEnvelope.ItemCount)
					{
						throw new InvalidDataException("Unsupported inventory format/count; original satchel payload retained.");
					}
					val.Load(new ZPackage(storageEnvelope.InventoryBytes));
					Validate(val);
					if (val.NrOfItems() != storageEnvelope.ItemCount || ((IEnumerable<ItemData>)val.GetAllItems()).Sum((Func<ItemData, long>)((ItemData i) => i.m_stack)) != storageEnvelope.StackTotal)
					{
						throw new InvalidDataException("Some satchel entries could not be restored; original payload retained.");
					}
				}
				BagState state = new BagState(item, val, storageEnvelope, value ?? "");
				value2.State = state;
				Owners.Add(val, state);
				val.m_onChanged = (Action)Delegate.Combine(val.m_onChanged, (Action)delegate
				{
					if (!committing)
					{
						SaveExternalChange(state);
					}
				});
				if (value == null)
				{
					Commit(state);
				}
				Plugin.Trace($"Loaded bag {storageEnvelope.Id}: {storageEnvelope.Columns}x{storageEnvelope.Rows}, {val.NrOfItems()} stacks, {storageEnvelope.StackTotal} items.");
				return state;
			}
			catch (Exception ex)
			{
				value2.Error = ex.Message;
				Plugin.Error($"Satchel data could not be loaded. Original data left untouched. {ex}");
				throw;
			}
		}

		internal static BagState Open(ItemData item)
		{
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Expected O, but got Unknown
			BagState bagState = Get(item, create: true);
			if (bagState.Envelope.Columns != Columns || bagState.Envelope.Rows != Rows)
			{
				if (bagState.Inventory.NrOfItems() != 0)
				{
					SatchelUI.Message($"Keeping saved size {bagState.Envelope.Columns}x{bagState.Envelope.Rows}. Empty the bag and reopen to apply {Columns}x{Rows}.");
				}
				else
				{
					Inventory val = new Inventory("Enchanting Satchel", (Sprite)null, Columns, Rows);
					Owners.Remove(bagState.Inventory);
					bagState.Envelope.Columns = Columns;
					bagState.Envelope.Rows = Rows;
					bagState = new BagState(item, val, bagState.Envelope, bagState.Payload);
					CacheByItem.GetValue(item, (ItemData _) => new Cache()).State = bagState;
					Owners.Add(val, bagState);
					BagState resized = bagState;
					val.m_onChanged = (Action)Delegate.Combine(val.m_onChanged, (Action)delegate
					{
						if (!committing)
						{
							SaveExternalChange(resized);
						}
					});
					Commit(bagState);
					Plugin.Trace($"Resized empty bag {bagState.Envelope.Id} to {Columns}x{Rows}.");
				}
			}
			return bagState;
		}

		internal static BagState? Owner(Inventory? inventory)
		{
			if (inventory == null || !Owners.TryGetValue(inventory, out BagState value))
			{
				return null;
			}
			return value;
		}

		internal static void Notify(Inventory inventory)
		{
			try
			{
				ChangedMethod.Invoke(inventory, new object[2] { false, false });
			}
			catch (Exception arg)
			{
				Plugin.Error($"Inventory changed notification failed after committed mutation: {arg}");
			}
		}

		private static void Validate(Inventory inventory)
		{
			HashSet<int> hashSet = new HashSet<int>();
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (!Object.op_Implicit((Object)(object)allItem.m_dropPrefab) || Plugin.IsSatchel(allItem) || allItem.m_stack < 1 || allItem.m_stack > 65535 || allItem.m_gridPos.x < 0 || allItem.m_gridPos.y < 0 || allItem.m_gridPos.x >= inventory.GetWidth() || allItem.m_gridPos.y >= inventory.GetHeight() || !hashSet.Add(allItem.m_gridPos.y * inventory.GetWidth() + allItem.m_gridPos.x))
				{
					throw new InvalidDataException("Invalid satchel item/position or nested bag; original data retained.");
				}
			}
		}

		private static string Encode(BagState state)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			Validate(state.Inventory);
			ZPackage val = new ZPackage();
			state.Inventory.Save(val);
			state.Envelope.InventoryBytes = val.GetArray();
			state.Envelope.ItemCount = state.Inventory.NrOfItems();
			state.Envelope.StackTotal = ((IEnumerable<ItemData>)state.Inventory.GetAllItems()).Sum((Func<ItemData, long>)((ItemData i) => i.m_stack));
			return state.Envelope.Encode();
		}

		internal static void Commit(BagState state)
		{
			string text = Encode(state);
			state.Owner.m_customData["DevilInLaw.EnchantingSatchel.storage"] = text;
			state.Payload = text;
			CacheByItem.GetValue(state.Owner, (ItemData _) => new Cache()).Raw = text;
			Plugin.Trace($"Saved bag {state.Envelope.Id}: {state.Envelope.ItemCount} stacks, {state.Envelope.StackTotal} items, {text.Length} encoded characters.");
		}

		private static void SaveExternalChange(BagState state)
		{
			try
			{
				Commit(state);
			}
			catch (Exception arg)
			{
				Plugin.Error($"External bag mutation could not be saved; original payload retained. {arg}");
				SatchelUI.Close("unsafe external mutation");
			}
		}

		private static StackIdentity Identity(ItemData item)
		{
			return new StackIdentity
			{
				Prefab = ((Object)item.m_dropPrefab).name,
				Quality = item.m_quality,
				Variant = item.m_variant,
				WorldLevel = item.m_worldLevel,
				CrafterId = item.m_crafterID,
				CrafterName = item.m_crafterName,
				Durability = item.m_durability,
				Cheated = item.m_cheated,
				PickedUp = item.m_pickedUp,
				CustomData = item.m_customData
			};
		}

		private static bool CanMerge(ItemData a, ItemData b)
		{
			return Identity(a).Difference(Identity(b)) == null;
		}

		internal static int Transfer(Inventory source, Inventory destination, ItemData item, int amount, Vector2i? target = null)
		{
			//IL_04d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0240: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Unknown result type (might be due to invalid IL or missing references)
			//IL_0247: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0253: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0321: Unknown result type (might be due to invalid IL or missing references)
			//IL_0326: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ff: Unknown result type (might be due to invalid IL or missing references)
			BagState bagState = Owner(source);
			BagState bagState2 = Owner(destination);
			if (bagState == null && bagState2 == null)
			{
				return 0;
			}
			if ((bagState != null && bagState != SatchelUI.Active) || (bagState2 != null && bagState2 != SatchelUI.Active))
			{
				return Reject("This bag is no longer the active equipped satchel.");
			}
			if (!SatchelUI.IsValidSession() || !source.ContainsItem(item) || amount < 1 || item.m_shared.m_questItem || item.m_equipped)
			{
				return Reject("The item or equipped satchel is no longer available.");
			}
			Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory();
			if ((bagState == null && source != inventory) || (bagState2 == null && destination != inventory))
			{
				return Reject("Transfer through your player inventory first.");
			}
			if (bagState2 != null && source != destination && (!MaterialPolicy.Ready || !MaterialPolicy.Accepts(item)))
			{
				return Reject("Only Epic Loot enchanting materials can enter this satchel.");
			}
			if (source == destination && !target.HasValue)
			{
				return 0;
			}
			amount = Math.Min(amount, item.m_stack);
			List<(ItemData, int)> list = new List<(ItemData, int)>();
			List<ItemData> list2 = new List<ItemData>();
			Vector2i? val = null;
			if (target.HasValue)
			{
				Vector2i value = target.Value;
				if (value.x < 0 || value.y < 0 || value.x >= destination.GetWidth() || value.y >= destination.GetHeight())
				{
					return 0;
				}
				ItemData itemAt = destination.GetItemAt(value.x, value.y);
				if (itemAt == item)
				{
					return amount;
				}
				if (itemAt != null)
				{
					if (!CanMerge(item, itemAt))
					{
						Plugin.Trace("Swap requested: " + ((Object)item.m_dropPrefab).name + " / " + ((Object)itemAt.m_dropPrefab).name + "; stacking difference=" + Identity(item).Difference(Identity(itemAt)) + ".");
						return Swap(source, destination, item, itemAt, amount, bagState, bagState2);
					}
					list2.Add(itemAt);
				}
				else
				{
					val = value;
				}
			}
			else
			{
				foreach (ItemData allItem in destination.GetAllItems())
				{
					if (CanMerge(item, allItem))
					{
						list2.Add(allItem);
					}
				}
				Vector2i val2 = (Vector2i)FindSlotMethod.Invoke(destination, new object[1] { true });
				if (val2.x >= 0)
				{
					val = val2;
				}
			}
			TransferPlan transferPlan = TransferPlan.Create(amount, item.m_stack, item.m_shared.m_maxStackSize, list2.Select((ItemData i) => i.m_shared.m_maxStackSize - i.m_stack).ToArray(), val.HasValue);
			for (int num = 0; num < list2.Count; num++)
			{
				if (transferPlan.MergeAmounts[num] > 0)
				{
					list.Add((list2[num], transferPlan.MergeAmounts[num]));
				}
			}
			int newStackAmount = transferPlan.NewStackAmount;
			int moved = transferPlan.Moved;
			if (moved <= 0)
			{
				return Reject("No room for that stack.");
			}
			List<ItemData> allItems = source.GetAllItems();
			List<ItemData> allItems2 = destination.GetAllItems();
			int stack = item.m_stack;
			Vector2i gridPos = item.m_gridPos;
			bool[] array = list.Select<(ItemData, int), bool>(((ItemData Item, int Amount) c) => c.Item.m_pickedUp).ToArray();
			ItemData val3 = null;
			string payload = bagState?.Payload;
			string payload2 = bagState2?.Payload;
			committing = true;
			try
			{
				foreach (var item5 in list)
				{
					ItemData item2 = item5.Item1;
					item2.m_stack += item5.Item2;
					ItemData item3 = item5.Item1;
					item3.m_pickedUp |= item.m_pickedUp;
				}
				if (newStackAmount > 0)
				{
					val3 = item.Clone();
					val3.m_stack = newStackAmount;
					val3.m_equipped = false;
					val3.m_gridPos = val.Value;
					allItems2.Add(val3);
				}
				item.m_stack -= moved;
				if (item.m_stack == 0)
				{
					allItems.Remove(item);
				}
				string payload3 = ((bagState == null) ? null : Encode(bagState));
				string payload4 = ((bagState2 == null || bagState2 == bagState) ? null : Encode(bagState2));
				if (bagState != null)
				{
					ApplyPayload(bagState, payload3);
				}
				if (bagState2 != null && bagState2 != bagState)
				{
					ApplyPayload(bagState2, payload4);
				}
			}
			catch (Exception arg)
			{
				if (val3 != null)
				{
					allItems2.Remove(val3);
				}
				for (int num2 = 0; num2 < list.Count; num2++)
				{
					ItemData item4 = list[num2].Item1;
					item4.m_stack -= list[num2].Item2;
					list[num2].Item1.m_pickedUp = array[num2];
				}
				item.m_stack = stack;
				item.m_gridPos = gridPos;
				if (!allItems.Contains(item))
				{
					allItems.Add(item);
				}
				if (bagState != null)
				{
					ApplyPayload(bagState, payload);
				}
				if (bagState2 != null && bagState2 != bagState)
				{
					ApplyPayload(bagState2, payload2);
				}
				Plugin.Error($"Transfer rolled back: {arg}");
				return Reject("Transfer failed; original inventories restored.");
			}
			finally
			{
				committing = false;
			}
			Notify(source);
			if (destination != source)
			{
				Notify(destination);
			}
			Notify(inventory);
			Plugin.Trace(string.Format("Transferred {0}/{1} {2}: {3} -> {4}. Metadata preserved.", moved, amount, ((Object)item.m_dropPrefab).name, (bagState == null) ? "player" : bagState.Envelope.Id, (bagState2 == null) ? "player" : bagState2.Envelope.Id));
			return moved;
		}

		private static void ApplyPayload(BagState state, string payload)
		{
			state.Owner.m_customData["DevilInLaw.EnchantingSatchel.storage"] = payload;
			state.Payload = payload;
			CacheByItem.GetValue(state.Owner, (ItemData _) => new Cache()).Raw = payload;
		}

		private static int Reject(string reason)
		{
			Plugin.Trace("Transfer rejected: " + reason);
			SatchelUI.Message(reason);
			return 0;
		}

		private static int Swap(Inventory source, Inventory destination, ItemData item, ItemData occupant, int amount, BagState? sourceBag, BagState? destinationBag)
		{
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			if (amount != item.m_stack)
			{
				return Reject("Drag the whole stack to swap it with another item.");
			}
			if (occupant.m_shared.m_questItem || occupant.m_equipped)
			{
				return Reject("That item cannot be moved into this slot.");
			}
			if (sourceBag != null && (!MaterialPolicy.Ready || !MaterialPolicy.Accepts(occupant)))
			{
				return Reject("Only Epic Loot enchanting materials can enter the satchel, including when swapping.");
			}
			Vector2i gridPos = item.m_gridPos;
			Vector2i gridPos2 = occupant.m_gridPos;
			List<ItemData> allItems = source.GetAllItems();
			List<ItemData> allItems2 = destination.GetAllItems();
			string payload = sourceBag?.Payload;
			string payload2 = destinationBag?.Payload;
			bool flag = false;
			committing = true;
			try
			{
				if (source != destination)
				{
					SlotExchange.Exchange(allItems, allItems2, item, occupant);
					flag = true;
				}
				item.m_gridPos = gridPos2;
				occupant.m_gridPos = gridPos;
				string payload3 = ((sourceBag == null) ? null : Encode(sourceBag));
				string payload4 = ((destinationBag == null || destinationBag == sourceBag) ? null : Encode(destinationBag));
				if (sourceBag != null)
				{
					ApplyPayload(sourceBag, payload3);
				}
				if (destinationBag != null && destinationBag != sourceBag)
				{
					ApplyPayload(destinationBag, payload4);
				}
			}
			catch (Exception arg)
			{
				if (flag)
				{
					SlotExchange.Exchange(allItems, allItems2, occupant, item);
				}
				item.m_gridPos = gridPos;
				occupant.m_gridPos = gridPos2;
				if (sourceBag != null)
				{
					ApplyPayload(sourceBag, payload);
				}
				if (destinationBag != null && destinationBag != sourceBag)
				{
					ApplyPayload(destinationBag, payload2);
				}
				Plugin.Error($"Slot swap rolled back: {arg}");
				return Reject("Swap failed; original items restored.");
			}
			finally
			{
				committing = false;
			}
			Notify(source);
			if (destination != source)
			{
				Notify(destination);
			}
			Notify(((Humanoid)Player.m_localPlayer).GetInventory());
			Plugin.Trace($"Swapped full stacks: {((Object)item.m_dropPrefab).name} ({gridPos.x},{gridPos.y}) -> ({gridPos2.x},{gridPos2.y}); {((Object)occupant.m_dropPrefab).name} moved to the original slot. Metadata/counts retained.");
			return amount;
		}

		internal static BagState? EquippedBag(Player player)
		{
			if (!Object.op_Implicit((Object)(object)player) || (Object)(object)player != (Object)(object)Player.m_localPlayer || ((Character)player).IsDead() || ((Character)player).IsTeleporting())
			{
				return null;
			}
			ItemData val = ((IEnumerable<ItemData>)((Humanoid)player).GetInventory().GetEquippedItems()).FirstOrDefault((Func<ItemData, bool>)Plugin.IsSatchel);
			if (val == null)
			{
				return null;
			}
			try
			{
				return Get(val, create: true);
			}
			catch
			{
				return null;
			}
		}

		private static TransferPlan PickupPlan(BagState bag, ItemData item, out List<ItemData> candidates, out Vector2i? empty)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			candidates = (from i in bag.Inventory.GetAllItems()
				where CanMerge(item, i)
				select i).ToList();
			Vector2i val = (Vector2i)FindSlotMethod.Invoke(bag.Inventory, new object[1] { true });
			empty = ((val.x >= 0) ? new Vector2i?(val) : ((Vector2i?)null));
			return TransferPlan.Create(item.m_stack, item.m_stack, item.m_shared.m_maxStackSize, candidates.Select((ItemData i) => i.m_shared.m_maxStackSize - i.m_stack).ToArray(), empty.HasValue);
		}

		internal static bool CanStorePickup(BagState bag, ItemData item)
		{
			if (!MaterialPolicy.Ready || !MaterialPolicy.Accepts(item) || item.m_shared.m_questItem || item.m_stack < 1)
			{
				return false;
			}
			List<ItemData> candidates;
			Vector2i? empty;
			return PickupPlan(bag, item, out candidates, out empty).Moved == item.m_stack;
		}

		internal static PickupReceipt? StorePickup(BagState bag, ItemData item)
		{
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			if (!CanStorePickup(bag, item))
			{
				return null;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer) || !bag.Owner.m_equipped || !((Humanoid)localPlayer).GetInventory().ContainsItem(bag.Owner))
			{
				return null;
			}
			List<ItemData> candidates;
			Vector2i? empty;
			TransferPlan transferPlan = PickupPlan(bag, item, out candidates, out empty);
			if (transferPlan.Moved != item.m_stack)
			{
				return null;
			}
			List<ItemData> allItems = bag.Inventory.GetAllItems();
			ItemData[] array = allItems.ToArray();
			int[] array2 = array.Select((ItemData i) => i.m_stack).ToArray();
			bool[] array3 = array.Select((ItemData i) => i.m_pickedUp).ToArray();
			string payload = bag.Payload;
			committing = true;
			try
			{
				for (int num = 0; num < candidates.Count; num++)
				{
					ItemData obj = candidates[num];
					obj.m_stack += transferPlan.MergeAmounts[num];
					if (transferPlan.MergeAmounts[num] > 0)
					{
						ItemData obj2 = candidates[num];
						obj2.m_pickedUp |= item.m_pickedUp;
					}
				}
				if (transferPlan.NewStackAmount > 0)
				{
					ItemData val = item.Clone();
					val.m_stack = transferPlan.NewStackAmount;
					val.m_equipped = false;
					val.m_gridPos = empty.Value;
					allItems.Add(val);
				}
				Commit(bag);
			}
			catch (Exception arg)
			{
				allItems.Clear();
				for (int num2 = 0; num2 < array.Length; num2++)
				{
					array[num2].m_stack = array2[num2];
					array[num2].m_pickedUp = array3[num2];
					allItems.Add(array[num2]);
				}
				ApplyPayload(bag, payload);
				Plugin.Error($"Pickup routing failed; bag restored and ordinary pickup retained. {arg}");
				return null;
			}
			finally
			{
				committing = false;
			}
			PickupReceipt result = new PickupReceipt(bag, payload, array, array2, array3);
			Notify(bag.Inventory);
			Notify(((Humanoid)localPlayer).GetInventory());
			Plugin.Trace($"Routed world pickup directly into bag {bag.Envelope.Id}: {item.m_stack} {((Object)item.m_dropPrefab).name}. World item removal remains with the native pickup routine.");
			return result;
		}

		internal static bool DropContents(Humanoid actor, Inventory inventory, ItemData item, int amount)
		{
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			BagState bagState = Owner(inventory);
			if (bagState == null || bagState != SatchelUI.Active || (Object)(object)actor != (Object)(object)Player.m_localPlayer || !SatchelUI.IsValidSession() || !inventory.ContainsItem(item) || item.m_shared.m_questItem || amount <= 0)
			{
				return false;
			}
			amount = Math.Min(amount, item.m_stack);
			List<ItemData> allItems = inventory.GetAllItems();
			int stack = item.m_stack;
			string payload = bagState.Payload;
			ItemDrop val = null;
			committing = true;
			try
			{
				val = ItemDrop.DropItem(item, amount, ((Component)actor).transform.position + ((Component)actor).transform.forward + ((Component)actor).transform.up, ((Component)actor).transform.rotation);
				if (!Object.op_Implicit((Object)(object)val))
				{
					throw new InvalidOperationException("The world item could not be created.");
				}
				val.OnPlayerDrop();
				item.m_stack -= amount;
				if (item.m_stack == 0)
				{
					allItems.Remove(item);
				}
				Commit(bagState);
			}
			catch (Exception arg)
			{
				item.m_stack = stack;
				if (!allItems.Contains(item))
				{
					allItems.Add(item);
				}
				ApplyPayload(bagState, payload);
				if (Object.op_Implicit((Object)(object)val))
				{
					try
					{
						ZNetScene.instance.Destroy(((Component)val).gameObject);
					}
					catch (Exception arg2)
					{
						Plugin.Error($"Failed to clean up a rolled-back world drop: {arg2}");
					}
				}
				Plugin.Error($"Direct bag drop failed; source contents restored. {arg}");
				SatchelUI.Message("Could not drop the item. Check the log.");
				return false;
			}
			finally
			{
				committing = false;
			}
			Notify(inventory);
			Notify(actor.GetInventory());
			Plugin.Trace($"Dropped {amount} {((Object)item.m_dropPrefab).name} directly from bag {bagState.Envelope.Id}; original item metadata preserved.");
			try
			{
				Rigidbody component = ((Component)val).GetComponent<Rigidbody>();
				if (Object.op_Implicit((Object)(object)component))
				{
					component.linearVelocity = (((Component)actor).transform.forward + Vector3.up) * ((val.m_itemData.GetWeight(-1) >= 300f) ? 0.5f : 5f);
				}
				((Character)actor).GetZAnim().SetTrigger("interact");
				actor.m_dropEffects.Create(((Component)actor).transform.position, Quaternion.identity, (Transform)null, 1f, -1, ((Character)actor).GetZDOID());
				((Character)actor).Message((MessageType)1, "$msg_dropped " + val.m_itemData.m_shared.m_name, amount, val.m_itemData.GetIcon(), false);
			}
			catch (Exception arg3)
			{
				Plugin.Error($"Item was dropped successfully, but drop feedback failed: {arg3}");
			}
			return true;
		}
	}
	internal static class SatchelUI
	{
		[HarmonyPatch(typeof(InventoryGui), "Update")]
		private static class InputAndLifecycle
		{
			[HarmonyPriority(800)]
			[HarmonyBefore(new string[] { "Azumatt.BowsBeforeHoes", "randyknapp.mods.epicloot" })]
			private static void Prefix(InventoryGui __instance)
			{
				//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
				//IL_0104: Unknown result type (might be due to invalid IL or missing references)
				if (Active != null && (!IsValidSession() || !InventoryGui.IsVisible() || Object.op_Implicit((Object)(object)CurrentContainer.Invoke(__instance)) || (QuiverOpen != null && (bool)QuiverOpen.GetValue(null, null))))
				{
					Close("inventory hidden, equipment/ownership changed, or another container opened");
				}
				if (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || ((Character)Player.m_localPlayer).IsDead() || !InventoryGui.IsVisible() || Console.IsVisible() || Menu.IsVisible() || (Object.op_Implicit((Object)(object)Chat.instance) && Chat.instance.HasFocus()) || !ZInput.GetButtonDown("Use"))
				{
					return;
				}
				Vector3 mousePosition = Input.mousePosition;
				Vector2i val = default(Vector2i);
				((Vector2i)(ref val))..ctor(Mathf.RoundToInt(mousePosition.x), Mathf.RoundToInt(mousePosition.y));
				ItemData item = __instance.m_playerGrid.GetItem(val);
				if (!Plugin.IsSatchel(item))
				{
					InventoryGrid[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<InventoryGrid>();
					foreach (InventoryGrid val2 in componentsInChildren)
					{
						if (val2.GetInventory() == ((Humanoid)Player.m_localPlayer).GetInventory())
						{
							ItemData item2 = val2.GetItem(val);
							if (Plugin.IsSatchel(item2))
							{
								item = item2;
								break;
							}
						}
					}
				}
				if (Plugin.IsSatchel(item))
				{
					ZInput.ResetButtonStatus("Use");
					Plugin.Trace("Interact pressed while hovering the satchel.");
					Toggle(__instance, item);
				}
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "UpdateContainer")]
		private static class Render
		{
			private static bool Prefix(InventoryGui __instance)
			{
				//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
				//IL_0101: Unknown result type (might be due to invalid IL or missing references)
				//IL_011c: Unknown result type (might be due to invalid IL or missing references)
				//IL_012d: Unknown result type (might be due to invalid IL or missing references)
				if (Active == null)
				{
					return true;
				}
				if (!IsValidSession())
				{
					Close("invalid session while rendering");
					return true;
				}
				((Component)__instance.m_container).gameObject.SetActive(true);
				((Component)__instance.m_takeAllButton).gameObject.SetActive(true);
				((Component)__instance.m_stackAllButton).gameObject.SetActive(true);
				__instance.ContainerGrid.UpdateInventory(Active.Inventory, (Player)null, DragItem.Invoke(__instance));
				__instance.m_containerName.text = $"Enchanting Satchel ({Active.Inventory.NrOfItems()}/{Active.Envelope.Columns * Active.Envelope.Rows})";
				__instance.m_containerWeight.text = Mathf.CeilToInt(Active.Inventory.GetTotalWeight()).ToString();
				Vector2 widgetSize = __instance.ContainerGrid.GetWidgetSize();
				__instance.m_container.sizeDelta = new Vector2(Mathf.Max(previousSize.x, widgetSize.x + 40f), Mathf.Max(previousSize.y, widgetSize.y + 130f));
				return false;
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "IsContainerOpen")]
		private static class ContainerVisibility
		{
			private static void Postfix(ref bool __result)
			{
				if (Active != null)
				{
					__result = true;
				}
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "Hide")]
		private static class Hidden
		{
			private static void Prefix()
			{
				Close("inventory closed");
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "CloseContainer")]
		private static class ContainerClosed
		{
			private static void Prefix()
			{
				Close("container closed");
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "Show")]
		private static class OtherContainer
		{
			private static void Prefix()
			{
				Close("inventory show/other container");
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "OnSelectedItem")]
		private static class QuickTransfer
		{
			private static bool Prefix(InventoryGui __instance, InventoryGrid grid, ItemData item, Modifier mod)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Invalid comparison between Unknown and I4
				if (Active == null || (int)mod != 2 || DragItem.Invoke(__instance) != null)
				{
					return true;
				}
				if (item != null)
				{
					Inventory inventory = grid.GetInventory();
					Inventory destination = (Inventory)((inventory == Active.Inventory) ? ((object)((Humanoid)Player.m_localPlayer).GetInventory()) : ((object)Active.Inventory));
					SatchelStorage.Transfer(inventory, destination, item, item.m_stack);
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(InventoryGrid), "DropItem")]
		private static class DragTransfer
		{
			private static bool Prefix(InventoryGrid __instance, Inventory fromInventory, ItemData item, int amount, Vector2i pos, ref bool __result)
			{
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				Inventory inventory = __instance.GetInventory();
				if (SatchelStorage.Owner(inventory) == null && SatchelStorage.Owner(fromInventory) == null)
				{
					return true;
				}
				__result = SatchelStorage.Transfer(fromInventory, inventory, item, amount, pos) > 0;
				return false;
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "OnTakeAll")]
		private static class TakeAll
		{
			private static bool Prefix()
			{
				if (Active == null)
				{
					return true;
				}
				ItemData[] array = Active.Inventory.GetAllItems().ToArray();
				foreach (ItemData val in array)
				{
					SatchelStorage.Transfer(Active.Inventory, ((Humanoid)Player.m_localPlayer).GetInventory(), val, val.m_stack);
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "OnStackAll")]
		private static class StackAll
		{
			private static bool Prefix()
			{
				if (Active == null)
				{
					return true;
				}
				string[] source = (from i in Active.Inventory.GetAllItems()
					select ((Object)i.m_dropPrefab).name).ToArray();
				Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory();
				ItemData[] array = inventory.GetAllItems().ToArray();
				foreach (ItemData val in array)
				{
					if (MaterialPolicy.Accepts(val) && source.Contains(((Object)val.m_dropPrefab).name))
					{
						SatchelStorage.Transfer(inventory, Active.Inventory, val, val.m_stack);
					}
				}
				return false;
			}
		}

		private static Vector2 previousSize;

		private static bool previousTakeAll;

		private static bool previousStackAll;

		private static readonly FieldRef<InventoryGui, ItemData> DragItem = AccessTools.FieldRefAccess<InventoryGui, ItemData>("m_dragItem");

		private static readonly FieldRef<InventoryGui, Inventory> DragInventory = AccessTools.FieldRefAccess<InventoryGui, Inventory>("m_dragInventory");

		private static readonly FieldRef<InventoryGui, Container> CurrentContainer = AccessTools.FieldRefAccess<InventoryGui, Container>("m_currentContainer");

		private static readonly MethodInfo SetupDrag = AccessTools.Method(typeof(InventoryGui), "SetupDragItem", (Type[])null, (Type[])null);

		private static readonly MethodInfo HideSplit = AccessTools.Method(typeof(InventoryGui), "HideSplitDialog", (Type[])null, (Type[])null);

		private static readonly PropertyInfo? QuiverOpen = AccessTools.Property(AccessTools.TypeByName("BowsBeforeHoes.QuiverFunctionality.QuiverContainerGui"), "IsOpen");

		internal static BagState? Active { get; private set; }

		internal static void Message(string text)
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false);
			}
		}

		internal static bool IsValidSession()
		{
			Player localPlayer = Player.m_localPlayer;
			if (Active != null && Object.op_Implicit((Object)(object)localPlayer) && !((Character)localPlayer).IsDead() && !((Character)localPlayer).IsTeleporting() && Active.Owner.m_equipped && ((Humanoid)localPlayer).GetInventory().ContainsItem(Active.Owner) && Active.Owner.m_customData.TryGetValue("DevilInLaw.EnchantingSatchel.storage", out var value))
			{
				return value == Active.Payload;
			}
			return false;
		}

		internal static void Close(string reason)
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			if (Active == null)
			{
				return;
			}
			Plugin.Trace("Closing bag " + Active.Envelope.Id + ": " + reason + ".");
			InventoryGui instance = InventoryGui.instance;
			if (Object.op_Implicit((Object)(object)instance))
			{
				if (DragInventory.Invoke(instance) == Active.Inventory)
				{
					SetupDrag.Invoke(instance, new object[3] { null, null, 1 });
				}
				HideSplit.Invoke(instance, Array.Empty<object>());
				instance.m_container.sizeDelta = previousSize;
				((Component)instance.m_container).gameObject.SetActive(false);
				((Component)instance.m_takeAllButton).gameObject.SetActive(previousTakeAll);
				((Component)instance.m_stackAllButton).gameObject.SetActive(previousStackAll);
			}
			Active = null;
		}

		private static void Toggle(InventoryGui gui, ItemData item)
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			if (!item.m_equipped || !((Humanoid)Player.m_localPlayer).GetInventory().ContainsItem(item))
			{
				Message("Equip this Enchanting Satchel first.");
				return;
			}
			if (Active != null)
			{
				Close("hover/interact toggled");
				return;
			}
			if (gui.IsContainerOpen() || DragItem.Invoke(gui) != null)
			{
				Message("Close the other container and finish dragging before opening the satchel.");
				return;
			}
			try
			{
				MaterialPolicy.Refresh();
				Active = SatchelStorage.Open(item);
				previousSize = gui.m_container.sizeDelta;
				previousTakeAll = ((Component)gui.m_takeAllButton).gameObject.activeSelf;
				previousStackAll = ((Component)gui.m_stackAllButton).gameObject.activeSelf;
				gui.ContainerGrid.ResetView();
				Plugin.Trace($"Opened bag {Active.Envelope.Id}: {Active.Envelope.Columns}x{Active.Envelope.Rows}, {Active.Inventory.NrOfItems()} occupied slots.");
			}
			catch (Exception arg)
			{
				Active = null;
				Plugin.Error($"Could not open satchel. {arg}");
				Message("Satchel contents could not be read. Original data retained; check the log.");
			}
		}
	}
	internal static class SlotExchange
	{
		internal static void Exchange<T>(IList<T> source, IList<T> destination, T item, T occupant)
		{
			int num = source.IndexOf(item);
			int num2 = destination.IndexOf(occupant);
			if (num < 0 || num2 < 0)
			{
				throw new InvalidOperationException("A swap item is no longer in its inventory.");
			}
			if (source != destination)
			{
				source[num] = occupant;
				destination[num2] = item;
			}
		}
	}
	internal sealed class StackIdentity
	{
		internal string Prefab = "";

		internal int Quality;

		internal int Variant;

		internal int WorldLevel;

		internal long CrafterId;

		internal string CrafterName = "";

		internal float Durability;

		internal bool Cheated;

		internal bool PickedUp;

		internal IReadOnlyDictionary<string, string> CustomData = new Dictionary<string, string>();

		internal string? Difference(StackIdentity other)
		{
			if (Prefab != other.Prefab)
			{
				return "prefab";
			}
			if (Quality != other.Quality)
			{
				return "quality";
			}
			if (Variant != other.Variant)
			{
				return "variant";
			}
			if (WorldLevel != other.WorldLevel)
			{
				return "world level";
			}
			if (CrafterId != other.CrafterId || CrafterName != other.CrafterName)
			{
				return "crafter";
			}
			if (Durability != other.Durability)
			{
				return "durability";
			}
			if (Cheated != other.Cheated)
			{
				return "cheated flag";
			}
			if (CustomData.Count != other.CustomData.Count || CustomData.Any<KeyValuePair<string, string>>((KeyValuePair<string, string> p) => !other.CustomData.TryGetValue(p.Key, out string value) || value != p.Value))
			{
				return "custom item data";
			}
			return null;
		}
	}
	internal sealed class StorageEnvelope
	{
		internal const int Schema = 1;

		internal const int MaxPayloadBytes = 4194304;

		internal string Id = Guid.NewGuid().ToString("N");

		internal int Columns;

		internal int Rows;

		internal int ItemCount;

		internal long StackTotal;

		internal byte[] InventoryBytes = Array.Empty<byte>();

		internal string Encode()
		{
			Validate();
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
			{
				binaryWriter.Write(1145852738);
				binaryWriter.Write(1);
				binaryWriter.Write(Id);
				binaryWriter.Write(Columns);
				binaryWriter.Write(Rows);
				binaryWriter.Write(ItemCount);
				binaryWriter.Write(StackTotal);
				binaryWriter.Write(InventoryBytes.Length);
				binaryWriter.Write(InventoryBytes);
			}
			byte[] array = memoryStream.ToArray();
			using SHA256 sHA = SHA256.Create();
			byte[] second = sHA.ComputeHash(array);
			return Convert.ToBase64String(array.Concat(second).ToArray());
		}

		internal static StorageEnvelope Decode(string encoded)
		{
			if (encoded.Length > 8390656)
			{
				throw new InvalidDataException("Satchel payload exceeds the supported size.");
			}
			byte[] array = Convert.FromBase64String(encoded);
			if (array.Length < 64 || array.Length > 4195328)
			{
				throw new InvalidDataException("Invalid satchel payload length.");
			}
			int count = array.Length - 32;
			using SHA256 sHA = SHA256.Create();
			if (!sHA.ComputeHash(array, 0, count).SequenceEqual(array.Skip(count)))
			{
				throw new InvalidDataException("Satchel checksum mismatch; original data retained.");
			}
			using MemoryStream memoryStream = new MemoryStream(array, 0, count, writable: false);
			using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8);
			if (binaryReader.ReadInt32() != 1145852738 || binaryReader.ReadInt32() != 1)
			{
				throw new InvalidDataException("Unrecognized satchel schema; original data retained.");
			}
			StorageEnvelope obj = new StorageEnvelope
			{
				Id = binaryReader.ReadString(),
				Columns = binaryReader.ReadInt32(),
				Rows = binaryReader.ReadInt32(),
				ItemCount = binaryReader.ReadInt32(),
				StackTotal = binaryReader.ReadInt64()
			};
			int num = binaryReader.ReadInt32();
			if (num < 0 || num > 4194304 || num != memoryStream.Length - memoryStream.Position)
			{
				throw new InvalidDataException("Invalid serialized inventory length.");
			}
			obj.InventoryBytes = binaryReader.ReadBytes(num);
			obj.Validate();
			return obj;
		}

		private void Validate()
		{
			if (!Guid.TryParseExact(Id, "N", out var _) || Columns < 1 || Columns > 8 || Rows < 1 || Rows > 8 || ItemCount < 0 || ItemCount > Columns * Rows || StackTotal < ItemCount || StackTotal > (long)ItemCount * 65535L || (ItemCount == 0 && StackTotal != 0L) || InventoryBytes.Length > 4194304)
			{
				throw new InvalidDataException("Invalid satchel dimensions, identity, or item counts.");
			}
		}
	}
	[HarmonyPatch]
	internal static class BlockUnvalidatedInsertion
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			return from m in typeof(Inventory).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == "AddItem" && m.ReturnType == typeof(bool)
				select m;
		}

		private static bool Prefix(Inventory __instance, ref bool __result)
		{
			if (SatchelStorage.Owner(__instance) == null)
			{
				return true;
			}
			__result = false;
			Plugin.Trace("Blocked an insertion into a satchel outside its validated transfer path.");
			return false;
		}
	}
	[HarmonyPatch(typeof(ItemData), "GetWeight")]
	internal static class ContainedWeight
	{
		private static void Postfix(ItemData __instance, ref float __result)
		{
			if (!Plugin.IsSatchel(__instance))
			{
				return;
			}
			try
			{
				BagState bagState = SatchelStorage.Get(__instance);
				if (bagState != null)
				{
					__result += bagState.Inventory.GetTotalWeight();
				}
			}
			catch
			{
				__result += 10000f;
			}
		}
	}
	[HarmonyPatch(typeof(Inventory), "IsTeleportable")]
	internal static class ContainedTeleportRules
	{
		private static void Postfix(Inventory __instance, bool allowAllItems, ref bool __result)
		{
			if (!__result)
			{
				return;
			}
			foreach (ItemData allItem in __instance.GetAllItems())
			{
				if (!Plugin.IsSatchel(allItem))
				{
					continue;
				}
				try
				{
					BagState bagState = SatchelStorage.Get(allItem);
					if (bagState != null && !bagState.Inventory.IsTeleportable(allowAllItems))
					{
						__result = false;
						break;
					}
				}
				catch
				{
					__result = false;
					break;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Humanoid), "DropItem")]
	internal static class DirectContentDrops
	{
		private static bool Prefix(Humanoid __instance, Inventory inventory, ItemData item, int amount, ref bool __result)
		{
			if (SatchelStorage.Owner(inventory) == null)
			{
				return true;
			}
			__result = SatchelStorage.DropContents(__instance, inventory, item, amount);
			return false;
		}
	}
	[HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(int) })]
	internal static class SatchelTooltip
	{
		private static void Postfix(ItemData __instance, ref string __result)
		{
			if (!Plugin.IsSatchel(__instance))
			{
				return;
			}
			try
			{
				BagState bagState = SatchelStorage.Get(__instance);
				int num = bagState?.Envelope.Columns ?? SatchelStorage.Columns;
				int num2 = bagState?.Envelope.Rows ?? SatchelStorage.Rows;
				__result = __result + $"\n\nEnchanting materials: {((bagState != null) ? bagState.Inventory.NrOfItems() : 0)}/{num * num2} slots ({num}x{num2})." + "\nHover this equipped satchel and press Interact (E by default) to open/close it.";
			}
			catch
			{
				__result += "\n\nContents unavailable. Original data preserved; check the log.";
			}
		}
	}
	internal sealed class TransferPlan
	{
		internal int[] MergeAmounts = Array.Empty<int>();

		internal int NewStackAmount;

		internal int Moved;

		internal static TransferPlan Create(int requested, int sourceCount, int newStackLimit, IReadOnlyList<int> compatibleSpaces, bool hasEmptySlot)
		{
			TransferPlan transferPlan = new TransferPlan
			{
				MergeAmounts = new int[compatibleSpaces.Count]
			};
			int num = Math.Max(0, Math.Min(requested, sourceCount));
			for (int i = 0; i < compatibleSpaces.Count; i++)
			{
				int num2 = Math.Min(num, Math.Max(0, compatibleSpaces[i]));
				transferPlan.MergeAmounts[i] = num2;
				transferPlan.Moved += num2;
				num -= num2;
			}
			transferPlan.NewStackAmount = (hasEmptySlot ? Math.Min(num, Math.Max(0, newStackLimit)) : 0);
			transferPlan.Moved += transferPlan.NewStackAmount;
			return transferPlan;
		}
	}
}