Decompiled source of RunicEnchantingCompatibility v0.1.8

plugins/RunicEnchantingCompatibility/RunicEnchantingCompatibility.dll

Decompiled 21 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using EpicLoot;
using EpicLoot_UnityLib;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")]
[assembly: AssemblyCompany("RunicEnchantingCompatibility")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.8.0")]
[assembly: AssemblyInformationalVersion("0.1.8")]
[assembly: AssemblyProduct("RunicEnchantingCompatibility")]
[assembly: AssemblyTitle("RunicEnchantingCompatibility")]
[assembly: AssemblyVersion("0.1.8.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 Vercadi.RunicEnchantingCompatibility
{
	internal sealed class BackpackAccess
	{
		internal sealed class Endpoint
		{
			private readonly BackpackAccess access;

			private readonly Player player;

			private readonly object bag;

			internal readonly Inventory Inventory;

			internal readonly ItemData Parent;

			internal readonly string Key;

			internal Endpoint(BackpackAccess access, Player player, object bag, Inventory inventory, ItemData parent, string key)
			{
				this.access = access;
				this.player = player;
				this.bag = bag;
				Inventory = inventory;
				Parent = parent;
				Key = key;
			}

			internal bool Valid()
			{
				Endpoint endpoint = access.Current(player);
				if (endpoint != null && endpoint.bag == bag && endpoint.Inventory == Inventory && endpoint.Parent == Parent)
				{
					return endpoint.Key == Key;
				}
				return false;
			}

			internal void Persist(string expected)
			{
				if (!Valid())
				{
					throw new InvalidOperationException("Equipped backpack changed during transfer.");
				}
				access.save.Invoke(bag, new object[1] { Inventory });
				if ((string)access.value.GetValue(bag) != expected || !Parent.m_customData.TryGetValue(Key, out var value) || value != expected)
				{
					throw new InvalidOperationException("Backpack material changes did not persist exactly.");
				}
			}

			internal void UpdateShadowParent(Inventory shadow, Inventory contents, string expected)
			{
				ItemData itemAt = shadow.GetItemAt(Parent.m_gridPos.x, Parent.m_gridPos.y);
				if (shadow == ((Humanoid)player).GetInventory() || contents == Inventory || itemAt == null || itemAt == Parent || (Object)(object)itemAt.m_dropPrefab != (Object)(object)Parent.m_dropPrefab || itemAt.m_quality != Parent.m_quality)
				{
					throw new InvalidOperationException("Backpack parent moved in the staged inventory.");
				}
				object obj = access.itemData.Invoke(null, new object[1] { itemAt });
				object obj2 = access.getComponent.Invoke(obj, new object[1] { "" });
				if (obj2 == null || obj2 == bag || access.item.GetValue(obj2) != itemAt || (string)access.key.GetValue(obj2) != Key || access.getInventory.Invoke(obj2, null) == Inventory)
				{
					throw new InvalidOperationException("Backpack shadow component is not isolated from the live item.");
				}
				access.save.Invoke(obj2, new object[1] { contents });
				if (access.getInventory.Invoke(obj2, null) != contents || (string)access.value.GetValue(obj2) != expected || !itemAt.m_customData.TryGetValue(Key, out var value) || value != expected)
				{
					throw new InvalidOperationException("Backpack shadow component did not retain the projected contents.");
				}
			}
		}

		internal const string Id = "vapok.mods.adventurebackpacks";

		internal const string Hash = "9F0B0FD4D1FE3B1406ECD39BEAA166C2E36F26CC012EA63812732F62AFA5BDA0";

		internal const string PreviousHash = "5773E28A4E8A7F27B839F1FBC4AB2272C5349E7E38D671F683950E30D47D4541";

		private const BindingFlags All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private readonly MethodInfo canCraft;

		private readonly MethodInfo equipped;

		private readonly MethodInfo getInventory;

		private readonly MethodInfo save;

		private readonly MethodInfo itemData;

		private readonly MethodInfo getComponent;

		private readonly FieldInfo open;

		private readonly FieldInfo loading;

		private readonly FieldInfo emptying;

		private readonly PropertyInfo alive;

		private readonly PropertyInfo item;

		private readonly PropertyInfo key;

		private readonly PropertyInfo value;

		private static MethodInfo Method(Type t, string n, Type[] args)
		{
			return t.GetMethod(n, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, args, null);
		}

		private static FieldInfo Field(Type t, string n)
		{
			return t.GetField(n, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		}

		private static PropertyInfo Property(Type t, string n)
		{
			while (t != null)
			{
				PropertyInfo property = t.GetProperty(n, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null)
				{
					return property;
				}
				t = t.BaseType;
			}
			return null;
		}

		internal BackpackAccess(Assembly assembly)
		{
			using (FileStream inputStream = File.OpenRead(assembly.Location))
			{
				using SHA256 sHA = SHA256.Create();
				string text = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "");
				if (text != "9F0B0FD4D1FE3B1406ECD39BEAA166C2E36F26CC012EA63812732F62AFA5BDA0" && text != "5773E28A4E8A7F27B839F1FBC4AB2272C5349E7E38D671F683950E30D47D4541")
				{
					throw new NotSupportedException("Unreviewed AdventureBackpacks binary.");
				}
			}
			Type type = assembly.GetType("AdventureBackpacks.Features.CraftFromBackpack", throwOnError: true);
			Type type2 = assembly.GetType("AdventureBackpacks.Extensions.PlayerExtensions", throwOnError: true);
			Type type3 = assembly.GetType("AdventureBackpacks.Components.BackpackComponent", throwOnError: true);
			itemData = Method(assembly.GetType("Vapok.Common.Managers.ItemExtensions", throwOnError: true), "Data", new Type[1] { typeof(ItemData) });
			getComponent = assembly.GetType("Vapok.Common.Managers.ItemInfo", throwOnError: true).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Single((MethodInfo m) => m.Name == "Get" && m.IsGenericMethodDefinition && m.GetGenericArguments().Length == 1 && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(string))
				.MakeGenericMethod(type3);
			canCraft = Method(type, "CanCraftFromBackpack", new Type[2]
			{
				typeof(Player),
				typeof(Inventory).MakeByRefType()
			});
			equipped = Method(type2, "GetEquippedBackpack", new Type[1] { typeof(Player) });
			getInventory = Method(type3, "GetInventory", Type.EmptyTypes);
			save = Method(type3, "Save", new Type[1] { typeof(Inventory) });
			open = Field(assembly.GetType("AdventureBackpacks.Patches.InventoryGuiPatches", throwOnError: true), "BackpackIsOpen");
			alive = Property(type3, "IsAlive");
			loading = Field(type3, "IsLoadingInventory");
			emptying = Field(type3, "IsEmptyingBackpack");
			item = Property(type3, "Item");
			key = Property(type3, "CustomDataKey");
			value = Property(type3, "Value");
			if (canCraft == null || equipped == null || getInventory == null || save == null || open == null || alive == null || loading == null || emptying == null || item == null || key == null || value == null || itemData == null || getComponent == null)
			{
				throw new MissingMemberException("Backpack material endpoint contract changed.");
			}
		}

		internal Endpoint Current(Player player)
		{
			if (!Object.op_Implicit((Object)(object)player) || (Object)(object)player != (Object)(object)Player.m_localPlayer || !((Character)player).IsOwner() || ((Character)player).IsDead() || ((Character)player).IsTeleporting() || (bool)open.GetValue(null))
			{
				return null;
			}
			object[] array = new object[2] { player, null };
			if (!(bool)canCraft.Invoke(null, array))
			{
				return null;
			}
			object obj = equipped.Invoke(null, new object[1] { player });
			object obj2 = array[1];
			Inventory val = (Inventory)((obj2 is Inventory) ? obj2 : null);
			if (obj == null || val == null || val == ((Humanoid)player).GetInventory() || val.GetAllItems().Count > 1024 || !(bool)alive.GetValue(obj) || (bool)loading.GetValue(obj) || (bool)emptying.GetValue(obj) || getInventory.Invoke(obj, null) != val)
			{
				return null;
			}
			object? obj3 = item.GetValue(obj);
			ItemData val2 = (ItemData)((obj3 is ItemData) ? obj3 : null);
			if (val2 == null || !((Humanoid)player).GetInventory().GetAllItems().Contains(val2))
			{
				return null;
			}
			return new Endpoint(this, player, obj, val, val2, (string)key.GetValue(obj));
		}
	}
	internal static class CheckedTransfer
	{
		internal static void Match(string[] actual, string[] expected)
		{
			if (!actual.SequenceEqual<string>(expected, StringComparer.Ordinal))
			{
				throw new InvalidOperationException("Inventory changed outside the planned enchanting transfer.");
			}
		}

		internal static void Run(IReadOnlyList<Action> steps, IReadOnlyList<string[]> checkpoints, Func<string[]> read, Action verify, Action<string[]> recover, Action quarantine)
		{
			if (checkpoints.Count != steps.Count + 1)
			{
				throw new ArgumentException("Incomplete transfer plan.");
			}
			Match(read(), checkpoints[0]);
			int num = 0;
			try
			{
				for (int i = 0; i < steps.Count; i++)
				{
					Match(read(), checkpoints[i]);
					num = i + 1;
					steps[i]();
					Match(read(), checkpoints[i + 1]);
				}
				verify();
				Match(read(), checkpoints[checkpoints.Count - 1]);
			}
			catch (Exception item)
			{
				List<Exception> list = new List<Exception> { item };
				try
				{
					string[] current = read();
					if (!checkpoints.Take(num + 1).Any((string[] c) => current.SequenceEqual<string>(c, StringComparer.Ordinal)))
					{
						throw new InvalidOperationException("Recovery would overwrite an unrecognized inventory change; nothing restored.");
					}
					if (!current.SequenceEqual<string>(checkpoints[0], StringComparer.Ordinal))
					{
						recover(current);
					}
					Match(read(), checkpoints[0]);
				}
				catch (Exception item2)
				{
					list.Add(item2);
					try
					{
						quarantine();
					}
					catch (Exception item3)
					{
						list.Add(item3);
					}
				}
				throw new AggregateException((list.Count == 1) ? "Enchanting transfer stopped; original inventories verified. No automatic retry." : "Enchanting transfer stopped; recovery required. Unknown contents preserved. No automatic retry.", list);
			}
		}
	}
	internal static class ChestPreview
	{
		private const BindingFlags All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly Type Reflection = ((object)Chainloader.PluginInfos["chazman.RunicCrafting"].Instance).GetType().Assembly.GetType("RunicCrafting.Integration.ValheimReflection", throwOnError: true);

		private static readonly MethodInfo View = Reflection.GetMethod("GetView", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Container) }, null);

		private static readonly Assembly Crafting = Reflection.Assembly;

		private static readonly FieldInfo Loading = Crafting.GetType("RunicCrafting.Integration.PreviewRefreshRuntime", throwOnError: true).GetField("LoadingPreview", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

		private static readonly MethodInfo Matches = Crafting.GetType("RunicCrafting.Integration.CraftingInventoryPayloadComparison", throwOnError: true).GetMethod("MatchesLoaded", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

		internal static Inventory Read(Container chest)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Expected O, but got Unknown
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Expected O, but got Unknown
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Expected O, but got Unknown
			ZNetView val = (ZNetView)View.Invoke(null, new object[1] { chest });
			ZDO val2 = ((Object.op_Implicit((Object)(object)val) && val.IsValid()) ? val.GetZDO() : null);
			Inventory inventory = chest.GetInventory();
			if (val2 == null || !val2.IsValid() || inventory == null)
			{
				return null;
			}
			byte[] byteArray = val2.GetByteArray(ZDOVars.s_items, (byte[])null);
			if (byteArray == null || byteArray.Length == 0 || byteArray.Length > 262144)
			{
				return null;
			}
			byte[] array = (byte[])byteArray.Clone();
			Inventory val3 = new Inventory(inventory.GetName(), (Sprite)null, inventory.GetWidth(), inventory.GetHeight());
			object value = Loading.GetValue(null);
			try
			{
				Loading.SetValue(null, val3);
				val3.Load(new ZPackage(array));
			}
			finally
			{
				Loading.SetValue(null, value);
			}
			ZPackage val4 = new ZPackage();
			val3.Save(val4);
			if (!(bool)Matches.Invoke(null, new object[2]
			{
				array,
				val4.GetArray()
			}))
			{
				return null;
			}
			byte[] byteArray2 = val2.GetByteArray(ZDOVars.s_items, (byte[])null);
			if (!Object.op_Implicit((Object)(object)val) || !val.IsValid() || val.GetZDO() != val2 || !val2.IsValid() || byteArray2 == null || byteArray2.Length != array.Length)
			{
				return null;
			}
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i] != byteArray2[i])
				{
					return null;
				}
			}
			return val3;
		}
	}
	internal sealed class PlayerSlotPolicy
	{
		internal const string Id = "shudnal.ExtraSlots";

		internal const string Hash = "389A9DFC6D36BB8897D7B0991AA1E02B37DD55A95D944DF0EB7637954B79322B";

		private readonly MethodInfo slot;

		internal PlayerSlotPolicy(Assembly assembly)
		{
			using (FileStream inputStream = File.OpenRead(assembly.Location))
			{
				using SHA256 sHA = SHA256.Create();
				if (BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "") != "389A9DFC6D36BB8897D7B0991AA1E02B37DD55A95D944DF0EB7637954B79322B")
				{
					throw new NotSupportedException("Unreviewed ExtraSlots inventory layout.");
				}
			}
			slot = assembly.GetType("ExtraSlots.Slots", throwOnError: true).GetMethod("GetSlotInGrid", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Vector2i) }, null) ?? throw new MissingMethodException("ExtraSlots grid contract changed.");
		}

		internal bool Ordinary(int x, int y)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			return slot.Invoke(null, new object[1] { (object)new Vector2i(x, y) }) == null;
		}
	}
	[BepInPlugin("vercadi.RunicEnchantingCompatibility", "Runic Enchanting Compatibility", "0.1.8")]
	[BepInProcess("valheim.exe")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency("randyknapp.mods.epicloot", "0.14.13")]
	[BepInDependency("chazman.RunicCrafting", "1.1.11")]
	[BepInDependency("chazman.RunicStorage", "1.3.12")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Id = "vercadi.RunicEnchantingCompatibility";

		internal const string EpicHash = "802790E0CBD8A6F951627619A488B84E6A406E8770E2687B2FFF8B63D2ADF9EA";

		internal const string CraftingHash = "3D98A550F9A160F796AD3BDA3E9C47DE2E26211FB1449F53AB511FF883D041B0";

		internal const string StorageHash = "C2EEE73F58A2F1AF7C19092FBE02AAC5D58A39BB92CA305997929F6AD0ACFEE2";

		private static Plugin instance;

		private RunicBridge bridge;

		private Harmony harmony;

		private bool ready;

		private bool faulted;

		private bool provider;

		private FieldInfo selected;

		private MethodInfo inUseDistance;

		private TableActions actions;

		private int actionDepth;

		private float previewUntil;

		private Player previewPlayer;

		private EnchantingTable previewTable;

		private List<ItemData> storedPreview;

		private Dictionary<string, int> storedCounts;

		private EnchantingTableUIPanelBase pendingRefresh;

		[ThreadStatic]
		private static Dictionary<string, int> preview;

		private void Awake()
		{
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Expected O, but got Unknown
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_021c: Expected O, but got Unknown
			//IL_021c: Expected O, but got Unknown
			//IL_0249: Unknown result type (might be due to invalid IL or missing references)
			//IL_024e: Unknown result type (might be due to invalid IL or missing references)
			//IL_025e: Expected O, but got Unknown
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_0299: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bd: Expected O, but got Unknown
			//IL_02bd: Expected O, but got Unknown
			instance = this;
			try
			{
				Require("randyknapp.mods.epicloot", "802790E0CBD8A6F951627619A488B84E6A406E8770E2687B2FFF8B63D2ADF9EA");
				bridge = new RunicBridge(Require("chazman.RunicCrafting", "3D98A550F9A160F796AD3BDA3E9C47DE2E26211FB1449F53AB511FF883D041B0"), Require("chazman.RunicStorage", "C2EEE73F58A2F1AF7C19092FBE02AAC5D58A39BB92CA305997929F6AD0ACFEE2"));
				if (Chainloader.PluginInfos.TryGetValue("shudnal.ExtraSlots", out var value))
				{
					bridge.SetSlotPolicy(new PlayerSlotPolicy(((object)value.Instance).GetType().Assembly).Ordinary);
				}
				if (((BaseUnityPlugin)this).Config.Bind<bool>("General", "Use equipped backpack", true, "Use items and materials from the equipped closed AdventureBackpack at the enchanting table. Requires its server crafting setting.").Value && Chainloader.PluginInfos.TryGetValue("vapok.mods.adventurebackpacks", out var value2))
				{
					try
					{
						bridge.SetBackpack(new BackpackAccess(((object)value2.Instance).GetType().Assembly));
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Backpack materials unavailable; chest integration remains enabled. " + ex.Message));
					}
				}
				selected = AccessTools.Field(typeof(UpgradeTableUI), "_selectedFeature") ?? throw new MissingFieldException("Selected feature unavailable.");
				inUseDistance = AccessTools.Method(typeof(EnchantingTable), "InUseDistance", new Type[1] { typeof(Humanoid) }, (Type[])null) ?? throw new MissingMethodException("Table use-distance contract changed.");
				actions = new TableActions();
				provider = API.RegisterInventoryProvider("vercadi.RunicEnchantingCompatibility", (Func<List<ItemData>>)ListPreview, (Func<string, int>)CountPreview, (Func<string, int, int>)null, (Func<ItemData, int, int>)null);
				if (!provider)
				{
					throw new InvalidOperationException("Epic Loot inventory provider registration refused.");
				}
				harmony = new Harmony("vercadi.RunicEnchantingCompatibility");
				harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(UpgradeTableUI), "Refresh", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "BeginPreview", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "EndPreview", (Type[])null), (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(UpgradeTableUI), "DoMainAction", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "BeforeUpgrade", (Type[])null)
				{
					priority = 0
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Type[] panels = TableActions.Panels;
				foreach (Type type in panels)
				{
					harmony.Patch((MethodBase)AccessTools.DeclaredMethod(type, "DoMainAction", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "BeforeAction", (Type[])null)
					{
						priority = 0
					}, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "AfterAction", (Type[])null), (HarmonyMethod)null);
				}
				ready = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"ACTIVE: reviewed EpicLoot 0.14.13 table items and materials with RunicCrafting 1.1.11 and RunicStorage 1.3.12. Identify, sacrifice, enchant, augment, disenchant, convert, runes and upgrades; equipped backpack and nearby chests; client-only. ");
			}
			catch (Exception ex2)
			{
				Harmony obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
				if (provider)
				{
					API.UnregisterInventoryProvider("vercadi.RunicEnchantingCompatibility");
				}
				provider = false;
				((BaseUnityPlugin)this).Logger.LogError((object)("INACTIVE: original enchanting behavior retained. " + ex2));
			}
			static Assembly Require(string id, string hash)
			{
				Assembly assembly = ((object)Chainloader.PluginInfos[id].Instance).GetType().Assembly;
				using FileStream inputStream = File.OpenRead(assembly.Location);
				using SHA256 sHA = SHA256.Create();
				if (BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "") != hash)
				{
					throw new NotSupportedException("Unreviewed dependency: " + id);
				}
				return assembly;
			}
		}

		private bool Context(UpgradeTableUI ui, out Player player, out EnchantingTable table)
		{
			if (TableContext(out player, out table) && Object.op_Implicit((Object)(object)ui))
			{
				return ((Component)ui).gameObject.activeInHierarchy;
			}
			return false;
		}

		private bool TableContext(out Player player, out EnchantingTable table)
		{
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			player = Player.m_localPlayer;
			table = (Object.op_Implicit((Object)(object)EnchantingTableUI.instance) ? EnchantingTableUI.instance.SourceTable : null);
			if (ready && !faulted && bridge.Enabled && Object.op_Implicit((Object)(object)player) && ((Character)player).IsOwner() && !((Character)player).IsDead() && !((Character)player).IsTeleporting() && Object.op_Implicit((Object)(object)table) && EnchantingTableUI.IsVisible() && (bool)inUseDistance.Invoke(table, new object[1] { player }))
			{
				return bridge.AllowsTable(((Component)table).transform.position);
			}
			return false;
		}

		private List<Cost> Costs(UpgradeTableUI ui, EnchantingTable table)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			int num = (int)selected.GetValue(ui);
			if (!Enum.IsDefined(typeof(EnchantingFeature), num))
			{
				return null;
			}
			EnchantingFeature val = (EnchantingFeature)num;
			if (!table.IsFeatureAvailable(val) || table.IsFeatureMaxLevel(val))
			{
				return null;
			}
			return Rules.Normalize((table.IsFeatureLocked(val) ? table.GetFeatureUnlockCost(val) : table.GetFeatureUpgradeCost(val)).Select(delegate(InventoryItemListElement row)
			{
				ItemData item = row.GetItem();
				return new Cost(item?.m_shared?.m_name, bridge.Resource(item), item?.m_stack ?? 0);
			}));
		}

		private static List<ItemData> ListPreview()
		{
			Plugin plugin = instance;
			if (!Object.op_Implicit((Object)(object)plugin))
			{
				return new List<ItemData>();
			}
			try
			{
				if (!plugin.TableContext(out var player, out var table))
				{
					return new List<ItemData>();
				}
				if (plugin.storedPreview == null || (Object)(object)plugin.previewPlayer != (Object)(object)player || (Object)(object)plugin.previewTable != (Object)(object)table || Time.unscaledTime >= plugin.previewUntil)
				{
					plugin.storedPreview = plugin.bridge.ReadItems(player);
					plugin.storedCounts = (from i in plugin.storedPreview
						group i by i.m_shared.m_name).ToDictionary<IGrouping<string, ItemData>, string, int>((IGrouping<string, ItemData> g) => g.Key, (IGrouping<string, ItemData> g) => (int)Math.Min(2147483647L, ((IEnumerable<ItemData>)g).Sum((Func<ItemData, long>)((ItemData i) => i.m_stack))), StringComparer.Ordinal);
					plugin.previewPlayer = player;
					plugin.previewTable = table;
					plugin.previewUntil = Time.unscaledTime + 1f;
				}
				return plugin.storedPreview;
			}
			catch (Exception ex)
			{
				plugin.Fail(ex);
				return new List<ItemData>();
			}
		}

		private static int CountPreview(string name)
		{
			Plugin plugin = instance;
			if (string.IsNullOrEmpty(name) || !Object.op_Implicit((Object)(object)plugin) || !plugin.ready || plugin.faulted || plugin.actionDepth > 0)
			{
				return 0;
			}
			if (preview != null)
			{
				if (!preview.TryGetValue(name, out var value))
				{
					return 0;
				}
				return value;
			}
			ListPreview();
			if (!plugin.TableContext(out var _, out var _) || plugin.storedCounts == null || !plugin.storedCounts.TryGetValue(name, out var value2))
			{
				return 0;
			}
			return value2;
		}

		private static bool BeforeAction(EnchantingTableUIPanelBase __instance, out bool __state)
		{
			__state = false;
			Plugin plugin = instance;
			if (!Object.op_Implicit((Object)(object)plugin) || !plugin.ready)
			{
				return true;
			}
			try
			{
				if (!plugin.TableContext(out var player, out var table) || !Object.op_Implicit((Object)(object)__instance) || !((Component)__instance).gameObject.activeInHierarchy)
				{
					if (__instance != null)
					{
						__instance.Cancel();
					}
					return false;
				}
				__state = true;
				plugin.actionDepth++;
				TableActions.Plan plan = plugin.actions.Create(__instance, table, plugin.bridge);
				if (plan == null)
				{
					__instance.Cancel();
					return false;
				}
				if (!plugin.bridge.Pull(player, plan.Costs, plan.Selections, out var remapped, out var reason))
				{
					__instance.Cancel();
					((Character)player).Message((MessageType)2, reason, 0, (Sprite)null, false);
					return false;
				}
				plugin.actions.Remap(__instance, plan, remapped);
				plugin.storedPreview = null;
				return true;
			}
			catch (Exception ex)
			{
				plugin.Fail(ex);
				__instance.Cancel();
				if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
				{
					((Character)Player.m_localPlayer).Message((MessageType)2, "Enchanting transfer stopped. Check the log before retrying.", 0, (Sprite)null, false);
				}
				return false;
			}
		}

		private static void AfterAction(EnchantingTableUIPanelBase __instance, bool __state, Exception __exception)
		{
			Plugin plugin = instance;
			if (Object.op_Implicit((Object)(object)plugin))
			{
				if (__state)
				{
					plugin.actionDepth--;
				}
				plugin.storedPreview = null;
				plugin.pendingRefresh = __instance;
				if (__exception != null)
				{
					plugin.Fail(__exception);
				}
			}
		}

		private void LateUpdate()
		{
			if (Object.op_Implicit((Object)(object)pendingRefresh))
			{
				EnchantingTableUIPanelBase panel = pendingRefresh;
				pendingRefresh = null;
				try
				{
					if (!faulted && ready)
					{
						actions.RefreshCosts(panel);
					}
				}
				catch (Exception ex)
				{
					Fail(ex);
				}
			}
			if (!EnchantingTableUI.IsVisible())
			{
				storedPreview = null;
				previewPlayer = null;
				previewTable = null;
			}
		}

		private static void BeginPreview(UpgradeTableUI __instance, out Dictionary<string, int> __state)
		{
			__state = preview;
			preview = null;
			Plugin plugin = instance;
			if (!Object.op_Implicit((Object)(object)plugin))
			{
				return;
			}
			try
			{
				if (plugin.Context(__instance, out var player, out var table))
				{
					List<Cost> list = plugin.Costs(__instance, table);
					if (list != null)
					{
						preview = plugin.bridge.Preview(player, list);
					}
				}
			}
			catch (Exception ex)
			{
				plugin.Fail(ex);
			}
		}

		private static void EndPreview(Dictionary<string, int> __state)
		{
			preview = __state;
		}

		private static bool BeforeUpgrade(UpgradeTableUI __instance)
		{
			Plugin plugin = instance;
			if (!Object.op_Implicit((Object)(object)plugin))
			{
				return true;
			}
			try
			{
				if (!plugin.Context(__instance, out var player, out var table) || player.NoCostCheat())
				{
					return true;
				}
				List<Cost> list = plugin.Costs(__instance, table);
				if (list == null || list.Count == 0)
				{
					return true;
				}
				if (plugin.bridge.Pull(player, list, out var reason))
				{
					return true;
				}
				((EnchantingTableUIPanelBase)__instance).Cancel();
				((Character)player).Message((MessageType)2, reason, 0, (Sprite)null, false);
				__instance.Refresh();
				return false;
			}
			catch (Exception ex)
			{
				plugin.Fail(ex);
				((EnchantingTableUIPanelBase)__instance).Cancel();
				if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
				{
					((Character)Player.m_localPlayer).Message((MessageType)2, "Enchanting material pull stopped. Check the log before retrying.", 0, (Sprite)null, false);
				}
				return false;
			}
		}

		private void Fail(Exception ex)
		{
			if (faulted)
			{
				return;
			}
			faulted = true;
			storedPreview = null;
			((BaseUnityPlugin)this).Logger.LogError((object)("External enchanting items disabled for this session after an error; no automatic retry. Restart the client before using the table again. " + ex));
			try
			{
				string text = bridge?.RecoveryRecord();
				if (text != null)
				{
					string text2 = Path.Combine(Paths.ConfigPath, "RunicEnchantingCompatibility-recovery");
					Directory.CreateDirectory(text2);
					string text3 = Path.Combine(text2, DateTime.UtcNow.ToString("yyyyMMdd-HHmmss") + "-" + Guid.NewGuid().ToString("N") + ".txt");
					File.WriteAllText(text3, "Recovery evidence only. Do not import automatically.\n" + text, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
					((BaseUnityPlugin)this).Logger.LogError((object)("Preserved endpoint and staging contents for investigation: " + text3));
				}
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not write recovery evidence: " + ex2));
			}
		}

		private void OnDestroy()
		{
			ready = false;
			if (provider)
			{
				API.UnregisterInventoryProvider("vercadi.RunicEnchantingCompatibility");
			}
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)instance == (Object)(object)this)
			{
				instance = null;
				preview = null;
			}
		}
	}
	internal sealed class Cost
	{
		internal readonly string Name;

		internal readonly string Resource;

		internal readonly int Amount;

		internal Cost(string name, string resource, int amount)
		{
			Name = name;
			Resource = resource;
			Amount = amount;
		}
	}
	internal static class Rules
	{
		internal static List<Cost> Normalize(IEnumerable<Cost> rows)
		{
			Dictionary<string, Cost> dictionary = new Dictionary<string, Cost>(StringComparer.Ordinal);
			int num = 0;
			int num2 = 0;
			foreach (Cost row in rows)
			{
				if (++num > 32 || row == null || string.IsNullOrEmpty(row.Name) || string.IsNullOrEmpty(row.Resource) || row.Amount <= 0 || row.Amount > 100000 || (num2 += row.Amount) > 100000)
				{
					throw new NotSupportedException("Unsupported or excessive table upgrade cost.");
				}
				if (dictionary.TryGetValue(row.Name, out var value))
				{
					if (value.Resource != row.Resource)
					{
						throw new NotSupportedException("Ambiguous material name in upgrade cost.");
					}
					dictionary[row.Name] = new Cost(row.Name, row.Resource, value.Amount + row.Amount);
				}
				else
				{
					dictionary.Add(row.Name, row);
				}
			}
			return dictionary.Values.OrderBy<Cost, string>((Cost c) => c.Name, StringComparer.Ordinal).ToList();
		}

		internal static List<Cost> Missing(IEnumerable<Cost> costs, Func<string, int> carried)
		{
			return (from c in costs
				select new Cost(c.Name, c.Resource, Math.Max(0, c.Amount - Math.Max(0, carried(c.Name)))) into c
				where c.Amount > 0
				select c).ToList();
		}
	}
	internal sealed class RunicBridge
	{
		private sealed class Source
		{
			internal Container Chest;

			internal Inventory Inventory;

			internal List<ItemData> Items;

			internal BackpackAccess.Endpoint Bag;
		}

		private sealed class Line
		{
			internal Source Source;

			internal ItemData Item;

			internal int Quantity;

			internal int SlotX = -1;

			internal int SlotY;

			internal ItemData PreviewItem;
		}

		private sealed class Origin
		{
			internal Container Chest;

			internal Inventory BagInventory;

			internal ItemData Parent;

			internal ItemData Proof;
		}

		private sealed class TransferEndpoint
		{
			internal Inventory Live;

			internal Inventory Shadow;

			internal object Before;

			internal Func<bool> Authority;

			internal Source Source;

			internal string Name;
		}

		private sealed class NoCapacityException : Exception
		{
		}

		private readonly MethodInfo query;

		private readonly MethodInfo eligible;

		private readonly MethodInfo readPreview;

		private readonly MethodInfo previewCount;

		private readonly MethodInfo resourceId;

		private readonly MethodInfo begin;

		private readonly MethodInfo acquire;

		private readonly MethodInfo move;

		private readonly MethodInfo restore;

		private readonly MethodInfo save;

		private readonly MethodInfo protect;

		private readonly MethodInfo stateAt;

		private readonly MethodInfo isBlocked;

		private readonly MethodInfo block;

		private readonly MethodInfo captureAuthority;

		private readonly ConstructorInfo snapshot;

		private BackpackAccess backpack;

		private readonly MethodInfo shadow;

		private readonly PropertyInfo range;

		private readonly PropertyInfo candidates;

		private readonly PropertyInfo returned;

		private readonly PropertyInfo initialized;

		private readonly PropertyInfo enabled;

		private readonly PropertyInfo crafting;

		private readonly PropertyInfo wardAccess;

		private readonly PropertyInfo storageEnabled;

		private const BindingFlags All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private readonly MethodInfo addAt;

		private readonly MethodInfo sameItem;

		private readonly ConditionalWeakTable<ItemData, Origin> origins = new ConditionalWeakTable<ItemData, Origin>();

		private Func<int, int, bool> ordinarySlot = (int x, int y) => true;

		private List<TransferEndpoint> recoveryEndpoints;

		internal bool Enabled
		{
			get
			{
				if (Flag(enabled) && Flag(crafting) && Flag(storageEnabled))
				{
					return (bool)initialized.GetValue(null);
				}
				return false;
			}
		}

		private float Radius => (float)range.GetValue(null);

		internal void SetBackpack(BackpackAccess access)
		{
			backpack = access;
		}

		private static MethodInfo Method(Type t, string name, int args)
		{
			return t.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Single((MethodInfo m) => m.Name == name && m.GetParameters().Length == args);
		}

		private static PropertyInfo Property(Type t, string name)
		{
			return t.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMemberException(t.FullName, name);
		}

		private static object Call(MethodInfo m, object target, params object[] args)
		{
			return m.Invoke(target, args);
		}

		private static bool Flag(PropertyInfo f)
		{
			return ((ConfigEntry<bool>)f.GetValue(null)).Value;
		}

		internal RunicBridge(Assembly craftingAssembly, Assembly storageAssembly)
		{
			Assembly craftingAssembly2 = craftingAssembly;
			Assembly storageAssembly2 = storageAssembly;
			base..ctor();
			query = Method(C("Integration.ContainerSpatialIndex"), "Query", 3);
			eligible = Method(C("Integration.ContainerQueryRuntime"), "IsEligibleContainer", 6);
			readPreview = Method(C("Integration.ValheimReflection"), "TryReadContainerInventory", 2);
			previewCount = Method(C("Integration.PreviewMaterialCounts"), "Count", 1);
			resourceId = C("Integration.ValheimReflection").GetMethod("ResourceId", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(ItemData) }, null) ?? throw new MissingMethodException("Material identity method unavailable.");
			range = Property(C("Configuration"), "SafeRangeCap");
			candidates = Property(C("Configuration"), "SafeMaximumCandidates");
			returned = Property(C("Configuration"), "SafeMaximumReturned");
			initialized = Property(C("Integration.CraftingRuntime"), "IsInitialized");
			enabled = Property(C("Configuration"), "Enabled");
			crafting = Property(C("Configuration"), "CraftFromContainers");
			wardAccess = Property(C("Configuration"), "RequireWardAccess");
			storageEnabled = Property(S("PluginConfig"), "Enabled");
			begin = Method(S("Runtime.StorageActions"), "TryBeginMutation", 4);
			acquire = Method(S("Runtime.StorageContainerAuthority"), "TryGetSynchronizedServerInventory", 2);
			move = Method(S("Runtime.ValheimContainerService"), "MoveUpTo", 9);
			restore = Method(S("Runtime.ValheimContainerService"), "RestoreInventory", 4);
			isBlocked = Method(storageAssembly2.GetType("RunicAutomation.MutationGate", throwOnError: true), "IsBlocked", 1);
			block = Method(storageAssembly2.GetType("RunicAutomation.MutationGate", throwOnError: true), "Block", 1);
			captureAuthority = Method(S("Runtime.StorageContainerAuthority"), "CaptureAuthority", 1);
			save = Method(S("Runtime.ValheimContainerService"), "SaveInventory", 1);
			protect = Method(S("Engine.StorageItemProtection"), "TryCapture", 4).MakeGenericMethod(typeof(ItemData));
			stateAt = Method(S("Engine.StorageProtectionSnapshot"), "StateAt", 1);
			snapshot = S("Runtime.StorageInventorySnapshot").GetConstructor(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Inventory) }, null) ?? throw new MissingMethodException("Exact inventory snapshot unavailable.");
			shadow = Method(snapshot.DeclaringType, "CreateShadow", 0);
			addAt = Method(S("Runtime.ValheimContainerService"), "AddAt", 4);
			sameItem = Method(S("Runtime.ValheimContainerService"), "CanMergeExact", 3);
			Type C(string n)
			{
				return craftingAssembly2.GetType("RunicCrafting." + n, throwOnError: true);
			}
			Type S(string n)
			{
				return storageAssembly2.GetType("RunicStorage." + n, throwOnError: true);
			}
		}

		internal bool AllowsTable(Vector3 position)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if (Flag(wardAccess))
			{
				return PrivateArea.CheckAccess(position, 0f, false, false);
			}
			return true;
		}

		internal string Resource(ItemData item)
		{
			return (string)Call(resourceId, null, item);
		}

		private bool Eligible(Container chest, Player player, bool writable)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			return (bool)Call(eligible, null, chest, player, ((Component)player).transform.position, Radius, null, writable);
		}

		private List<Container> Nearby(Player player, bool writable)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			List<Container> list = new List<Container>();
			foreach (Container item in (IReadOnlyList<Container>)Call(query, null, ((Component)player).transform.position, Radius, (int)candidates.GetValue(null)))
			{
				if (list.Count >= (int)returned.GetValue(null))
				{
					break;
				}
				if (Eligible(item, player, writable))
				{
					list.Add(item);
				}
			}
			return list;
		}

		private Inventory Acquire(Container chest)
		{
			object[] array = new object[2] { chest, null };
			if (!(bool)Call(acquire, null, array))
			{
				return null;
			}
			object obj = array[1];
			return (Inventory)((obj is Inventory) ? obj : null);
		}

		private string Serialized(Inventory inventory)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			return ((ZPackage)Call(save, null, inventory)).GetBase64();
		}

		internal Dictionary<string, int> Preview(Player player, List<Cost> costs)
		{
			Dictionary<string, int> dictionary = costs.ToDictionary<Cost, string, int>((Cost c) => c.Name, (Cost c) => 0, StringComparer.Ordinal);
			BackpackAccess.Endpoint endpoint = backpack?.Current(player);
			if (endpoint != null)
			{
				foreach (Cost cost in costs)
				{
					dictionary[cost.Name] = Math.Min(cost.Amount, (from i in Usable(endpoint.Inventory)
						where i.m_shared.m_name == cost.Name && Resource(i) == cost.Resource
						select i).Sum((ItemData i) => i.m_stack));
				}
			}
			foreach (Container item in Nearby(player, writable: false))
			{
				object[] array = new object[2] { item, null };
				if (!(bool)Call(readPreview, null, array))
				{
					continue;
				}
				foreach (Cost cost2 in costs)
				{
					int val = (int)Call(previewCount, array[1], cost2.Resource);
					dictionary[cost2.Name] = (int)Math.Min(cost2.Amount, (long)dictionary[cost2.Name] + (long)Math.Max(0, val));
				}
			}
			return dictionary;
		}

		private int Move(Inventory source, Inventory destination, ItemData item, int quantity, Player player, object lease, Container chest)
		{
			object[] args = new object[9] { source, destination, item, quantity, null, player, lease, chest, null };
			return (int)Call(move, null, args);
		}

		private bool Valid(Source source, Player player)
		{
			if (source.Bag == null)
			{
				if (Eligible(source.Chest, player, writable: true))
				{
					return Acquire(source.Chest) == source.Inventory;
				}
				return false;
			}
			return source.Bag.Valid();
		}

		private List<ItemData> Usable(Inventory inventory)
		{
			List<ItemData> list = (from i in inventory.GetAllItems()
				orderby i.m_gridPos.y, i.m_gridPos.x
				select i).ToList();
			object[] array = new object[4] { list, null, null, true };
			if (list.Count > 1024 || !(bool)Call(protect, null, array))
			{
				return new List<ItemData>();
			}
			List<ItemData> list2 = new List<ItemData>();
			for (int num = 0; num < list.Count; num++)
			{
				ItemData val = list[num];
				if (val?.m_shared != null && val.m_stack > 0 && !val.m_equipped && !val.m_shared.m_questItem && val.m_worldLevel >= Game.m_worldLevel && Convert.ToInt32(Call(stateAt, array[1], num)) != 2)
				{
					list2.Add(val);
				}
			}
			return list2;
		}

		internal bool Pull(Player player, List<Cost> costs, out string reason)
		{
			Dictionary<ItemData, ItemData> remapped;
			return Pull(player, costs, new List<Selection>(), out remapped, out reason);
		}

		internal bool Pull(Player player, List<Cost> costs, List<Selection> selections, out Dictionary<ItemData, ItemData> remapped, out string reason)
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Expected O, but got Unknown
			remapped = new Dictionary<ItemData, ItemData>();
			reason = "Nearby materials are unavailable or busy. Close any open chests and try again.";
			List<Cost> list = Rules.Missing(costs, (string n) => ((Humanoid)player).GetInventory().CountItems(n, -1, true));
			if (!ValidateCarried(player, selections, costs, remapped, out reason))
			{
				return false;
			}
			if (list.Count == 0 && remapped.Count == selections.Count)
			{
				return true;
			}
			reason = "Nearby items or materials are unavailable or busy. Close chests and reselect the action.";
			object[] array = new object[4] { "Enchanting table upgrade", null, null, true };
			if (!(bool)Call(begin, null, array))
			{
				return false;
			}
			using IDisposable lease = (IDisposable)array[2];
			if ((Object)(Player)array[1] != (Object)(object)player)
			{
				return false;
			}
			List<Source> list2 = new List<Source>();
			BackpackAccess.Endpoint endpoint = backpack?.Current(player);
			if (endpoint != null)
			{
				list2.Add(new Source
				{
					Bag = endpoint,
					Inventory = endpoint.Inventory,
					Items = Usable(endpoint.Inventory)
				});
			}
			foreach (Container item in Nearby(player, writable: true))
			{
				Inventory val = Acquire(item);
				if (val != null && val != ((Humanoid)player).GetInventory() && val.GetAllItems().Count <= 1024)
				{
					list2.Add(new Source
					{
						Chest = item,
						Inventory = val,
						Items = Usable(val)
					});
				}
			}
			List<Line> list3 = new List<Line>();
			if (!PlanSelections(player, selections, list2, list3, remapped, out reason))
			{
				return false;
			}
			foreach (Cost item2 in list)
			{
				int num = item2.Amount;
				foreach (Source item3 in list2)
				{
					foreach (ItemData item4 in item3.Items)
					{
						if (num > 0 && !(item4.m_shared.m_name != item2.Name) && !(Resource(item4) != item2.Resource))
						{
							int num2 = Math.Min(num, item4.m_stack);
							list3.Add(new Line
							{
								Source = item3,
								Item = item4,
								Quantity = num2
							});
							num -= num2;
						}
					}
				}
				if (num > 0)
				{
					return false;
				}
			}
			if (list3.Count > 1024)
			{
				reason = "Too many material stacks for one upgrade.";
				return false;
			}
			if (!Transfer(player, lease, list3, costs, out reason))
			{
				return false;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			foreach (Line item5 in list3.Where((Line l) => l.PreviewItem != null))
			{
				remapped.Add(item5.PreviewItem, inventory.GetItemAt(item5.SlotX, item5.SlotY));
			}
			reason = "";
			return true;
		}

		private object Restore(Inventory inventory, object backup, Player player, object lease)
		{
			if ((bool)Call(isBlocked, null, inventory))
			{
				throw new InvalidOperationException("Runic marked this inventory for recovery; automatic rollback is blocked.");
			}
			return Call(restore, null, inventory, backup, player, lease);
		}

		internal List<ItemData> ReadItems(Player player)
		{
			List<ItemData> result = new List<ItemData>();
			HashSet<Container> hashSet = new HashSet<Container>();
			BackpackAccess.Endpoint endpoint = backpack?.Current(player);
			if (endpoint != null)
			{
				Add(endpoint.Inventory, null, endpoint);
			}
			foreach (Container item in Nearby(player, writable: false))
			{
				if (hashSet.Add(item))
				{
					Add(ChestPreview.Read(item), item, null);
				}
			}
			return result;
			void Add(Inventory inventory, Container chest, BackpackAccess.Endpoint bag)
			{
				if (inventory == null || inventory.GetAllItems().Count > 1024)
				{
					return;
				}
				foreach (ItemData item2 in Usable(inventory))
				{
					if (result.Count >= 4096)
					{
						break;
					}
					ItemData val = item2.Clone();
					origins.Add(val, new Origin
					{
						Chest = chest,
						BagInventory = bag?.Inventory,
						Parent = bag?.Parent,
						Proof = item2.Clone()
					});
					result.Add(val);
				}
			}
		}

		private bool Same(ItemData a, ItemData b)
		{
			return (bool)Call(sameItem, null, a, b, false);
		}

		private bool ValidateCarried(Player player, List<Selection> selections, List<Cost> costs, Dictionary<ItemData, ItemData> map, out string reason)
		{
			reason = "The selected items changed. Reopen the table and select them again.";
			if (selections.Count > 128 || selections.Any((Selection s) => s == null || s.Item == null || s.Amount <= 0 || s.Amount > s.Item.m_stack) || selections.Select((Selection s) => s.Item).Distinct().Count() != selections.Count)
			{
				return false;
			}
			if (selections.Any((Selection s) => costs.Any((Cost c) => c.Name == s.Item.m_shared.m_name)))
			{
				reason = "A selected item is also used as this action's currency. Separate the items and use the ordinary inventory flow.";
				return false;
			}
			foreach (Selection selection in selections)
			{
				Origin value;
				if (((Humanoid)player).GetInventory().GetAllItems().Contains(selection.Item))
				{
					map.Add(selection.Item, selection.Item);
				}
				else if (!origins.TryGetValue(selection.Item, out value))
				{
					return false;
				}
			}
			return true;
		}

		private bool PlanSelections(Player player, List<Selection> selections, List<Source> sources, List<Line> lines, Dictionary<ItemData, ItemData> map, out string reason)
		{
			reason = "A selected stored item changed or is ambiguous. Select it again.";
			foreach (Selection selection in selections)
			{
				if (!map.ContainsKey(selection.Item))
				{
					if (!origins.TryGetValue(selection.Item, out var origin))
					{
						return false;
					}
					Source source = sources.FirstOrDefault((Source s) => (!((Object)(object)origin.Chest != (Object)null)) ? (s.Bag != null && s.Inventory == origin.BagInventory && s.Bag.Parent == origin.Parent) : ((Object)(object)s.Chest == (Object)(object)origin.Chest));
					if (source == null)
					{
						reason = "The selected item's storage is busy or still synchronizing. Close chests and try again.";
						return false;
					}
					List<ItemData> list = source.Items.Where((ItemData i) => Same(i, origin.Proof) && i.m_stack == origin.Proof.m_stack).Take(2).ToList();
					if (list.Count != 1)
					{
						return false;
					}
					ItemData item = list[0];
					if (selection.Amount > item.m_stack || selection.Amount > item.m_shared.m_maxStackSize || lines.Any((Line l) => l.Source.Inventory == source.Inventory && l.Item == item))
					{
						return false;
					}
					lines.Add(new Line
					{
						Source = source,
						Item = item,
						Quantity = selection.Amount,
						PreviewItem = selection.Item
					});
				}
			}
			return true;
		}

		private bool FindSlot(Inventory inventory, out int x, out int y)
		{
			for (y = 0; y < inventory.GetHeight(); y++)
			{
				for (x = 0; x < inventory.GetWidth(); x++)
				{
					if (ordinarySlot(x, y) && inventory.GetItemAt(x, y) == null)
					{
						return true;
					}
				}
			}
			x = (y = -1);
			return false;
		}

		internal void SetSlotPolicy(Func<int, int, bool> policy)
		{
			ordinarySlot = policy ?? throw new ArgumentNullException("policy");
		}

		internal string RecoveryRecord()
		{
			if (recoveryEndpoints == null)
			{
				return null;
			}
			return string.Join("\n", recoveryEndpoints.Select((TransferEndpoint e) => e.Name + "\nbefore\n" + Serialized((Inventory)Call(shadow, e.Before)) + "\ncurrent\n" + Serialized(e.Live)));
		}

		private bool Transfer(Player player, object lease, List<Line> lines, List<Cost> costs, out string reason)
		{
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_024c: Expected O, but got Unknown
			//IL_033b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0342: Expected O, but got Unknown
			//IL_04e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ee: Expected O, but got Unknown
			//IL_03be: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d1: Unknown result type (might be due to invalid IL or missing references)
			reason = "Keep enough ordinary inventory space for the selected items and materials.";
			List<TransferEndpoint> endpoints = new List<TransferEndpoint>();
			Inventory playerInventory = ((Humanoid)player).GetInventory();
			TransferEndpoint carried = Endpoint("player", playerInventory, () => (Object)(object)player == (Object)(object)Player.m_localPlayer && ((Character)player).IsOwner() && ((Humanoid)player).GetInventory() == playerInventory);
			Dictionary<Source, TransferEndpoint> dictionary = new Dictionary<Source, TransferEndpoint>();
			foreach (Source source in lines.Select((Line l) => l.Source).Distinct())
			{
				Func<bool> authority = ((source.Bag == null) ? ((Func<bool>)Call(captureAuthority, null, source.Chest)) : new Func<bool>(source.Bag.Valid));
				dictionary.Add(source, Endpoint("source-" + dictionary.Count, source.Inventory, () => authority() && Valid(source, player), source));
			}
			Dictionary<Line, TransferEndpoint> dictionary2 = new Dictionary<Line, TransferEndpoint>();
			foreach (Line item in lines.Where((Line l) => l.Source.Bag != null && l.PreviewItem == null))
			{
				int num;
				checked
				{
					num = unchecked(checked(item.Quantity - 1) / Math.Max(1, item.Item.m_shared.m_maxStackSize)) + 1;
					if (num > 1024)
					{
						throw new NotSupportedException("Transfer staging limit exceeded.");
					}
				}
				dictionary2.Add(item, Endpoint("staging-" + dictionary2.Count, new Inventory("Compatibility material staging", (Sprite)null, 8, (num + 7) / 8), () => true));
			}
			List<Action> steps = new List<Action>();
			List<string[]> checkpoints = new List<string[]> { Shadows() };
			long checkpointCharacters = ((IEnumerable<string>)checkpoints[0]).Sum((Func<string, long>)((string s) => s.Length));
			try
			{
				foreach (Line line in lines)
				{
					TransferEndpoint source2 = dictionary[line.Source];
					Inventory before = (Inventory)Call(shadow, snapshot.Invoke(new object[1] { carried.Shadow }));
					TransferEndpoint value;
					if (line.PreviewItem != null)
					{
						if (!FindSlot(carried.Shadow, out line.SlotX, out line.SlotY))
						{
							return false;
						}
						Vector2i position = line.Item.m_gridPos;
						Vector2i slot = position;
						slot.x = line.SlotX;
						slot.y = line.SlotY;
						AddStep(delegate
						{
							//IL_007d: Unknown result type (might be due to invalid IL or missing references)
							Call(addAt, null, carried.Shadow, source2.Shadow.GetItemAt(position.x, position.y), line.Quantity, slot);
						}, delegate
						{
							//IL_0062: Unknown result type (might be due to invalid IL or missing references)
							Call(addAt, null, carried.Live, line.Item, line.Quantity, slot);
						});
						AddStep(delegate
						{
							if (!source2.Shadow.RemoveItem(source2.Shadow.GetItemAt(position.x, position.y), line.Quantity))
							{
								throw new InvalidOperationException("Selected shadow item changed.");
							}
							ProjectBag(source2);
						}, delegate
						{
							if (!source2.Live.RemoveItem(line.Item, line.Quantity))
							{
								throw new InvalidOperationException("Selected source item changed.");
							}
							line.Source.Bag?.Persist(Serialized(source2.Live));
						});
					}
					else if (dictionary2.TryGetValue(line, out value))
					{
						NativeMove(source2, value, line.Item, line.Quantity, null);
						before = (Inventory)Call(shadow, snapshot.Invoke(new object[1] { carried.Shadow }));
						ItemData[] array = value.Shadow.GetAllItems().ToArray();
						foreach (ItemData val in array)
						{
							NativeMove(value, carried, val, val.m_stack, null);
						}
					}
					else
					{
						NativeMove(source2, carried, line.Item, line.Quantity, line.Source.Chest);
					}
					if (line.PreviewItem != null && line.Source.Bag != null)
					{
						line.Source.Bag.UpdateShadowParent(before, source2.Shadow, Serialized(source2.Shadow));
					}
					if (!FitsPlayer(before))
					{
						return false;
					}
				}
			}
			catch (NoCapacityException)
			{
				return false;
			}
			CheckedTransfer.Run(steps, checkpoints, Read, delegate
			{
				if (Rules.Missing(costs, (string n) => playerInventory.CountItems(n, -1, true)).Count != 0)
				{
					throw new InvalidOperationException("The complete table cost was not delivered.");
				}
			}, Recover, Quarantine);
			reason = "";
			return true;
			void AddStep(Action project, Action execute)
			{
				project();
				string[] array2 = Shadows();
				checkpointCharacters += ((IEnumerable<string>)array2).Sum((Func<string, long>)((string s) => s.Length));
				if (steps.Count >= 1024 || checkpointCharacters > 16777216)
				{
					throw new NotSupportedException("Enchanting transfer exceeds the bounded recovery-plan size.");
				}
				checkpoints.Add(array2);
				steps.Add(execute);
			}
			TransferEndpoint Endpoint(string name, Inventory live, Func<bool> authority2, Source source3 = null)
			{
				//IL_0087: Unknown result type (might be due to invalid IL or missing references)
				//IL_0091: Expected O, but got Unknown
				if (endpoints.Any((TransferEndpoint e) => e.Live == live))
				{
					throw new InvalidOperationException("Duplicate transfer inventory.");
				}
				object obj = snapshot.Invoke(new object[1] { live });
				TransferEndpoint transferEndpoint = new TransferEndpoint
				{
					Name = name,
					Live = live,
					Before = obj,
					Shadow = (Inventory)Call(shadow, obj),
					Authority = authority2,
					Source = source3
				};
				endpoints.Add(transferEndpoint);
				return transferEndpoint;
			}
			bool FitsPlayer(Inventory val2)
			{
				foreach (ItemData allItem in carried.Shadow.GetAllItems())
				{
					ItemData itemAt = val2.GetItemAt(allItem.m_gridPos.x, allItem.m_gridPos.y);
					if ((itemAt == null || !Same(allItem, itemAt) || allItem.m_stack != itemAt.m_stack) && !ordinarySlot(allItem.m_gridPos.x, allItem.m_gridPos.y))
					{
						return false;
					}
				}
				return true;
			}
			void NativeMove(TransferEndpoint from, TransferEndpoint to, ItemData original, int amount, Container chest)
			{
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0032: Unknown result type (might be due to invalid IL or missing references)
				Vector2i position2 = original.m_gridPos;
				AddStep(delegate
				{
					if (Move(from.Shadow, to.Shadow, from.Shadow.GetItemAt(position2.x, position2.y), amount, null, lease, chest) != amount)
					{
						throw new NoCapacityException();
					}
					ProjectBag(from);
				}, delegate
				{
					if (Move(from.Live, to.Live, from.Live.GetItemAt(position2.x, position2.y), amount, player, lease, chest) != amount)
					{
						throw new InvalidOperationException("Material transfer was incomplete.");
					}
					from.Source?.Bag?.Persist(Serialized(from.Live));
				});
			}
			void ProjectBag(TransferEndpoint e)
			{
				e.Source?.Bag?.UpdateShadowParent(carried.Shadow, e.Shadow, Serialized(e.Shadow));
			}
			void Quarantine()
			{
				recoveryEndpoints = endpoints;
				foreach (TransferEndpoint item2 in endpoints)
				{
					Call(block, null, item2.Live);
				}
			}
			string[] Read()
			{
				foreach (TransferEndpoint item3 in endpoints)
				{
					if (!item3.Authority() || (bool)Call(isBlocked, null, item3.Live))
					{
						throw new InvalidOperationException("Enchanting endpoint ownership changed or requires recovery: " + item3.Name);
					}
				}
				return endpoints.Select((TransferEndpoint e) => Serialized(e.Live)).ToArray();
			}
			void Recover(string[] array2)
			{
				//IL_0057: Unknown result type (might be due to invalid IL or missing references)
				//IL_0061: Expected O, but got Unknown
				//IL_0103: Unknown result type (might be due to invalid IL or missing references)
				//IL_010d: Expected O, but got Unknown
				CheckedTransfer.Match(Read(), array2);
				foreach (TransferEndpoint item4 in endpoints)
				{
					item4.Shadow = (Inventory)Call(shadow, snapshot.Invoke(new object[1] { item4.Live }));
				}
				foreach (TransferEndpoint item5 in (from e in endpoints
					where e != carried
					orderby (e.Source?.Bag != null) ? 1 : 0
					select e).Concat(new TransferEndpoint[1] { carried }))
				{
					CheckedTransfer.Match(Read(), array2);
					item5.Shadow = (Inventory)Call(shadow, item5.Before);
					ProjectBag(item5);
					string[] array3 = Shadows();
					if (!array2.SequenceEqual<string>(array3, StringComparer.Ordinal))
					{
						Restore(item5.Live, item5.Before, player, lease);
						item5.Source?.Bag?.Persist(Serialized(item5.Live));
						CheckedTransfer.Match(Read(), array3);
						array2 = array3;
					}
				}
			}
			string[] Shadows()
			{
				return endpoints.Select((TransferEndpoint e) => Serialized(e.Shadow)).ToArray();
			}
		}
	}
	internal sealed class Selection
	{
		internal readonly ItemData Item;

		internal readonly int Amount;

		internal Selection(ItemData item, int amount)
		{
			Item = item;
			Amount = amount;
		}
	}
	internal sealed class TableActions
	{
		internal sealed class Plan
		{
			internal List<Selection> Selections;

			internal List<Cost> Costs;

			internal List<InventoryItemListElement> Rows;
		}

		internal static readonly Type[] Panels = new Type[6]
		{
			typeof(SacrificeUI),
			typeof(EnchantUI),
			typeof(AugmentUI),
			typeof(DisenchantUI),
			typeof(ConvertUI),
			typeof(RuneUI)
		};

		private const BindingFlags All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private readonly Dictionary<string, MethodInfo> costs = new Dictionary<string, MethodInfo>();

		private readonly FieldInfo sacrificeMode;

		private readonly FieldInfo rarity;

		private readonly FieldInfo augmentIndex;

		private readonly FieldInfo runeMode;

		private readonly FieldInfo runeRarity;

		private readonly FieldInfo runeItem;

		private readonly FieldInfo overrideRune;

		private readonly FieldInfo locked;

		private readonly MethodInfo costReduction;

		internal TableActions()
		{
			Type type = typeof(SacrificeUI).Assembly.GetType("EpicLoot.CraftingV2.EnchantingUIController", throwOnError: true);
			string[] array = new string[6] { "GetIdentifyCostForCategory", "GetEnchantCost", "GetAugmentCost", "GetDisenchantCost", "GetRuneExtractCost", "GetRuneEtchCost" };
			foreach (string text in array)
			{
				costs.Add(text, type.GetMethod(text, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMethodException(text));
			}
			sacrificeMode = Field(typeof(SacrificeUI), "_sacrificeMode");
			rarity = Field(typeof(EnchantUI), "_rarity");
			augmentIndex = Field(typeof(AugmentUI), "_augmentIndex");
			runeMode = Field(typeof(RuneUI), "_runeAction");
			runeRarity = Field(typeof(RuneUI), "_selectedRarity");
			runeItem = Field(typeof(RuneUI), "_selectedItem");
			overrideRune = Field(typeof(RuneUI), "_selectedOverrideRune");
			locked = Field(typeof(EnchantingTableUIPanelBase), "_locked");
			costReduction = typeof(RuneUI).GetMethod("GetCostReduction", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMethodException("Rune cost reduction");
			static FieldInfo Field(Type type2, string name)
			{
				return type2.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException(type2.FullName, name);
			}
		}

		internal Plan Create(EnchantingTableUIPanelBase panel, EnchantingTable table, RunicBridge bridge)
		{
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_034a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0359: Unknown result type (might be due to invalid IL or missing references)
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0295: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e8: Unknown result type (might be due to invalid IL or missing references)
			List<Tuple<IListElement, int>> list = ((panel is ConvertUI) ? new List<Tuple<IListElement, int>>() : panel.AvailableItems.GetSelectedItems<IListElement>());
			if (!(panel is SacrificeUI) && list.Count > 1)
			{
				throw new NotSupportedException("Unexpected multiple table targets.");
			}
			if (list.Any((Tuple<IListElement, int> x) => !(x.Item1 is InventoryItemListElement)))
			{
				throw new NotSupportedException("Unsupported table item row.");
			}
			List<InventoryItemListElement> list2 = ((IEnumerable<Tuple<IListElement, int>>)list).Select((Func<Tuple<IListElement, int>, InventoryItemListElement>)((Tuple<IListElement, int> x) => (InventoryItemListElement)x.Item1)).ToList();
			List<Selection> list3 = list.Select((Tuple<IListElement, int> x) => new Selection(x.Item1.GetItem(), x.Item2)).ToList();
			ItemData val = list3.FirstOrDefault()?.Item;
			List<InventoryItemListElement> list4 = new List<InventoryItemListElement>();
			SacrificeUI val2 = (SacrificeUI)(object)((panel is SacrificeUI) ? panel : null);
			EnchantingFeature val3;
			if (val2 != null)
			{
				val3 = (EnchantingFeature)0;
				if (sacrificeMode.GetValue(val2).ToString() == "Identify" && val != null)
				{
					list4 = Cost("GetIdentifyCostForCategory", new object[3]
					{
						val2.IdentifyStyle.options[val2.IdentifyStyle.value].text,
						list3.Select((Selection s) => Tuple.Create<ItemData, int>(s.Item, s.Amount)).ToList(),
						Reduction(val3)
					});
				}
			}
			else
			{
				EnchantUI val4 = (EnchantUI)(object)((panel is EnchantUI) ? panel : null);
				if (val4 != null)
				{
					val3 = (EnchantingFeature)2;
					if (val != null)
					{
						list4 = Cost("GetEnchantCost", new object[2]
						{
							val,
							rarity.GetValue(val4)
						});
					}
				}
				else
				{
					AugmentUI val5 = (AugmentUI)(object)((panel is AugmentUI) ? panel : null);
					if (val5 != null)
					{
						val3 = (EnchantingFeature)3;
						if (val != null)
						{
							list4 = Cost("GetAugmentCost", new object[2]
							{
								val,
								augmentIndex.GetValue(val5)
							});
						}
					}
					else if (panel is DisenchantUI)
					{
						val3 = (EnchantingFeature)4;
						if (val != null)
						{
							list4 = Cost("GetDisenchantCost", new object[1] { val });
						}
					}
					else
					{
						ConvertUI val6 = (ConvertUI)(object)((panel is ConvertUI) ? panel : null);
						if (val6 != null)
						{
							val3 = (EnchantingFeature)1;
							list4 = ConvertUI.GetConversionCost(((EnchantingTableUIPanelBase)val6).AvailableItems.GetSelectedItems<ConversionRecipeUnity>());
						}
						else
						{
							RuneUI val7 = (RuneUI)(object)((panel is RuneUI) ? panel : null);
							if (val7 == null)
							{
								throw new NotSupportedException("Unsupported table panel.");
							}
							val3 = (EnchantingFeature)5;
							bool flag = runeMode.GetValue(val7).ToString() == "Etch";
							if (val != null)
							{
								list4 = Cost(flag ? "GetRuneEtchCost" : "GetRuneExtractCost", new object[3]
								{
									val,
									runeRarity.GetValue(val7),
									Reduction(val3)
								});
							}
							if (flag)
							{
								Tuple<InventoryItemListElement, int> singleSelectedItem = val7.AvailableRunes.GetSingleSelectedItem<InventoryItemListElement>();
								if (singleSelectedItem != null)
								{
									list2.Add(singleSelectedItem.Item1);
									list3.Add(new Selection(singleSelectedItem.Item1.Item, 1));
								}
							}
						}
					}
				}
			}
			if (!table.IsFeatureAvailable(val3) || !table.IsFeatureUnlocked(val3))
			{
				return null;
			}
			if (Player.m_localPlayer.NoCostCheat())
			{
				list4.Clear();
			}
			return new Plan
			{
				Rows = list2,
				Selections = list3,
				Costs = Rules.Normalize(list4.Select((InventoryItemListElement r) => new Cost(r.Item?.m_shared?.m_name, bridge.Resource(r.Item), r.Item?.m_stack ?? 0)))
			};
			List<InventoryItemListElement> Cost(string method, params object[] args)
			{
				return (List<InventoryItemListElement>)costs[method].Invoke(null, args);
			}
			float Reduction(EnchantingFeature f)
			{
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				return (float)costReduction.Invoke(null, new object[1] { table.GetFeatureCurrentValue(f).Item1 });
			}
		}

		internal void Remap(EnchantingTableUIPanelBase panel, Plan plan, Dictionary<ItemData, ItemData> map)
		{
			foreach (InventoryItemListElement row in plan.Rows)
			{
				row.Item = map[row.Item];
			}
			if (!(panel is RuneUI))
			{
				return;
			}
			FieldInfo[] array = new FieldInfo[2] { runeItem, overrideRune };
			foreach (FieldInfo fieldInfo in array)
			{
				object? value = fieldInfo.GetValue(panel);
				ItemData val = (ItemData)((value is ItemData) ? value : null);
				if (val != null && map.TryGetValue(val, out var value2))
				{
					fieldInfo.SetValue(panel, value2);
				}
			}
		}

		internal void RefreshCosts(EnchantingTableUIPanelBase panel)
		{
			if (Object.op_Implicit((Object)(object)panel) && ((Component)panel).gameObject.activeInHierarchy && !(bool)locked.GetValue(panel))
			{
				((object)panel).GetType().GetMethod("OnSelectedItemsChanged", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(panel, null);
			}
		}
	}
}