Decompiled source of RepairRequiresMaterials v1.1.2

RepairRequiresMaterials.dll

Decompiled 14 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.ObjectPool;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("RepairRequiresMaterials")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("sighsorry")]
[assembly: AssemblyProduct("RepairRequiresMaterials")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")]
[assembly: AssemblyFileVersion("1.1.2")]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.2.0")]
[module: UnverifiableCode]
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;
		}
	}
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}
namespace RepairRequiresMaterials
{
	internal static class AdminCommands
	{
		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		private static class ZNetRpcPeerInfoPatch
		{
			[HarmonyPostfix]
			private static void Postfix(ZNet __instance, ZRpc rpc)
			{
				RegisterPeerRpcs(__instance, rpc);
			}
		}

		private const string SetDurabilityCommand = "rrm_setdurability";

		private const string SetDurabilityRequestRpc = "sighsorry.RepairRequiresMaterials.RequestSetDurability.v1";

		private const string SetDurabilityResponseRpc = "sighsorry.RepairRequiresMaterials.SetDurabilityResponse.v1";

		private const float AuthorizationTimeoutSeconds = 10f;

		private static bool _registered;

		private static int _nextRequestId;

		private static int _pendingRequestId;

		private static float _pendingPercentage;

		private static float _pendingRequestTime;

		private static ZRpc? _pendingServerRpc;

		private static Terminal? _pendingContext;

		internal static void Register()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_003e: Expected O, but got Unknown
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			if (!_registered)
			{
				_registered = true;
				new ConsoleCommand("rrm_setdurability", "<0-100> - set all durability-bearing equipment in your inventory to a percentage of its quality-adjusted maximum", new ConsoleEventFailable(SetInventoryEquipmentDurability), false, false, false, false, false, false, new ConsoleOptionsFetcher(GetDurabilityOptions), false, false, false);
			}
		}

		private static object SetInventoryEquipmentDurability(ConsoleEventArgs args)
		{
			float num = default(float);
			if (args.Length != 2 || !args.TryParameterFloat(1, ref num) || float.IsNaN(num) || float.IsInfinity(num) || num < 0f || num > 100f)
			{
				return "Usage: rrm_setdurability <0-100>";
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return "A local player is not available.";
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return "A network session is not available.";
			}
			if (!instance.IsServer())
			{
				ZRpc serverRPC = instance.GetServerRPC();
				if (serverRPC == null)
				{
					return "The server connection is not available.";
				}
				int num2 = (_pendingRequestId = NextRequestId());
				_pendingPercentage = num;
				_pendingRequestTime = Time.realtimeSinceStartup;
				_pendingServerRpc = serverRPC;
				_pendingContext = args.Context;
				serverRPC.Invoke("sighsorry.RepairRequiresMaterials.RequestSetDurability.v1", new object[2] { num2, num });
				((MonoBehaviour)localPlayer).StartCoroutine(ExpirePendingRequest(num2));
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString("RepairRequiresMaterials: requesting administrator authorization...");
				}
				return true;
			}
			ApplyInventoryEquipmentDurability(localPlayer, num, args.Context);
			return true;
		}

		private static void ApplyInventoryEquipmentDurability(Player player, float percentage, Terminal? context)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			Inventory inventory = ((Humanoid)player).GetInventory();
			int num = 0;
			int num2 = 0;
			float num3 = percentage / 100f;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem == null || !EquipmentTypeRules.IsEquipment(allItem.m_shared.m_itemType) || !allItem.m_shared.m_useDurability)
				{
					continue;
				}
				float maxDurability = allItem.GetMaxDurability();
				if (maxDurability > 0f && !float.IsNaN(maxDurability) && !float.IsInfinity(maxDurability))
				{
					num++;
					float num4 = Mathf.Clamp(maxDurability * num3, 0f, maxDurability);
					if (!allItem.m_durability.Equals(num4))
					{
						allItem.m_durability = num4;
						num2++;
					}
				}
			}
			if (num2 > 0)
			{
				inventory.Changed(false, false);
			}
			string text = percentage.ToString("0.##", CultureInfo.InvariantCulture);
			AddCommandMessage(context, string.Format("{0}: set {1} of {2} eligible equipment items to {3}% durability.", "RepairRequiresMaterials", num2, num, text));
		}

		private static int NextRequestId()
		{
			_nextRequestId++;
			if (_nextRequestId <= 0)
			{
				_nextRequestId = 1;
			}
			return _nextRequestId;
		}

		private static void RegisterPeerRpcs(ZNet zNet, ZRpc rpc)
		{
			if (zNet.IsServer())
			{
				rpc.Register<int, float>("sighsorry.RepairRequiresMaterials.RequestSetDurability.v1", (Action<ZRpc, int, float>)delegate(ZRpc senderRpc, int requestId, float percentage)
				{
					HandleSetDurabilityRequest(zNet, senderRpc, requestId, percentage);
				});
				return;
			}
			ZNetPeer peer = zNet.GetPeer(rpc);
			if (peer != null && peer.m_server)
			{
				ClearPendingRequest();
				rpc.Register<int, bool, string>("sighsorry.RepairRequiresMaterials.SetDurabilityResponse.v1", (Action<ZRpc, int, bool, string>)delegate(ZRpc senderRpc, int requestId, bool approved, string message)
				{
					HandleSetDurabilityResponse(zNet, senderRpc, requestId, approved, message);
				});
			}
		}

		private static void HandleSetDurabilityRequest(ZNet zNet, ZRpc rpc, int requestId, float percentage)
		{
			if (zNet.IsServer())
			{
				string text = ValidateRemoteRequest(zNet, rpc, requestId, percentage);
				rpc.Invoke("sighsorry.RepairRequiresMaterials.SetDurabilityResponse.v1", new object[3]
				{
					requestId,
					text.Length == 0,
					text
				});
			}
		}

		private static string ValidateRemoteRequest(ZNet zNet, ZRpc rpc, int requestId, float percentage)
		{
			if (requestId <= 0 || float.IsNaN(percentage) || float.IsInfinity(percentage) || percentage < 0f || percentage > 100f)
			{
				return "Invalid durability request.";
			}
			ZNetPeer peer = zNet.GetPeer(rpc);
			if (!rpc.IsConnected() || peer == null || !peer.IsReady())
			{
				return "The requesting player is not available.";
			}
			string hostName = rpc.GetSocket().GetHostName();
			if (string.IsNullOrWhiteSpace(hostName) || !zNet.IsAdmin(hostName))
			{
				return "Administrator or host privileges are required.";
			}
			return string.Empty;
		}

		private static void HandleSetDurabilityResponse(ZNet zNet, ZRpc rpc, int requestId, bool approved, string message)
		{
			if (zNet.IsServer() || requestId != _pendingRequestId || rpc != _pendingServerRpc)
			{
				return;
			}
			float pendingPercentage = _pendingPercentage;
			bool num = Time.realtimeSinceStartup - _pendingRequestTime > 10f;
			Terminal pendingContext = _pendingContext;
			ClearPendingRequest();
			if (num)
			{
				AddCommandMessage(pendingContext, "The administrator authorization request expired.");
				return;
			}
			if (!approved)
			{
				AddCommandMessage(pendingContext, string.IsNullOrWhiteSpace(message) ? "Administrator or host privileges are required." : message);
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				AddCommandMessage(pendingContext, "A local player is not available.");
			}
			else
			{
				ApplyInventoryEquipmentDurability(localPlayer, pendingPercentage, pendingContext);
			}
		}

		private static IEnumerator ExpirePendingRequest(int requestId)
		{
			yield return (object)new WaitForSecondsRealtime(10f);
			if (requestId == _pendingRequestId)
			{
				Terminal? pendingContext = _pendingContext;
				ClearPendingRequest();
				AddCommandMessage(pendingContext, "The administrator authorization request expired.");
			}
		}

		private static void ClearPendingRequest()
		{
			_pendingRequestId = 0;
			_pendingPercentage = 0f;
			_pendingRequestTime = 0f;
			_pendingServerRpc = null;
			_pendingContext = null;
		}

		private static void AddCommandMessage(Terminal? context, string message)
		{
			if ((Object)(object)context != (Object)null)
			{
				context.AddString(message);
				return;
			}
			Console instance = Console.instance;
			if (instance != null)
			{
				((Terminal)instance).AddString(message);
			}
		}

		private static List<string> GetDurabilityOptions()
		{
			return new List<string> { "0", "25", "50", "75", "100" };
		}
	}
	internal static class ArtisanMasteryCompat
	{
		internal const string PluginGuid = "Dreanegade.Artisan_Mastery";

		private const string AnvilPath = "Upgrades/Anvil/x_FurnitureAnvil";

		private static MethodInfo? _playFx;

		private static bool _failureLogged;

		private static Component? _anvil;

		private static CraftingStation? _station;

		private static Player? _player;

		private static InventoryGui? _gui;

		private static string _successFx = string.Empty;

		private static bool _sessionOpen;

		private static readonly List<(GameObject Object, bool WasActive)> HiddenUi = new List<(GameObject, bool)>();

		private static UIGroupHandler? _craftingGroup;

		private static GameObject? _defaultElement;

		private static GameObject? _rightStickElement;

		private static FieldRef<InventoryGui, float> _craftTimer = null;

		private static FieldRef<InventoryGui, Recipe> _craftRecipe = null;

		private static FieldRef<InventoryGui, Animator> _animator = null;

		private static FieldRef<UIGroupHandler, GameObject> _rightStickSelectable = null;

		private static Action<InventoryGui, List<Recipe>> _updateRecipeList = null;

		private static Action<InventoryGui, int, bool> _setRecipe = null;

		private static readonly List<Recipe> EmptyRecipes = new List<Recipe>();

		internal static void Initialize(Harmony harmony)
		{
			//IL_03e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0402: Expected O, but got Unknown
			//IL_0416: Unknown result type (might be due to invalid IL or missing references)
			//IL_0423: Expected O, but got Unknown
			if (!Chainloader.PluginInfos.TryGetValue("Dreanegade.Artisan_Mastery", out var value) || (Object)(object)value.Instance == (Object)null)
			{
				return;
			}
			Type type = ((object)value.Instance).GetType().Assembly.GetType("Artisan_Mastery.RepairAllTable");
			MethodInfo methodInfo = ((type == null) ? null : AccessTools.DeclaredMethod(type, "Interact", new Type[3]
			{
				typeof(Humanoid),
				typeof(bool),
				typeof(bool)
			}, (Type[])null));
			MethodInfo methodInfo2 = ((type == null) ? null : AccessTools.DeclaredMethod(type, "GetHoverText", Type.EmptyTypes, (Type[])null));
			_playFx = ((type == null) ? null : AccessTools.DeclaredMethod(type, "PlayFxEverybody", new Type[1] { typeof(string) }, (Type[])null));
			if (type == null || !typeof(Component).IsAssignableFrom(type) || methodInfo == null || methodInfo.IsStatic || methodInfo.ReturnType != typeof(bool) || methodInfo2 == null || methodInfo2.IsStatic || methodInfo2.ReturnType != typeof(string) || _playFx == null || _playFx.IsStatic || _playFx.ReturnType != typeof(void) || !HasField(type, "_nextUseTime", typeof(float)) || !HasField(type, "CooldownSeconds", typeof(float)) || !HasField(type, "RpcSuccessFx", typeof(string)))
			{
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)"Artisan Mastery repair-table API was not recognized. Galleon repair compatibility could not be enabled.");
				return;
			}
			try
			{
				_craftTimer = AccessTools.FieldRefAccess<InventoryGui, float>("m_craftTimer");
				_craftRecipe = AccessTools.FieldRefAccess<InventoryGui, Recipe>("m_craftRecipe");
				_animator = AccessTools.FieldRefAccess<InventoryGui, Animator>("m_animator");
				_rightStickSelectable = AccessTools.FieldRefAccess<UIGroupHandler, GameObject>("m_rightStickSelectable");
				_updateRecipeList = AccessTools.MethodDelegate<Action<InventoryGui, List<Recipe>>>(AccessTools.DeclaredMethod(typeof(InventoryGui), "UpdateRecipeList", new Type[1] { typeof(List<Recipe>) }, (Type[])null), (object)null, true);
				_setRecipe = AccessTools.MethodDelegate<Action<InventoryGui, int, bool>>(AccessTools.DeclaredMethod(typeof(InventoryGui), "SetRecipe", new Type[2]
				{
					typeof(int),
					typeof(bool)
				}, (Type[])null), (object)null, true);
				PatchPrefix(harmony, typeof(InventoryGui), "Update", "ValidateSessionPrefix", Type.EmptyTypes);
				PatchPrefix(harmony, typeof(InventoryGui), "UpdateCraftingPanel", "RepairOnlyPanelPrefix", typeof(bool));
				PatchPrefix(harmony, typeof(InventoryGui), "UpdateRecipe", "RepairOnlyPanelPrefix", typeof(Player), typeof(float));
				PatchPrefix(harmony, typeof(InventoryGui), "OnCraftPressed", "BlockCraftingPrefix", Type.EmptyTypes);
				PatchPrefix(harmony, typeof(InventoryGui), "DoCrafting", "BlockCraftingPrefix", typeof(Player));
				PatchPrefix(harmony, typeof(InventoryGui), "UpdateRecipeGamepadInput", "GamepadSelectionPrefix", Type.EmptyTypes);
				PatchPrefix(harmony, typeof(Player), "SetCraftingStation", "ChangeStationPrefix", typeof(CraftingStation));
				PatchPrefix(harmony, typeof(Player), "AddKnownStation", "AddKnownStationPrefix", typeof(CraftingStation));
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ArtisanMasteryCompat), "InteractPrefix", (Type[])null)
				{
					priority = 800
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(ArtisanMasteryCompat), "HoverPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				RepairRequiresMaterialsPlugin.Log.LogInfo((object)"Artisan Mastery galleon anvil now opens RRM's repair-only station.");
			}
			catch (Exception exception)
			{
				LogFailure("patch installation", exception);
			}
		}

		private static void PatchPrefix(Harmony harmony, Type type, string method, string prefix, params Type[] arguments)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.DeclaredMethod(type, method, arguments, (Type[])null) ?? throw new MissingMethodException(type.FullName, method);
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ArtisanMasteryCompat), prefix, (Type[])null)
			{
				priority = 800
			}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		internal static bool IsRepairStation(CraftingStation? station)
		{
			if (_station != null)
			{
				return station == _station;
			}
			return false;
		}

		internal static bool CanUseStation(Player player)
		{
			if (_sessionOpen && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)_player && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead() && !((Character)player).IsTeleporting() && !((Character)player).InCutscene() && (Object)(object)_gui != (Object)null && (Object)(object)_gui == (Object)(object)InventoryGui.instance && ((Component)_gui).gameObject.activeInHierarchy && _animator.Invoke(_gui).GetBool("visible") && (Object)(object)_anvil != (Object)null && _anvil.gameObject.activeInHierarchy && (Object)(object)_station != (Object)null && ((Component)_station).gameObject.activeInHierarchy && IsRepairStation(player.GetCurrentCraftingStation()))
			{
				return _station.InUseDistance((Humanoid)(object)player);
			}
			return false;
		}

		private static bool HasField(Type type, string name, Type fieldType)
		{
			FieldInfo fieldInfo = AccessTools.DeclaredField(type, name);
			if (fieldInfo != null && !fieldInfo.IsStatic)
			{
				return fieldInfo.FieldType == fieldType;
			}
			return false;
		}

		private static bool IsGalleonAnvil(Component table)
		{
			if ((Object)(object)table == (Object)null)
			{
				return false;
			}
			Ship componentInParent = table.GetComponentInParent<Ship>(true);
			if ((Object)(object)componentInParent != (Object)null && string.Equals(Utils.GetPrefabName(((Component)componentInParent).gameObject), "ShipGalleon_DO", StringComparison.Ordinal))
			{
				return (Object)(object)((Component)componentInParent).transform.Find("Upgrades/Anvil/x_FurnitureAnvil") == (Object)(object)table.transform;
			}
			return false;
		}

		private static bool InteractPrefix(Component __instance, Humanoid user, bool hold, ref bool __result, ref float ____nextUseTime, float ___CooldownSeconds, string ___RpcSuccessFx)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			if (!IsGalleonAnvil(__instance))
			{
				return true;
			}
			__result = false;
			if (!hold)
			{
				Player val = (Player)(object)((user is Player) ? user : null);
				if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && !((Character)val).IsDead() && !((Character)val).IsTeleporting() && !((Character)val).InCutscene() && __instance.gameObject.activeInHierarchy && !(Time.time < ____nextUseTime) && !(Vector3.Distance(((Component)val).transform.position, __instance.transform.position) >= val.m_maxInteractDistance))
				{
					____nextUseTime = Time.time + Mathf.Max(0.1f, ___CooldownSeconds);
					try
					{
						OpenStation(__instance, val, ___RpcSuccessFx);
					}
					catch (Exception exception)
					{
						LogFailure("repair interaction", exception);
						CloseStation(hideGui: true);
						((Character)val).Message((MessageType)2, RepairRequiresMaterialsLocalization.Localize("$rrm_galleon_failed"), 0, (Sprite)null, false);
					}
					return false;
				}
			}
			return false;
		}

		private static void OpenStation(Component anvil, Player player, string successFx)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			CloseStation(hideGui: true);
			InventoryGui instance = InventoryGui.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				instance.Hide();
				_anvil = anvil;
				_player = player;
				_gui = instance;
				_successFx = successFx;
				_sessionOpen = true;
				GameObject val = new GameObject("RRM_GalleonRepairStation");
				val.SetActive(false);
				val.transform.SetParent(anvil.transform, false);
				try
				{
					_station = val.AddComponent<CraftingStation>();
				}
				catch
				{
					Object.Destroy((Object)(object)val);
					throw;
				}
				_station.m_name = "$rrm_galleon_station";
				_station.m_craftRequireRoof = false;
				_station.m_craftRequireFire = false;
				_station.m_discoverRange = 0f;
				_station.m_rangeBuild = 0f;
				_station.m_useDistance = player.m_maxInteractDistance;
				_station.m_showBasicRecipies = false;
				_station.m_hasCraftTab = false;
				_station.m_canRepair = true;
				val.SetActive(true);
				CancelCrafting(instance);
				CaptureUi(instance);
				_updateRecipeList(instance, EmptyRecipes);
				_setRecipe(instance, -1, arg3: false);
				RepairSelectionState.Reset();
				player.SetCraftingStation(_station);
				instance.Show((Container)null, 3);
			}
		}

		internal static void PlayRepairEffect(Player player)
		{
			if (!CanUseStation(player))
			{
				return;
			}
			try
			{
				_playFx.Invoke(_anvil, new object[1] { _successFx });
			}
			catch (Exception exception)
			{
				LogFailure("repair effect", exception);
			}
		}

		internal static void CloseStation(bool hideGui = false)
		{
			if (!_sessionOpen)
			{
				return;
			}
			_sessionOpen = false;
			InventoryGui gui = _gui;
			if ((Object)(object)_player != (Object)null && IsRepairStation(_player.GetCurrentCraftingStation()))
			{
				_player.SetCraftingStation((CraftingStation)null);
			}
			foreach (var (val, active) in HiddenUi)
			{
				if ((Object)(object)val != (Object)null)
				{
					val.SetActive(active);
				}
			}
			HiddenUi.Clear();
			if ((Object)(object)_craftingGroup != (Object)null)
			{
				_craftingGroup.m_defaultElement = _defaultElement;
				_rightStickSelectable.Invoke(_craftingGroup) = _rightStickElement;
			}
			_craftingGroup = null;
			_defaultElement = null;
			_rightStickElement = null;
			if ((Object)(object)_station != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_station).gameObject);
			}
			_station = null;
			_anvil = null;
			_player = null;
			_gui = null;
			_successFx = string.Empty;
			RepairSelectionState.Reset();
			RepairStripController.Hide();
			if (hideGui && (Object)(object)gui != (Object)null)
			{
				gui.Hide();
			}
		}

		private static void CaptureUi(InventoryGui gui)
		{
			Remember((Component?)(object)gui.m_tabCraft);
			Remember((Component?)(object)gui.m_tabUpgrade);
			Remember((Component?)(object)gui.m_recipeListRoot);
			Remember((Component?)(object)gui.m_recipeListScroll);
			Remember((Component?)(object)gui.m_recipeIcon);
			Remember((Component?)(object)gui.m_recipeName);
			Remember((Component?)(object)gui.m_recipeDecription);
			Remember((Component?)(object)gui.m_variantButton);
			Remember((Component?)(object)gui.m_craftButton);
			Remember((Component?)(object)gui.m_craftCancelButton);
			Remember((Component?)(object)gui.m_craftProgressPanel);
			Remember((Component?)(object)gui.m_qualityPanel);
			Remember((Component?)(object)gui.m_minStationLevelIcon);
			Remember((Component?)(object)gui.m_minStationLevelText);
			Remember((Component?)(object)gui.m_itemCraftType);
			Remember((Component?)(object)gui.m_upgradeItemIcon);
			Remember((Component?)(object)gui.m_upgradeItemDurability);
			Remember((Component?)(object)gui.m_upgradeItemName);
			Remember((Component?)(object)gui.m_upgradeItemQuality);
			GameObject upgradeItemQualityArrow = gui.m_upgradeItemQualityArrow;
			Remember((Component?)(object)((upgradeItemQualityArrow != null) ? upgradeItemQualityArrow.transform : null));
			Remember((Component?)(object)gui.m_upgradeItemNextQuality);
			Remember((Component?)(object)gui.m_upgradeItemIndex);
			Remember((Component?)(object)gui.m_craftingStationIcon);
			Remember((Component?)(object)gui.m_craftingStationLevelRoot);
			GameObject[] recipeRequirementList = gui.m_recipeRequirementList;
			foreach (GameObject val in recipeRequirementList)
			{
				Remember((Component?)(object)(((Object)(object)val != (Object)null) ? val.transform : null));
			}
			if (gui.m_uiGroups.Length > 3 && (Object)(object)gui.m_uiGroups[3] != (Object)null)
			{
				_craftingGroup = gui.m_uiGroups[3];
				_defaultElement = _craftingGroup.m_defaultElement;
				_rightStickElement = _rightStickSelectable.Invoke(_craftingGroup);
				_craftingGroup.m_defaultElement = null;
				_rightStickSelectable.Invoke(_craftingGroup) = null;
			}
		}

		internal static void RefreshRepairFocus(InventoryGui gui)
		{
			if (_sessionOpen && (Object)(object)gui == (Object)(object)_gui && (Object)(object)_craftingGroup != (Object)null)
			{
				_craftingGroup.m_defaultElement = ((((Behaviour)gui.m_repairButton).isActiveAndEnabled && ((Selectable)gui.m_repairButton).interactable) ? ((Component)gui.m_repairButton).gameObject : null);
			}
		}

		private static void Remember(Component? component)
		{
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			GameObject gameObject = component.gameObject;
			foreach (var item in HiddenUi)
			{
				if ((Object)(object)item.Object == (Object)(object)gameObject)
				{
					return;
				}
			}
			HiddenUi.Add((gameObject, gameObject.activeSelf));
		}

		private static void CancelCrafting(InventoryGui gui)
		{
			_craftTimer.Invoke(gui) = -1f;
			_craftRecipe.Invoke(gui) = null;
			gui.OnCraftPointerExit();
		}

		private static void ValidateSessionPrefix()
		{
			if (_sessionOpen && ((Object)(object)_player == (Object)null || !CanUseStation(_player)))
			{
				CloseStation(hideGui: true);
			}
		}

		private static bool RepairOnlyPanelPrefix(InventoryGui __instance)
		{
			if (!_sessionOpen || (Object)(object)__instance != (Object)(object)_gui)
			{
				return true;
			}
			CancelCrafting(__instance);
			foreach (var item in HiddenUi)
			{
				if ((Object)(object)item.Object != (Object)null && item.Object.activeSelf)
				{
					item.Object.SetActive(false);
				}
			}
			__instance.m_craftingStationName.text = RepairRequiresMaterialsLocalization.Localize("$rrm_galleon_station");
			return false;
		}

		private static bool BlockCraftingPrefix(InventoryGui __instance)
		{
			if (!_sessionOpen || (Object)(object)__instance != (Object)(object)_gui)
			{
				return true;
			}
			CancelCrafting(__instance);
			return false;
		}

		private static bool GamepadSelectionPrefix(InventoryGui __instance)
		{
			if (!_sessionOpen || (Object)(object)__instance != (Object)(object)_gui)
			{
				return true;
			}
			if ((Object)(object)_player != (Object)null && CanUseStation(_player))
			{
				if (ZInput.GetButtonDown("JoyLStickDown") || ZInput.GetButtonDown("JoyDPadDown"))
				{
					RepairStripController.ScrollSelection(__instance, 1);
				}
				else if (ZInput.GetButtonDown("JoyLStickUp") || ZInput.GetButtonDown("JoyDPadUp"))
				{
					RepairStripController.ScrollSelection(__instance, -1);
				}
			}
			return false;
		}

		private static void ChangeStationPrefix(Player __instance, CraftingStation station)
		{
			if (_sessionOpen && (Object)(object)__instance == (Object)(object)_player && !IsRepairStation(station))
			{
				CloseStation();
			}
		}

		private static bool AddKnownStationPrefix(CraftingStation station)
		{
			return !IsRepairStation(station);
		}

		private static void HoverPostfix(Component __instance, ref string __result)
		{
			if (IsGalleonAnvil(__instance))
			{
				Localization instance = Localization.instance;
				string text = ((instance != null) ? instance.Localize("$KEY_Use") : null) ?? "E";
				if (text == "$KEY_Use")
				{
					text = "E";
				}
				__result = "[<color=yellow><b>" + text + "</b></color>] " + RepairRequiresMaterialsLocalization.Localize("$rrm_galleon_action");
			}
		}

		private static void LogFailure(string operation, Exception exception)
		{
			if (!_failureLogged)
			{
				_failureLogged = true;
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Artisan Mastery " + operation + " failed: " + exception.GetType().Name + ": " + exception.Message));
			}
		}
	}
	internal static class ArtisanStorageCompat
	{
		private const string PendingKey = "rrm_galleon_recovery_pending";

		private const string BackupKey = "rrm_galleon_recovery_original";

		private static MethodInfo _cleanup = null;

		private static MethodInfo _follow = null;

		private static MethodInfo _backlink = null;

		private static MethodInfo _instantiate = null;

		private static MethodInfo _request = null;

		private static MethodInfo _findStorage = null;

		private static FieldInfo _slot = null;

		private static FieldInfo _link = null;

		private static FieldInfo _prefab = null;

		private static FieldInfo _visual = null;

		private static Action<Container> _save = null;

		private static readonly HashSet<ZDOID> Recovering = new HashSet<ZDOID>();

		private static readonly HashSet<ZDOID> OwnershipWarnings = new HashSet<ZDOID>();

		internal static void Initialize(Harmony harmony)
		{
			if (!Chainloader.PluginInfos.TryGetValue("Dreanegade.Artisan_Mastery", out var value) || (Object)(object)value.Instance == (Object)null)
			{
				return;
			}
			List<(MethodInfo Target, MethodInfo Patch)> installed = new List<(MethodInfo, MethodInfo)>();
			try
			{
				ResolveContract(((object)value.Instance).GetType().Assembly);
				_save = AccessTools.MethodDelegate<Action<Container>>(AccessTools.DeclaredMethod(typeof(Container), "Save", Type.EmptyTypes, (Type[])null), (object)null, true);
				Install(_backlink, "BacklinkPrefix");
				Install(_instantiate, "InstantiatePrefix");
				Install(_follow, "FollowPrefix");
				Install(_request, "RequestPrefix");
				Install(_cleanup, "CleanupPrefix");
				RepairRequiresMaterialsPlugin.Log.LogInfo((object)"Artisan Mastery hidden galleon storage protection enabled.");
			}
			catch (Exception ex)
			{
				foreach (var item in installed)
				{
					harmony.Unpatch((MethodBase)item.Target, item.Patch);
				}
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Artisan storage protection was not enabled: " + ex.GetBaseException().Message));
			}
			void Install(MethodInfo target, string name)
			{
				//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)
				//IL_0046: Expected O, but got Unknown
				MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ArtisanStorageCompat), name, (Type[])null, (Type[])null);
				installed.Add((target, methodInfo));
				harmony.Patch((MethodBase)target, new HarmonyMethod(methodInfo)
				{
					priority = 800
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		internal static void ResolveContract(Assembly assembly)
		{
			Type type = assembly.GetType("Artisan_Mastery.ShipHiddenContainerRoot", throwOnError: true);
			Type type2 = assembly.GetType("Artisan_Mastery.ShipHiddenContainerSystem+StorageDef", throwOnError: true);
			if (!typeof(Component).IsAssignableFrom(type) || !type2.IsValueType)
			{
				throw new NotSupportedException("Unrecognized hidden storage types");
			}
			_cleanup = Method(type, "CleanupStorage", typeof(void), typeof(ZDO), typeof(string), type2);
			_follow = Method(type, "UpdateLinkedStoragesPassiveFollow", typeof(void));
			_backlink = Method(type, "EnsureStorageBackLink", typeof(void), type2, typeof(ZNetView), typeof(string));
			_instantiate = Method(type, "InstantiateExistingStorage", typeof(GameObject), type2, typeof(ZDO), typeof(Transform), typeof(bool));
			_request = Method(type, "RPC_RequestOpenHiddenContainer", typeof(void), typeof(long), typeof(int), typeof(long));
			_findStorage = Method(type, "FindStorageZDOByStableLink", typeof(ZDO), typeof(string), typeof(int));
			_slot = Field(type2, "Slot", typeof(int));
			_link = Field(type2, "LinkKey", typeof(string));
			_prefab = Field(type2, "HiddenPrefabName", typeof(string));
			_visual = Field(type2, "VisualPath", typeof(string));
			Field(type, "_shipNView", typeof(ZNetView));
			Field(type, "_nextPassiveFollowTime", typeof(float));
			Field(type, "_defs", type2.MakeArrayType());
			Method(typeof(Container), "Save", typeof(void));
		}

		private static MethodInfo Method(Type type, string name, Type result, params Type[] args)
		{
			MethodInfo methodInfo = AccessTools.DeclaredMethod(type, name, args, (Type[])null);
			if (methodInfo == null || methodInfo.IsStatic || methodInfo.ReturnType != result)
			{
				throw new MissingMethodException(type.FullName, name);
			}
			return methodInfo;
		}

		private static FieldInfo Field(Type type, string name, Type valueType)
		{
			FieldInfo fieldInfo = AccessTools.DeclaredField(type, name);
			if (fieldInfo == null || fieldInfo.IsStatic || fieldInfo.FieldType != valueType)
			{
				throw new MissingFieldException(type.FullName, name);
			}
			return fieldInfo;
		}

		private static bool IsHiddenStorage(ZDO zdo)
		{
			int prefab = zdo.GetPrefab();
			if (prefab != StringExtensionMethods.GetStableHashCode("ShipContainerHiddenLarge_DO") && prefab != StringExtensionMethods.GetStableHashCode("ShipContainerHiddenMedium_DO"))
			{
				return prefab == StringExtensionMethods.GetStableHashCode("ShipContainerHiddenSmall_DO");
			}
			return true;
		}

		private static Container? LoadedContainer(ZDO zdo)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = ZNetScene.instance.FindInstance(zdo.m_uid);
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return val.GetComponent<Container>();
		}

		private static bool IsBusy(ZDO zdo)
		{
			Container val = LoadedContainer(zdo);
			if (zdo.GetInt(ZDOVars.s_inUse, 0) == 0)
			{
				if ((Object)(object)val != (Object)null)
				{
					return val.IsInUse();
				}
				return false;
			}
			return true;
		}

		private static bool ProtectStorage(ZDO zdo)
		{
			if (IsHiddenStorage(zdo))
			{
				if (!zdo.GetBool("rrm_galleon_recovery_pending", false))
				{
					if (!zdo.IsOwner())
					{
						return IsBusy(zdo);
					}
					return false;
				}
				return true;
			}
			return false;
		}

		private static bool BacklinkPrefix(ZNetView storageNView)
		{
			if ((Object)(object)storageNView == (Object)null || !storageNView.IsValid())
			{
				return true;
			}
			return !ProtectStorage(storageNView.GetZDO());
		}

		private static void InstantiatePrefix(ZDO storageZdo, ref bool writeZdoPosition)
		{
			if (storageZdo != null && ProtectStorage(storageZdo))
			{
				writeZdoPosition = false;
			}
		}

		private static bool FollowPrefix(Component __instance, ZNetView ____shipNView, ref float ____nextPassiveFollowTime, Array ____defs)
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: 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 ((Object)(object)____shipNView == (Object)null || !____shipNView.IsValid() || !____shipNView.IsOwner() || Time.time < ____nextPassiveFollowTime)
			{
				return false;
			}
			____nextPassiveFollowTime = Time.time + 1f;
			ZDO zDO = ____shipNView.GetZDO();
			foreach (object ____def in ____defs)
			{
				ZDO zDO2 = ZDOMan.instance.GetZDO(zDO.GetZDOID((string)_link.GetValue(____def)));
				if (zDO2 == null || !IsHiddenStorage(zDO2) || !HasExpectedLink(zDO2, zDO, zDO.GetString("galleon_container_ship_stable_id", ""), ____def))
				{
					continue;
				}
				Transform val = __instance.transform.Find((string)_visual.GetValue(____def));
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Vector3 val2 = val.position + Vector3.up * 0.15f;
				if (Vector3.Distance(zDO2.GetPosition(), val2) < 6f)
				{
					continue;
				}
				if (ProtectStorage(zDO2))
				{
					if (OwnershipWarnings.Add(zDO2.m_uid))
					{
						Warn(zDO2, "skipped passive following: in use by another peer or recovery pending");
					}
					continue;
				}
				if (!zDO2.IsOwner())
				{
					zDO2.SetOwner(ZDOMan.GetSessionID());
				}
				zDO2.SetPosition(val2);
			}
			return false;
		}

		private static bool RequestPrefix(Component __instance, long requesterPeerId, int slot, ZNetView ____shipNView, Array ____defs)
		{
			//IL_0062: 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)
			if ((Object)(object)____shipNView == (Object)null || !____shipNView.IsValid() || !____shipNView.IsOwner())
			{
				return true;
			}
			ZDO zDO = ____shipNView.GetZDO();
			foreach (object ____def in ____defs)
			{
				if ((int)_slot.GetValue(____def) == slot)
				{
					ZDO val = ZDOMan.instance.GetZDO(zDO.GetZDOID((string)_link.GetValue(____def)));
					if (val == null || !val.GetBool("rrm_galleon_recovery_pending", false))
					{
						string text = zDO.GetString("galleon_container_ship_stable_id", "");
						val = (ZDO)(string.IsNullOrEmpty(text) ? null : /*isinst with value type is only supported in some contexts*/);
					}
					if (val == null || !val.GetBool("rrm_galleon_recovery_pending", false))
					{
						return true;
					}
					Warn(val, "open refused: interrupted recovery requires administrator inspection");
					____shipNView.InvokeRPC(requesterPeerId, "HH_OpenHiddenContainerResponse", new object[3]
					{
						slot,
						ZDOID.None,
						false
					});
					return false;
				}
			}
			return true;
		}

		private static bool CleanupPrefix(Component __instance, ZDO shipZdo, string stableShipId, object def)
		{
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			int num = (int)_slot.GetValue(def);
			string text = (string)_link.GetValue(def);
			try
			{
				if (!shipZdo.IsOwner())
				{
					return false;
				}
				HashSet<ZDOID> hashSet = new HashSet<ZDOID>();
				ZDOID zDOID = shipZdo.GetZDOID(text);
				if (!((ZDOID)(ref zDOID)).IsNone())
				{
					hashSet.Add(zDOID);
				}
				if (!string.IsNullOrEmpty(stableShipId))
				{
					object? obj = _findStorage.Invoke(__instance, new object[2] { stableShipId, num });
					ZDO val = (ZDO)((obj is ZDO) ? obj : null);
					if (val != null)
					{
						hashSet.Add(val.m_uid);
					}
				}
				bool flag = true;
				foreach (ZDOID item in hashSet)
				{
					ZDO zDO = ZDOMan.instance.GetZDO(item);
					if (zDO == null)
					{
						flag = false;
						RepairRequiresMaterialsPlugin.Log.LogWarning((object)$"Galleon slot={num}, storage={item}: missing storage; link retained.");
					}
					else if (!HasExpectedLink(zDO, shipZdo, stableShipId, def))
					{
						Warn(zDO, "unexpected prefab/backlink; data preserved");
						flag = false;
					}
					else
					{
						flag &= RecoverStorage(zDO, __instance.transform.position);
					}
				}
				if (flag)
				{
					shipZdo.RemoveZDOID(text);
				}
			}
			catch (Exception ex)
			{
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)$"Galleon slot={num}, ship={shipZdo.m_uid}: cleanup stopped, link retained: {ex.GetBaseException().Message}");
			}
			return false;
		}

		private static bool HasExpectedLink(ZDO storage, ZDO ship, string stableId, object def)
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			if (IsHiddenStorage(storage) && storage.GetPrefab() == StringExtensionMethods.GetStableHashCode((string)_prefab.GetValue(def)) && !string.IsNullOrEmpty(stableId) && storage.GetString("galleon_parent_ship_stable_id", "") == stableId && storage.GetInt("galleon_container_slot", -1) == (int)_slot.GetValue(def))
			{
				return storage.GetZDOID("galleon_parent_ship") == ship.m_uid;
			}
			return false;
		}

		private static bool RecoverStorage(ZDO source, Vector3 position)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0587: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Expected O, but got Unknown
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Expected O, but got Unknown
			//IL_043e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0445: Expected O, but got Unknown
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: 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_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b4: Unknown result type (might be due to invalid IL or missing references)
			if (!Recovering.Add(source.m_uid))
			{
				return false;
			}
			List<Container> list = new List<Container>();
			byte[] array = null;
			byte[] array2 = null;
			bool flag = false;
			bool flag2 = false;
			try
			{
				if (source.GetBool("rrm_galleon_recovery_pending", false))
				{
					throw new InvalidOperationException("interrupted recovery; not replaying it");
				}
				if (!source.IsOwner() || IsBusy(source))
				{
					throw new InvalidOperationException("storage is in use or owned by another peer");
				}
				Container val = LoadedContainer(source);
				Container component = ZNetScene.instance.GetPrefab(source.GetPrefab()).GetComponent<Container>();
				if ((Object)(object)component == (Object)null)
				{
					throw new InvalidOperationException("hidden container prefab unavailable");
				}
				array = source.GetByteArray(ZDOVars.s_items, (byte[])null)?.Clone() as byte[];
				Inventory val2 = new Inventory(component.m_name, component.m_bkg, component.m_width, component.m_height);
				if (array != null)
				{
					val2.Load(new ZPackage(array));
				}
				array2 = Serialize(val2);
				if (array != null && !IsSupportedInventoryRoundTrip(array, array2))
				{
					throw new InvalidOperationException("saved inventory lost data or used an unsupported format transition");
				}
				if ((Object)(object)val != (Object)null && val.GetInventory().NrOfItems() > 0 && !BytesEqual(array2, Serialize(val.GetInventory())))
				{
					throw new InvalidOperationException("live inventory differs from saved inventory");
				}
				if (val2.NrOfItems() == 0)
				{
					ZDOMan.instance.DestroyZDO(source);
					return true;
				}
				GameObject prefab = ZNetScene.instance.GetPrefab("CargoCrate_DO");
				Container val3 = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Container>() : null);
				if ((Object)(object)prefab == (Object)null || (Object)(object)val3 == (Object)null || val3.m_width <= 0 || val3.m_height <= 0)
				{
					throw new InvalidOperationException("CargoCrate_DO container unavailable");
				}
				List<byte[]> list2 = StageCrates(val2, val3);
				source.Set("rrm_galleon_recovery_original", array);
				if (!BytesEqual(array, source.GetByteArray("rrm_galleon_recovery_original", (byte[])null)))
				{
					throw new InvalidOperationException("source backup readback mismatch");
				}
				source.Set("rrm_galleon_recovery_pending", true);
				flag = true;
				foreach (byte[] item in list2)
				{
					Container component2 = Object.Instantiate<GameObject>(prefab, position + Vector3.up * 0.5f, Quaternion.identity).GetComponent<Container>();
					if ((Object)(object)component2 == (Object)null)
					{
						throw new InvalidOperationException("spawned cargo crate has no container");
					}
					list.Add(component2);
					ZNetView component3 = ((Component)component2).GetComponent<ZNetView>();
					if ((Object)(object)component3 == (Object)null || !component3.IsValid() || !component3.IsOwner() || component2.IsInUse() || component2.GetInventory() == null || component2.GetInventory().NrOfItems() != 0)
					{
						throw new InvalidOperationException("cargo crate is not empty and locally owned");
					}
					WriteInventory(component2, item);
				}
				if (!source.IsOwner() || IsBusy(source) || !BytesEqual(array, source.GetByteArray(ZDOVars.s_items, (byte[])null)))
				{
					throw new InvalidOperationException("source changed during recovery");
				}
				for (int i = 0; i < list.Count; i++)
				{
					Container obj = list[i];
					ZNetView component4 = ((Component)obj).GetComponent<ZNetView>();
					RequireWritable(obj);
					if (!BytesEqual(list2[i], component4.GetZDO().GetByteArray(ZDOVars.s_items, (byte[])null)))
					{
						throw new InvalidOperationException("cargo contents changed during recovery");
					}
				}
				val2.GetAllItems().Clear();
				byte[] array3 = Serialize(val2);
				if ((Object)(object)val != (Object)null)
				{
					WriteInventory(val, array3);
				}
				else
				{
					source.Set(ZDOVars.s_items, array3);
				}
				if (!BytesEqual(source.GetByteArray(ZDOVars.s_items, (byte[])null), array3))
				{
					throw new InvalidOperationException("source clear could not be verified");
				}
				flag2 = true;
				ZDOMan.instance.DestroyZDO(source);
				RepairRequiresMaterialsPlugin.Log.LogInfo((object)$"Galleon storage={source.m_uid}: recovered into {list.Count} cargo crate(s).");
				return true;
			}
			catch (Exception ex)
			{
				if (flag && !flag2)
				{
					try
					{
						foreach (Container item2 in list)
						{
							ZNetView component5 = ((Component)item2).GetComponent<ZNetView>();
							if ((Object)(object)component5 == (Object)null || !component5.IsValid() || !component5.IsOwner() || item2.IsInUse())
							{
								throw new InvalidOperationException("rollback cargo is unavailable/in use");
							}
							Inventory inventory = new Inventory("", (Sprite)null, 1, 1);
							WriteInventory(item2, Serialize(inventory));
						}
						if (!source.IsOwner() || IsBusy(source))
						{
							throw new InvalidOperationException("rollback source ownership changed");
						}
						if (array2 == null)
						{
							throw new InvalidOperationException("rollback snapshot unavailable");
						}
						Container val4 = LoadedContainer(source);
						if ((Object)(object)val4 != (Object)null)
						{
							WriteInventory(val4, array2);
						}
						else
						{
							source.Set(ZDOVars.s_items, array2);
						}
						if (!BytesEqual(array2, source.GetByteArray(ZDOVars.s_items, (byte[])null)))
						{
							throw new InvalidOperationException("rollback source verification failed");
						}
						foreach (Container item3 in list)
						{
							((Component)item3).GetComponent<ZNetView>().Destroy();
						}
						source.Set("rrm_galleon_recovery_pending", false);
						source.RemoveByteArray(StringExtensionMethods.GetStableHashCode("rrm_galleon_recovery_original"));
					}
					catch (Exception ex2)
					{
						Warn(source, "recovery quarantined: " + ex2.GetBaseException().Message);
					}
				}
				Warn(source, (flag2 ? "contents recovered; source deletion failed" : "data preserved/quarantined") + ": " + ex.GetBaseException().Message);
				return flag2;
			}
			finally
			{
				Recovering.Remove(source.m_uid);
			}
		}

		private static List<byte[]> StageCrates(Inventory source, Container template)
		{
			//IL_009c: 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_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Expected O, but got Unknown
			int num = checked(template.m_width * template.m_height);
			List<byte[]> list = new List<byte[]>();
			Inventory val = null;
			int num2 = 0;
			foreach (ItemData allItem in source.GetAllItems())
			{
				if (num2 % num == 0)
				{
					if (val != null)
					{
						list.Add(Serialize(val));
					}
					if (list.Count >= 64)
					{
						throw new InvalidOperationException("too many cargo crates required");
					}
					val = new Inventory(template.m_name, template.m_bkg, template.m_width, template.m_height);
				}
				ItemData val2 = allItem.Clone();
				int num3 = num2++ % num;
				val2.m_gridPos = new Vector2i(num3 % template.m_width, num3 / template.m_width);
				val.GetAllItems().Add(val2);
			}
			if (val != null)
			{
				list.Add(Serialize(val));
			}
			return list;
		}

		private static void WriteInventory(Container container, byte[] payload)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			RequireWritable(container);
			Inventory inventory = container.GetInventory();
			Action onChanged = inventory.m_onChanged;
			inventory.m_onChanged = null;
			try
			{
				inventory.Load(new ZPackage(payload));
				if (!BytesEqual(payload, Serialize(inventory)))
				{
					throw new InvalidOperationException("cargo/source inventory lost data on load");
				}
				RequireWritable(container);
				_save(container);
				if (!BytesEqual(payload, ((Component)container).GetComponent<ZNetView>().GetZDO().GetByteArray(ZDOVars.s_items, (byte[])null)))
				{
					throw new InvalidOperationException("inventory save readback mismatch");
				}
			}
			finally
			{
				inventory.m_onChanged = onChanged;
			}
		}

		private static void RequireWritable(Container container)
		{
			ZNetView component = ((Component)container).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner() || IsBusy(component.GetZDO()))
			{
				throw new InvalidOperationException("container is unavailable, in use, or owned by another peer");
			}
		}

		private static byte[] Serialize(Inventory inventory)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			inventory.Save(val);
			return val.GetArray();
		}

		private static bool IsSupportedInventoryRoundTrip(byte[] original, byte[] serialized)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			if (BytesEqual(original, serialized))
			{
				return true;
			}
			if (original.Length != serialized.Length || original.Length < 6)
			{
				return false;
			}
			try
			{
				ZPackage val = new ZPackage(original);
				if (val.ReadInt() != 109 || new ZPackage(serialized).ReadInt() != 110)
				{
					return false;
				}
				byte[] array = (byte[])original.Clone();
				Array.Copy(serialized, array, 4);
				int num = val.ReadUShort();
				for (int i = 0; i < num; i++)
				{
					ItemData.Load(val, (Item)109);
					int num2 = val.GetPos() - 1;
					if ((original[num2] & -2) != 0)
					{
						return false;
					}
					array[num2] = 0;
				}
				return val.GetPos() == val.Size() && BytesEqual(array, serialized);
			}
			catch (Exception ex) when (ex is IOException || ex is ArgumentException || ex is OverflowException)
			{
				return false;
			}
		}

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

		private static void Warn(ZDO storage, string message)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			RepairRequiresMaterialsPlugin.Log.LogWarning((object)string.Format("Galleon slot={0}, storage={1}, owner={2}: {3}", storage.GetInt("galleon_container_slot", -1), storage.m_uid, storage.GetOwner(), message));
		}

		internal static void Shutdown()
		{
			Recovering.Clear();
			OwnershipWarnings.Clear();
		}
	}
	internal static class AzuCraftyBoxesCompat
	{
		private sealed class ContainerConsumption
		{
			internal object Container { get; }

			internal MethodInfo GetPrefabNameMethod { get; }

			internal MethodInfo ItemCountMethod { get; }

			internal MethodInfo ProcessInventoryMethod { get; }

			internal int Amount { get; }

			internal ContainerConsumption(object container, MethodInfo getPrefabNameMethod, MethodInfo itemCountMethod, MethodInfo processInventoryMethod, int amount)
			{
				Container = container;
				GetPrefabNameMethod = getPrefabNameMethod;
				ItemCountMethod = itemCountMethod;
				ProcessInventoryMethod = processInventoryMethod;
				Amount = amount;
			}
		}

		private sealed class RequirementConsumption
		{
			internal string ItemName { get; }

			internal string PrefabName { get; }

			internal int InventoryAmount { get; }

			internal List<ContainerConsumption> Containers { get; } = new List<ContainerConsumption>();

			internal RequirementConsumption(string itemName, string prefabName, int inventoryAmount)
			{
				ItemName = itemName;
				PrefabName = prefabName;
				InventoryAmount = inventoryAmount;
			}
		}

		private const string DeferredKgDrawerTypeName = "AzuCraftyBoxes.IContainers.kgDrawer";

		internal const string PluginGuid = "Azumatt.AzuCraftyBoxes";

		private static bool _initialized;

		private static bool _available;

		private static bool _failureLogged;

		private static FieldInfo? _rangeField;

		private static MethodInfo? _queryFrameGetMethod;

		private static MethodInfo? _shouldPreventMethod;

		private static MethodInfo? _canItemBePulledMethod;

		private static MethodInfo? _checkAndDecrementMethod;

		internal static bool ShouldUseNearbyContainers()
		{
			try
			{
				return EnsureInitialized() && !ShouldPrevent();
			}
			catch (Exception exception)
			{
				DisableAfterFailure("availability check", exception);
				return false;
			}
		}

		internal static bool TryCountAvailable(Player player, RepairMaterialCost cost, int currentAmount, out int totalAmount)
		{
			totalAmount = currentAmount;
			if (!_available)
			{
				return false;
			}
			try
			{
				Requirement sourceRequirement = cost.SourceRequirement;
				if ((Object)(object)sourceRequirement?.m_resItem == (Object)null)
				{
					return false;
				}
				IList nearbyContainers = GetNearbyContainers(player);
				if (nearbyContainers == null)
				{
					return false;
				}
				string name = sourceRequirement.m_resItem.m_itemData.m_shared.m_name;
				string resourcePrefabName = cost.ResourcePrefabName;
				foreach (object item in nearbyContainers)
				{
					if (item != null && TryGetContainerMethods(item, out MethodInfo getPrefabNameMethod, out MethodInfo itemCountMethod, out MethodInfo _) && CanPull(item, getPrefabNameMethod, resourcePrefabName))
					{
						totalAmount += GetPullableAmount(item, itemCountMethod, name);
					}
				}
				return true;
			}
			catch (Exception exception)
			{
				DisableAfterFailure("container count", exception);
				totalAmount = currentAmount;
				return false;
			}
		}

		internal static bool TryConsume(Player player, IReadOnlyList<RepairMaterialCost> costs, out bool shouldCompleteRepair)
		{
			shouldCompleteRepair = false;
			if (!ShouldUseNearbyContainers())
			{
				return false;
			}
			try
			{
				IList nearbyContainers = GetNearbyContainers(player);
				if (nearbyContainers == null)
				{
					return false;
				}
				Inventory inventory = ((Humanoid)player).GetInventory();
				List<RequirementConsumption> list = new List<RequirementConsumption>(costs.Count);
				foreach (RepairMaterialCost cost in costs)
				{
					Requirement sourceRequirement = cost.SourceRequirement;
					if ((Object)(object)sourceRequirement?.m_resItem == (Object)null)
					{
						return false;
					}
					int num = Math.Max(0, cost.RequiredAmount);
					if (num <= 0)
					{
						continue;
					}
					string name = sourceRequirement.m_resItem.m_itemData.m_shared.m_name;
					string resourcePrefabName = cost.ResourcePrefabName;
					int num2 = Math.Min(num, inventory.CountItems(name, -1, true));
					int num3 = num - num2;
					RequirementConsumption requirementConsumption = new RequirementConsumption(name, resourcePrefabName, num2);
					foreach (object item in nearbyContainers)
					{
						if (num3 <= 0)
						{
							break;
						}
						if (item != null && TryGetContainerMethods(item, out MethodInfo getPrefabNameMethod, out MethodInfo itemCountMethod, out MethodInfo processInventoryMethod) && CanPull(item, getPrefabNameMethod, resourcePrefabName))
						{
							int num4 = Math.Min(num3, GetPullableAmount(item, itemCountMethod, name));
							if (num4 > 0)
							{
								requirementConsumption.Containers.Add(new ContainerConsumption(item, getPrefabNameMethod, itemCountMethod, processInventoryMethod, num4));
								num3 -= num4;
							}
						}
					}
					if (num3 > 0)
					{
						return false;
					}
					list.Add(requirementConsumption);
				}
				if (!ValidateConsumptionPlan(inventory, list))
				{
					return false;
				}
				return ExecuteConsumptionPlan(inventory, list, out shouldCompleteRepair);
			}
			catch (Exception exception)
			{
				DisableAfterFailure("material consumption", exception, shouldCompleteRepair);
				return false;
			}
		}

		private static bool ValidateConsumptionPlan(Inventory inventory, IEnumerable<RequirementConsumption> plan)
		{
			foreach (RequirementConsumption item in plan)
			{
				if (inventory.CountItems(item.ItemName, -1, true) < item.InventoryAmount)
				{
					return false;
				}
				foreach (ContainerConsumption container in item.Containers)
				{
					if (!CanPull(container.Container, container.GetPrefabNameMethod, item.PrefabName) || GetPullableAmount(container.Container, container.ItemCountMethod, item.ItemName) < container.Amount)
					{
						return false;
					}
				}
			}
			return true;
		}

		private static bool ExecuteConsumptionPlan(Inventory inventory, IEnumerable<RequirementConsumption> plan, out bool shouldCompleteRepair)
		{
			shouldCompleteRepair = false;
			foreach (RequirementConsumption item in plan)
			{
				if (item.InventoryAmount > 0)
				{
					int num = inventory.CountItems(item.ItemName, -1, true);
					int num2;
					try
					{
						inventory.RemoveItem(item.ItemName, item.InventoryAmount, -1, true);
						num2 = num - inventory.CountItems(item.ItemName, -1, true);
					}
					catch (Exception exception)
					{
						try
						{
							shouldCompleteRepair |= num - inventory.CountItems(item.ItemName, -1, true) > 0;
						}
						catch
						{
							shouldCompleteRepair = true;
						}
						DisableAfterFailure("player inventory material removal", exception, shouldCompleteRepair);
						return false;
					}
					shouldCompleteRepair |= num2 > 0;
					if (num2 != item.InventoryAmount)
					{
						return DisableAfterConsumptionMismatch("player inventory changed during material removal", shouldCompleteRepair);
					}
				}
				foreach (ContainerConsumption container in item.Containers)
				{
					int rawContainerAmount = GetRawContainerAmount(container.Container, container.ItemCountMethod, item.ItemName);
					int num3;
					int rawContainerAmount2;
					try
					{
						num3 = Convert.ToInt32(container.ProcessInventoryMethod.Invoke(container.Container, new object[3] { item.ItemName, 0, container.Amount }) ?? ((object)0));
						rawContainerAmount2 = GetRawContainerAmount(container.Container, container.ItemCountMethod, item.ItemName);
					}
					catch (Exception exception2)
					{
						try
						{
							shouldCompleteRepair |= rawContainerAmount - GetRawContainerAmount(container.Container, container.ItemCountMethod, item.ItemName) > 0;
						}
						catch
						{
							shouldCompleteRepair = true;
						}
						DisableAfterFailure("container material removal", exception2, shouldCompleteRepair);
						return false;
					}
					int num4 = rawContainerAmount - rawContainerAmount2;
					shouldCompleteRepair |= num4 > 0;
					if (num3 != container.Amount || num4 != container.Amount)
					{
						return DisableAfterConsumptionMismatch("container '" + GetContainerName(container) + "' did not remove the planned amount of " + item.ItemName, shouldCompleteRepair);
					}
				}
			}
			return true;
		}

		private static bool EnsureInitialized()
		{
			if (_initialized)
			{
				return _available;
			}
			_initialized = true;
			if (!Chainloader.PluginInfos.TryGetValue("Azumatt.AzuCraftyBoxes", out var value) || (Object)(object)value.Instance == (Object)null)
			{
				return false;
			}
			Assembly assembly = ((object)value.Instance).GetType().Assembly;
			Type type = assembly.GetType("AzuCraftyBoxes.AzuCraftyBoxesPlugin");
			Type type2 = assembly.GetType("AzuCraftyBoxes.Util.Functions.Boxes");
			Type type3 = assembly.GetType("AzuCraftyBoxes.Util.Functions.Boxes+QueryFrame");
			Type type4 = assembly.GetType("AzuCraftyBoxes.Util.Functions.MiscFunctions");
			_rangeField = type?.GetField("mRange", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			_queryFrameGetMethod = type3?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo method) => method.Name == "Get" && method.IsGenericMethodDefinition);
			_shouldPreventMethod = type4?.GetMethod("ShouldPrevent", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			_canItemBePulledMethod = type2?.GetMethod("CanItemBePulled", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			_checkAndDecrementMethod = type2?.GetMethod("CheckAndDecrement", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			_available = type != null && type2 != null && type3 != null && type4 != null && _rangeField != null && _queryFrameGetMethod != null && _shouldPreventMethod != null && _canItemBePulledMethod != null && _checkAndDecrementMethod != null;
			if (!_available && !_failureLogged)
			{
				_failureLogged = true;
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)"AzuCraftyBoxes was found, but its compatible container API was not available. Nearby-container repair support is disabled.");
			}
			return _available;
		}

		private static bool TryGetContainerMethods(object container, out MethodInfo getPrefabNameMethod, out MethodInfo itemCountMethod, out MethodInfo processInventoryMethod)
		{
			Type type = container.GetType();
			getPrefabNameMethod = type.GetMethod("GetPrefabName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			itemCountMethod = type.GetMethod("ItemCount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			processInventoryMethod = type.GetMethod("ProcessContainerInventory", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (getPrefabNameMethod != null && itemCountMethod != null && processInventoryMethod != null)
			{
				return SupportsVerifiedRemoval(container);
			}
			return false;
		}

		private static bool CanPull(object container, MethodInfo getPrefabNameMethod, string prefabName)
		{
			string text = Convert.ToString(getPrefabNameMethod.Invoke(container, null)) ?? string.Empty;
			return Convert.ToBoolean(_canItemBePulledMethod.Invoke(null, new object[3]
			{
				text,
				prefabName,
				string.Empty
			}) ?? ((object)false));
		}

		private static bool SupportsVerifiedRemoval(object container)
		{
			return !string.Equals(container.GetType().FullName, "AzuCraftyBoxes.IContainers.kgDrawer", StringComparison.Ordinal);
		}

		private static int GetPullableAmount(object container, MethodInfo itemCountMethod, string itemName)
		{
			int rawContainerAmount = GetRawContainerAmount(container, itemCountMethod, itemName);
			return Math.Max(0, Convert.ToInt32(_checkAndDecrementMethod.Invoke(null, new object[1] { rawContainerAmount }) ?? ((object)0)));
		}

		private static int GetRawContainerAmount(object container, MethodInfo itemCountMethod, string itemName)
		{
			return Math.Max(0, Convert.ToInt32(itemCountMethod.Invoke(container, new object[1] { itemName }) ?? ((object)0)));
		}

		private static string GetContainerName(ContainerConsumption container)
		{
			return Convert.ToString(container.GetPrefabNameMethod.Invoke(container.Container, null)) ?? "unknown";
		}

		private static bool ShouldPrevent()
		{
			return Convert.ToBoolean(_shouldPreventMethod.Invoke(null, null) ?? ((object)true));
		}

		private static IList? GetNearbyContainers(Player player)
		{
			object value = _rangeField.GetValue(null);
			float num = Convert.ToSingle((value?.GetType().GetProperty("Value"))?.GetValue(value) ?? ((object)0f));
			return _queryFrameGetMethod.MakeGenericMethod(typeof(Player)).Invoke(null, new object[2] { player, num }) as IList;
		}

		private static bool DisableAfterConsumptionMismatch(string reason, bool repairWillComplete)
		{
			_available = false;
			if (!_failureLogged)
			{
				_failureLogged = true;
				string text = (repairWillComplete ? "The selected item will still be repaired because materials were already consumed." : "The repair was cancelled because no material removal was confirmed.");
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)("AzuCraftyBoxes consumption verification failed; nearby-container repair support is disabled for this session. " + text + " Reason: " + reason));
			}
			return false;
		}

		private static void DisableAfterFailure(string operation, Exception exception, bool repairWillComplete = false)
		{
			_available = false;
			if (!_failureLogged)
			{
				_failureLogged = true;
				Exception ex = ((exception is TargetInvocationException && exception.InnerException != null) ? exception.InnerException : exception);
				string text = (repairWillComplete ? " The selected item will still be repaired because consumption may already have started." : string.Empty);
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)("AzuCraftyBoxes " + operation + " failed; nearby-container repair support is disabled for this session: " + ex.Message + "." + text));
			}
		}
	}
	[HarmonyPatch]
	internal static class PlayerCraftingEquipSpeedPatch
	{
		private readonly struct QueueState
		{
			internal readonly MinorActionData? ExistingAction;

			internal readonly ActionType ActionType;

			internal QueueState(MinorActionData? existingAction, ActionType actionType)
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				ExistingAction = existingAction;
				ActionType = actionType;
			}
		}

		private static bool _missingTargetWarningLogged;

		private static IEnumerable<MethodBase> TargetMethods()
		{
			MethodBase methodBase = AccessTools.Method(typeof(Player), "QueueEquipAction", new Type[1] { typeof(ItemData) }, (Type[])null);
			if (methodBase != null)
			{
				yield return methodBase;
			}
			else
			{
				LogMissingTarget("QueueEquipAction");
			}
			MethodBase methodBase2 = AccessTools.Method(typeof(Player), "QueueUnequipAction", new Type[1] { typeof(ItemData) }, (Type[])null);
			if (methodBase2 != null)
			{
				yield return methodBase2;
			}
			else
			{
				LogMissingTarget("QueueUnequipAction");
			}
		}

		private static void Prefix(Player __instance, ItemData item, MethodBase __originalMethod, out QueueState __state)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			ActionType actionType = (ActionType)(__originalMethod.Name == "QueueUnequipAction");
			__state = new QueueState(FindAction(__instance, item, actionType), actionType);
		}

		[HarmonyPriority(0)]
		private static void Postfix(Player __instance, ItemData item, QueueState __state)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)__instance == (Object)null || __instance != Player.m_localPlayer || item == null)
			{
				return;
			}
			float value = RepairRequiresMaterialsPlugin.CraftingEquipTimeReductionAtLevel100.Value;
			if (value <= 0f || float.IsNaN(value))
			{
				return;
			}
			MinorActionData val = FindAction(__instance, item, __state.ActionType);
			if (val != null && val != __state.ExistingAction)
			{
				val.m_duration = CalculateAdjustedDuration(val.m_duration, ((Character)__instance).GetSkillFactor((SkillType)107), value);
				if (val.m_duration < 1f)
				{
					val.m_startEffect = null;
				}
			}
		}

		private static MinorActionData? FindAction(Player player, ItemData? item, ActionType actionType)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (item == null)
			{
				return null;
			}
			for (int num = player.m_actionQueue.Count - 1; num >= 0; num--)
			{
				MinorActionData val = player.m_actionQueue[num];
				if (val.m_type == actionType && val.m_item == item)
				{
					return val;
				}
			}
			return null;
		}

		private static float CalculateAdjustedDuration(float baseDuration, float skillFactor, float maximumReductionPercent)
		{
			if (float.IsNaN(baseDuration) || float.IsInfinity(baseDuration) || baseDuration <= 0f)
			{
				return 0f;
			}
			double num = ClampUnit(skillFactor);
			double num2 = ClampPercent(maximumReductionPercent) / 100.0;
			if (num <= 0.0 || num2 <= 0.0)
			{
				return baseDuration;
			}
			double num3 = Math.Max(0.0, 1.0 - num * num2);
			return (float)((double)baseDuration * num3);
		}

		private static double ClampUnit(float value)
		{
			if (float.IsNaN(value) || value <= 0f)
			{
				return 0.0;
			}
			if (!float.IsPositiveInfinity(value) && !(value >= 1f))
			{
				return value;
			}
			return 1.0;
		}

		private static double ClampPercent(float value)
		{
			if (float.IsNaN(value) || value <= 0f)
			{
				return 0.0;
			}
			if (!float.IsPositiveInfinity(value) && !(value >= 100f))
			{
				return value;
			}
			return 100.0;
		}

		private static void LogMissingTarget(string methodName)
		{
			if (!_missingTargetWarningLogged)
			{
				_missingTargetWarningLogged = true;
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Crafting equip-time reduction was not fully applied because Player." + methodName + " was not found."));
			}
		}
	}
	[HarmonyPatch]
	internal static class PlayerEquipmentActionSprintPatch
	{
		private static bool _missingTargetWarningLogged;

		private static IEnumerable<MethodBase> TargetMethods()
		{
			MethodBase methodBase = AccessTools.DeclaredMethod(typeof(Player), "CheckRun", new Type[2]
			{
				typeof(Vector3),
				typeof(float)
			}, (Type[])null);
			if (methodBase != null)
			{
				yield return methodBase;
			}
			else if (!_missingTargetWarningLogged)
			{
				_missingTargetWarningLogged = true;
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)"Equipment changes while running were not enabled because Player.CheckRun was not found.");
			}
		}

		[HarmonyPrefix]
		[HarmonyPriority(0)]
		private static bool Prefix(Player __instance, ref bool __result)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Invalid comparison between Unknown and I4
			if ((Object)__instance == (Object)null || __instance != Player.m_localPlayer || !RepairRequiresMaterialsPlugin.AllowEquipmentChangesWhileRunning.Value.IsOn())
			{
				return true;
			}
			string text = default(string);
			float num = default(float);
			MinorActionData val = default(MinorActionData);
			__instance.GetActionProgress(ref text, ref num, ref val);
			if (val == null || ((int)val.m_type != 0 && (int)val.m_type != 1))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	internal static class CraftingFreeRepairSystem
	{
		private enum TicketOutcome
		{
			None,
			Free,
			AnvilFree,
			Paid
		}

		private sealed class TicketState
		{
			internal string ItemId { get; set; }

			internal ulong Cycle { get; set; }

			internal TicketOutcome Outcome { get; set; }

			internal string PlanFingerprint { get; set; }

			internal TicketState(string itemId, ulong cycle, TicketOutcome outcome, string planFingerprint)
			{
				ItemId = itemId;
				Cycle = cycle;
				Outcome = outcome;
				PlanFingerprint = planFingerprint;
			}

			internal string Serialize()
			{
				char c = Outcome switch
				{
					TicketOutcome.Free => 'F', 
					TicketOutcome.AnvilFree => 'A', 
					TicketOutcome.Paid => 'P', 
					_ => 'N', 
				};
				return string.Join("|", "v1", ItemId, Cycle.ToString(CultureInfo.InvariantCulture), c.ToString(), PlanFingerprint);
			}
		}

		private const string TicketKey = "sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket";

		private const string TicketSchema = "v1";

		private const string RollDomain = "sighsorry.RepairRequiresMaterials.SkillFreeRepairRoll.v1";

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

		internal static RepairPreview ResolvePreview(Player player, RepairPreview stationPreview)
		{
			if (stationPreview.PaymentKind != RepairPaymentKind.StationMaterials)
			{
				return stationPreview;
			}
			ItemData item = stationPreview.Item;
			Inventory inventory = ((Humanoid)player).GetInventory();
			bool flag = HasMaterialCost(stationPreview);
			TicketState ticketState = ReadState(item, inventory, out var corruptState);
			if (corruptState)
			{
				if (!flag)
				{
					return stationPreview;
				}
				ticketState = new TicketState(Guid.NewGuid().ToString("N"), 0uL, TicketOutcome.Paid, BuildPlanFingerprint(stationPreview));
				if (!WriteState(item, inventory, ticketState))
				{
					return stationPreview;
				}
				return stationPreview.WithPayment(RepairPaymentKind.StationMaterials, ticketState.Serialize());
			}
			if (ticketState != null)
			{
				NormalizeSessionCycle(item, inventory, ticketState);
			}
			if (ticketState != null && ticketState.Outcome == TicketOutcome.Paid)
			{
				return stationPreview.WithPayment(RepairPaymentKind.StationMaterials, ticketState.Serialize());
			}
			bool flag2;
			if (ticketState != null)
			{
				TicketOutcome outcome = ticketState.Outcome;
				if ((uint)(outcome - 1) <= 1u)
				{
					flag2 = true;
					goto IL_00ae;
				}
			}
			flag2 = false;
			goto IL_00ae;
			IL_00ae:
			if (flag2)
			{
				string b = BuildPlanFingerprint(stationPreview);
				if (!string.Equals(ticketState.PlanFingerprint, b, StringComparison.Ordinal))
				{
					ticketState.Outcome = TicketOutcome.Paid;
					if (!WriteState(item, inventory, ticketState))
					{
						return stationPreview;
					}
					return stationPreview.WithPayment(RepairPaymentKind.StationMaterials, ticketState.Serialize());
				}
				return stationPreview.WithPayment(GetPaymentKind(player, ticketState), ticketState.Serialize());
			}
			if (!flag || !IsFeatureEnabled())
			{
				return stationPreview;
			}
			if (ticketState == null)
			{
				ticketState = new TicketState(Guid.NewGuid().ToString("N"), 0uL, TicketOutcome.None, string.Empty);
			}
			double num = CalculateFreeRepairChance(((Character)player).GetSkillFactor((SkillType)107), RepairRequiresMaterialsPlugin.CraftingSkillFreeRepairChanceAtLevel0.Value, RepairRequiresMaterialsPlugin.CraftingSkillFreeRepairChanceAtLevel100.Value);
			double num2 = Math.Min(1.0, num + NormalizePercent(RepairRequiresMaterialsPlugin.GalleonAnvilFreeRepairBonus.Value) / 100.0);
			double deterministicRoll = GetDeterministicRoll(ticketState.ItemId, ticketState.Cycle);
			ticketState.Outcome = ((deterministicRoll < num) ? TicketOutcome.Free : ((deterministicRoll < num2) ? TicketOutcome.AnvilFree : TicketOutcome.Paid));
			ticketState.PlanFingerprint = BuildPlanFingerprint(stationPreview);
			if (!WriteState(item, inventory, ticketState))
			{
				return stationPreview;
			}
			return stationPreview.WithPayment(GetPaymentKind(player, ticketState), ticketState.Serialize());
		}

		private static RepairPaymentKind GetPaymentKind(Player player, TicketState state)
		{
			if (!IsFeatureEnabled() || (state.Outcome != TicketOutcome.Free && (state.Outcome != TicketOutcome.AnvilFree || !ArtisanMasteryCompat.CanUseStation(player))))
			{
				return RepairPaymentKind.StationMaterials;
			}
			return RepairPaymentKind.CraftingSkillFree;
		}

		internal static void CompleteSuccessfulRepair(Player player, RepairPreview preview)
		{
			if (!preview.HasRawMaterialCost || string.IsNullOrEmpty(preview.SkillFreeTicketToken))
			{
				return;
			}
			try
			{
				if (!TryParseState(preview.SkillFreeTicketToken, out TicketState state) || state == null)
				{
					return;
				}
				ulong num = ((state.Cycle == ulong.MaxValue) ? 0 : (state.Cycle + 1));
				string itemId = ((state.Cycle == ulong.MaxValue) ? Guid.NewGuid().ToString("N") : state.ItemId);
				if (!SessionMinimumCycles.TryGetValue(state.ItemId, out var value) || num > value)
				{
					SessionMinimumCycles[state.ItemId] = num;
				}
				ItemData item = preview.Item;
				Inventory inventory = ((Humanoid)player).GetInventory();
				bool corruptState;
				TicketState ticketState = ReadState(item, inventory, out corruptState);
				if (ticketState != null && (!string.Equals(ticketState.ItemId, state.ItemId, StringComparison.Ordinal) || ticketState.Cycle != state.Cycle))
				{
					RepairRequiresMaterialsPlugin.Log.LogWarning((object)"A skill-free repair ticket changed during a completed repair; the newer item state was preserved.");
					return;
				}
				TicketState state2 = new TicketState(itemId, num, TicketOutcome.None, string.Empty);
				if (!WriteState(item, inventory, state2))
				{
					RepairRequiresMaterialsPlugin.Log.LogWarning((object)"A completed skill-free repair ticket could not be persisted; its cycle remains advanced for this session.");
				}
			}
			catch (Exception ex)
			{
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Could not complete the skill-free repair ticket: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static bool IsFeatureEnabled()
		{
			return RepairRequiresMaterialsPlugin.EnableCraftingSkillFreeRepairs.Value.IsOn();
		}

		internal static double CalculateFreeRepairChance(float skillFactor, float chanceAtLevel0Percent, float chanceAtLevel100Percent)
		{
			double num = NormalizePercent(chanceAtLevel100Percent) / 100.0;
			double num2 = Math.Min(NormalizePercent(chanceAtLevel0Percent) / 100.0, num);
			double num3 = NormalizeSkillFactor(skillFactor);
			return num2 + (num - num2) * num3;
		}

		private static double NormalizeSkillFactor(float value)
		{
			if (float.IsNaN(value) || value <= 0f)
			{
				return 0.0;
			}
			if (!float.IsPositiveInfinity(value) && !(value >= 1f))
			{
				return value;
			}
			return 1.0;
		}

		private static double NormalizePercent(float value)
		{
			if (float.IsNaN(value) || value <= 0f)
			{
				return 0.0;
			}
			if (!float.IsPositiveInfinity(value) && !(value >= 100f))
			{
				return value;
			}
			return 100.0;
		}

		private static bool HasMaterialCost(RepairPreview preview)
		{
			return preview.Costs.Any((RepairMaterialCost cost) => cost.RequiredAmount > 0);
		}

		private static TicketState? ReadState(ItemData item, Inventory inventory, out bool corruptState)
		{
			corruptState = false;
			if (item.m_customData == null || !item.m_customData.TryGetValue("sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket", out var value))
			{
				return null;
			}
			if (TryParseState(value, out TicketState state))
			{
				return state;
			}
			corruptState = true;
			try
			{
				item.m_customData.Remove("sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket");
				RepairService.MarkInventoryDirty(inventory);
			}
			catch
			{
			}
			return null;
		}

		private static bool TryParseState(string serialized, out TicketState? state)
		{
			state = null;
			string[] array = serialized.Split(new char[1] { '|' }, 5);
			if (array.Length != 5 || !string.Equals(array[0], "v1", StringComparison.Ordinal) || !Guid.TryParseExact(array[1], "N", out var _) || !ulong.TryParse(array[2], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || array[3].Length != 1)
			{
				return false;
			}
			TicketOutcome ticketOutcome = array[3][0] switch
			{
				'F' => TicketOutcome.Free, 
				'A' => TicketOutcome.AnvilFree, 
				'P' => TicketOutcome.Paid, 
				'N' => TicketOutcome.None, 
				_ => (TicketOutcome)(-1), 
			};
			if (ticketOutcome < TicketOutcome.None || (ticketOutcome != TicketOutcome.None && array[4].Length == 0) || (ticketOutcome == TicketOutcome.None && array[4].Length != 0))
			{
				return false;
			}
			state = new TicketState(array[1], result2, ticketOutcome, array[4]);
			return true;
		}

		private static void NormalizeSessionCycle(ItemData item, Inventory inventory, TicketState state)
		{
			if (SessionMinimumCycles.TryGetValue(state.ItemId, out var value) && state.Cycle < value)
			{
				state.Cycle = value;
				state.Outcome = TicketOutcome.None;
				state.PlanFingerprint = string.Empty;
				WriteState(item, inventory, state);
			}
		}

		private static bool WriteState(ItemData item, Inventory inventory, TicketState state)
		{
			try
			{
				if (item.m_customData == null)
				{
					item.m_customData = new Dictionary<string, string>();
				}
				string text = state.Serialize();
				if (item.m_customData.TryGetValue("sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket", out var value) && string.Equals(value, text, StringComparison.Ordinal))
				{
					return true;
				}
				item.m_customData["sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket"] = text;
				RepairService.MarkInventoryDirty(inventory);
				return true;
			}
			catch (Exception ex)
			{
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Could not persist a skill-free repair ticket: " + ex.GetType().Name + ": " + ex.Message));
				return false;
			}
		}

		private static string BuildPlanFingerprint(RepairPreview preview)
		{
			StringBuilder stringBuilder = new StringBuilder();
			AppendPart(stringBuilder, "plan-v1");
			AppendPart(stringBuilder, ResolveItemPrefabName(preview.Item));
			AppendPart(stringBuilder, preview.Item.m_quality.ToString(CultureInfo.InvariantCulture));
			AppendPart(stringBuilder, preview.DurabilityBucketPercent.ToString(CultureInfo.InvariantCulture));
			foreach (RepairMaterialCost item in preview.Costs.Where((RepairMaterialCost cost) => cost.RequiredAmount > 0).OrderBy<RepairMaterialCost, string>((RepairMaterialCost cost) => cost.ResourcePrefabName, StringComparer.Ordinal).ThenBy((RepairMaterialCost cost) => cost.RequiredAmount))
			{
				AppendPart(stringBuilder, item.ResourcePrefabName);
				AppendPart(stringBuilder, item.RequiredAmount.ToString(CultureInfo.InvariantCulture));
			}
			return Sha256Hex(stringBuilder.ToString());
		}

		private static void AppendPart(StringBuilder builder, string value)
		{
			builder.Append(value.Length.ToString(CultureInfo.InvariantCulture));
			builder.Append(':');
			builder.Append(value);
			builder.Append('|');
		}

		private static string ResolveItemPrefabName(ItemData item)
		{
			string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name)?.Trim() ?? string.Empty;
			if (!text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase))
			{
				return text;
			}
			return text.Substring(0, text.Length - "(Clone)".Length).Trim();
		}

		private static double GetDeterministicRoll(string itemId, ulong cycle)
		{
			string s = string.Join("|", "sighsorry.RepairRequiresMaterials.SkillFreeRepairRoll.v1", itemId, cycle.ToString(CultureInfo.InvariantCulture));
			byte[] array;
			using (SHA256 sHA = SHA256.Create())
			{
				array = sHA.ComputeHash(Encoding.UTF8.GetBytes(s));
			}
			ulong num = 0uL;
			for (int i = 0; i < 8; i++)
			{
				num = (num << 8) | array[i];
			}
			return (double)(num >> 11) / 9007199254740992.0;
		}

		private static string Sha256Hex(string value)
		{
			byte[] array;
			using (SHA256 sHA = SHA256.Create())
			{
				array = sHA.ComputeHash(Encoding.UTF8.GetBytes(value));
			}
			StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture));
			}
			return stringBuilder.ToString();
		}
	}
	internal static class CraftingProductionBonusSystem
	{
		internal const int UseVanillaBonus = -1;

		internal const string DefaultExcludedOutputPrefabPatterns = "Simple_*_Socket, Advanced_*_Socket, Perfect_*_Socket";

		private const int MaximumIndependentRolls = 10000;

		private static bool _largeOutputWarningLogged;

		private static volatile PrefabPatternMatcher _excludedOutputPrefabs = PrefabPatternMatcher.Empty;

		internal static void SetExcludedOutputPrefabPatterns(string? patterns)
		{
			_excludedOutputPrefabs = PrefabPatternMatcher.Parse(patterns);
		}

		internal static float CalculatePerItemChance(float skillFactor, float bonusOutputChanceAtLevel100Percent)
		{
			double num = ClampProbability(skillFactor);
			double num2 = ClampLevel100ChancePercent(bonusOutputChanceAtLevel100Percent) / 100.0;
			if (num <= 0.0 || num2 <= 0.0)
			{
				return 0f;
			}
			return (float)ClampProbability(num * num2);
		}

		internal static int RollBonusItems(int baseItemCount, float itemChance, Func<float> nextRandomValue)
		{
			if (baseItemCount <= 0)
			{
				return 0;
			}
			int num = int.MaxValue - baseItemCount;
			if (num <= 0)
			{
				return 0;
			}
			double num2 = ClampProbability(itemChance);
			if (num2 <= 0.0)
			{
				return 0;
			}
			if (num2 >= 1.0)
			{
				return Math.Min(baseItemCount, num);
			}
			if (nextRandomValue == null)
			{
				throw new ArgumentNullException("nextRandomValue");
			}
			int num3 = 0;
			for (int i = 0; i < baseItemCount; i++)
			{
				if (num3 >= num)
				{
					break;
				}
				if ((double)nextRandomValue() < num2)
				{
					num3++;
				}
			}
			return num3;
		}

		internal static int CalculateCraftingSkillBonusOrUseVanilla(InventoryGui gui, Player player, CraftingStation station, int baseItemCount)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Invalid comparison between Unknown and I4
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			if ((Object)station == (Object)null || (int)station.m_craftingSkill != 107)
			{
				return -1;
			}
			if ((Object)player == (Object)null || baseItemCount <= 0)
			{
				return 0;
			}
			Recipe craftRecipe = gui.m_craftRecipe;
			if ((Object)craftRecipe == (Object)null || (Object)craftRecipe.m_item == (Object)null)
			{
				return 0;
			}
			string name = ((Object)((Component)craftRecipe.m_item).gameObject).name;
			if (_excludedOutputPrefabs.IsMatch(name) || craftRecipe.m_item.m_itemData.m_shared.m_maxStackSize <= 1)
			{
				return 0;
			}
			float value = RepairRequiresMaterialsPlugin.CraftingBonusOutputChanceAtLevel100.Value;
			float num = CalculatePerItemChance(((Character)player).GetSkillFactor((SkillType)107), value);
			if (num <= 0f)
			{
				return 0;
			}
			if (baseItemCount > 10000 && num < 1f)
			{
				if (!_largeOutputWarningLogged)
				{
					_largeOutputWarningLogged = true;
					RepairRequiresMaterialsPlugin.Log.LogWarning((object)($"A crafting result exceeded {10000} base items; " + "using Valheim's production-bonus calculation to avoid a long main-thread roll loop."));
				}
				return -1;
			}
			return RollBonusItems(baseItemCount, num, NextRandomValue);
		}

		private static double ClampProbability(double value)
		{
			if (double.IsNaN(value) || value <= 0.0)
			{
				return 0.0;
			}
			if (!double.IsPositiveInfinity(value) && !(value >= 1.0))
			{
				return value;
			}
			return 1.0;
		}

		private static double ClampLevel100ChancePercent(float value)
		{
			if (float.IsNaN(value) || value <= 0f)
			{
				return 0.0;
			}
			if (!float.IsPositiveInfinity(value) && !(value >= 25f))
			{
				return value;
			}
			return 25.0;
		}

		private static float NextRandomValue()
		{
			return Random.value;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	[HarmonyPriority(0)]
	internal static class InventoryGuiCraftingProductionBonusPatch
	{
		private static readonly MethodInfo GetAmountMethod = AccessTools.Method(typeof(Recipe), "GetAmount", new Type[4]
		{
			typeof(int),
			typeof(int).MakeByRefType(),
			typeof(ItemData).MakeByRefType(),
			typeof(int)
		}, (Type[])null);

		private static readonly MethodInfo GetCurrentCraftingStationMethod = AccessTools.Method(typeof(Player), "GetCurrentCraftingStation", (Type[])null, (Type[])null);

		private static readonly MethodInfo BonusHelperMethod = AccessTools.Method(typeof(CraftingProductionBonusSystem), "CalculateCraftingSkillBonusOrUseVanilla", (Type[])null, (Type[])null);

		private static readonly MethodInfo RandomValueGetter = AccessTools.PropertyGetter(typeof(Random), "value");

		private static readonly FieldInfo CraftUpgradeItemField = AccessTools.Field(typeof(InventoryGui), "m_craftUpgradeItem");

		private static readonly FieldInfo CraftBonusChanceField = AccessTools.Field(typeof(InventoryGui), "m_craftBonusChance");

		private static readonly FieldInfo CraftBonusAmountField = AccessTools.Field(typeof(InventoryGui), "m_craftBonusAmount");

		private static bool _patternWarningLogged;

		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
		{
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			try
			{
				if (!TryInject(list, generator, out string failure))
				{
					LogPatternFailure(failure);
				}
			}
			catch (Exception arg)
			{
				LogPatternFailure($"unexpected transpiler error: {arg}");
			}
			return list;
		}

		private static bool TryInject(List<CodeInstruction> codes, ILGenerator generator, out string failure)
		{
			//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cc: Expected O, but got Unknown
			//IL_02e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f0: Expected O, but got Unknown
			//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0301: Expected O, but got Unknown
			//IL_0332: Unknown result type (might be due to invalid IL or missing references)
			//IL_033c: Expected O, but got Unknown
			//IL_0369: Unknown result type (might be due to invalid IL or missing references)
			//IL_0373: Expected O, but got Unknown
			//IL_0380: Unknown result type (might be due to invalid IL or missing references)
			//IL_038a: Expected O, but got Unknown
			//IL_03b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c1: Expected O, but got Unknown
			//IL_03e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ec: Expected O, but got Unknown
			failure = string.Empty;
			int num = FindCall(codes, GetAmountMethod, 0);
			int num2 = FindCall(codes, GetCurrentCraftingStationMethod, 0);
			if (num < 0 || num2 < 0)
			{
				failure = "could not locate Recipe.GetAmount and GetCurrentCraftingStation anchors";
				return false;
			}
			if (num + 1 >= codes.Count || !TryGetStoredLocal(codes[num + 1], out var localIndex))
			{
				failure = "could not resolve the pre-bonus result amount local";
				return false;
			}
			int num3 = num2 + 1;
			if (num3 >= codes.Count || !TryGetStoredLocal(codes[num3], out var localIndex2))
			{
				failure = "could not resolve the current crafting station local";
				return false;
			}
			int startIndex = Math.Max(num + 2, num3 + 1);
			int num4 = FindCall(codes, RandomValueGetter, startIndex);
			if (num4 < 0 || !TryFindZeroInitializedLocal(codes, startIndex, num4, out var storeIndex, out var localIndex3))
			{
				failure = "could not resolve the vanilla crafting-bonus accumulator";
				return false;
			}
			int num5 = FindLocalLoad(codes, localIndex2, storeIndex + 1, num4);
			if (num5 < 0)
			{
				failure = "the vanilla crafting-bonus entry pattern changed";
				return false;
			}
			int num6 = FindFieldLoad(codes, CraftBonusChanceField, num5);
			int num7 = FindFieldLoad(codes, CraftBonusAmountField, num5);
			if (num4 <= num5 || num6 <= num4 || num7 <= num6)
			{
				failure = "could not locate the ordered vanilla crafting-bonus roll";
				return false;
			}
			int num8 = num7 - 2;
			int index = num7 + 2;
			int index2 = num7 + 3;
			int index3 = num7 + 4;
			int num9 = num7 + 6;
			if (num8 < num5 || num9 >= codes.Count || !LoadsLocal(codes[num8], localIndex3) || codes[num7 - 1].opcode != OpCodes.Ldarg_0 || codes[num7 + 1].opcode != OpCodes.Add || !StoresLocal(codes[index], localIndex3) || !LoadsLocal(codes[index2], localIndex) || !LoadsLocal(codes[index3], localIndex3) || codes[num7 + 5].opcode != OpCodes.Add || !StoresLocal(codes[num9], localIndex))
			{
				failure = "the vanilla crafting-bonus accumulation pattern changed";
				return false;
			}
			int num10 = FindFieldLoad(codes, CraftUpgradeItemField, num9 + 1);
			int num11 = num10 - 1;
			if (num10 <= num5 || num11 < 0 || num10 + 1 >= codes.Count || codes[num11].opcode != OpCodes.Ldarg_0 || !IsBranchTrue(codes[num10 + 1]))
			{
				failure = "could not locate the post-bonus inventory-capacity check";
				return false;
			}
			if (num4 >= num11 || num6 >= num11 || num9 >= num11)
			{
				failure = "the vanilla crafting-bonus anchors crossed the capacity-check boundary";
				return false;
			}
			Label label = generator.DefineLabel();
			Label label2;
			if (codes[num11].labels.Count > 0)
			{
				label2 = codes[num11].labels[0];
			}
			else
			{
				label2 = generator.DefineLabel();
				codes[num11].labels.Add(label2);
			}
			CodeInstruction val = new CodeInstruction(OpCodes.Ldc_I4_0, (object)null);
			val.labels.Add(label);
			List<CodeInstruction> list = new List<CodeInstruction>
			{
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Ldarg_1, (object)null),
				CloneWithoutMetadata(codes[num5]),
				CloneWithoutMetadata(codes[index2]),
				new CodeInstruction(OpCodes.Call, (object)BonusHelperMethod),
				CloneWithoutMetadata(codes[storeIndex]),
				CloneWithoutMetadata(codes[index3]),
				new CodeInstruction(OpCodes.Ldc_I4_0, (object)null),
				new CodeInstruction(OpCodes.Blt, (object)label),
				CloneWithoutMetadata(codes[index2]),
				CloneWithoutMetadata(codes[index3]),
				new CodeInstruction(OpCodes.Add, (object)null),
				CloneWithoutMetadata(codes[num + 1]),
				new CodeInstruction(OpCodes.Br, (object)label2),
				val,
				CloneWithoutMetadata(codes[storeIndex])
			};
			list[0].labels.AddRange(codes[num5].labels);
			codes[num5].labels.Clear();
			list[0].blocks.AddRange(codes[num5].blocks);
			codes[num5].blocks.Clear();
			codes.InsertRange(num5, list);
			return true;
		}

		private static int FindCall(List<CodeInstruction> codes, MethodInfo method, int startIndex)
		{
			for (int i = Math.Max(0, startIndex); i < codes.Count; i++)
			{
				if ((codes[i].opcode == OpCodes.Call || codes[i].opcode == OpCodes.Callvirt) && object.Equals(codes[i].operand, method))
				{
					return i;
				}
			}
			return -1;
		}

		private static int FindFieldLoad(List<CodeInstruction> codes, FieldInfo field, int startIndex)
		{
			for (int i = Math.Max(0, startIndex); i < codes.Count; i++)
			{
				if ((codes[i].opcode == OpCodes.Ldfld || codes[i].opcode == OpCodes.Ldsfld) && object.Equals(codes[i].operand, field))
				{
					return i;
				}
			}
			return -1;
		}

		private static bool TryFindZeroInitializedLocal(List<CodeInstruction> codes, int startIndex, int endIndex, out int storeIndex, out int localIndex)
		{
			storeIndex = -1;
			localIndex = -1;
			for (int i = Math.Max(0, startIndex); i + 1 < Math.Min(endIndex, codes.Count); i++)
			{
				if (IsLoadConstantZero(codes[i]) && TryGetStoredLocal(codes[i + 1], out var localIndex2))
				{
					storeIndex = i + 1;
					localIndex = localIndex2;
					return true;
				}
			}
			return false;
		}

		private static int FindLocalLoad(List<CodeInstruction> codes, int localIndex, int startIndex, int endIndex)
		{
			for (int i = Math.Max(0, startIndex); i < Math.Min(endIndex, codes.Count); i++)
			{
				if (LoadsLocal(codes[i], localIndex))
				{
					return i;
				}
			}
			return -1;
		}

		private static bool LoadsLocal(CodeInstruction instruction, int localIndex)
		{
			if (TryGetLocalIndex(instruction, load: true, out var localIndex2))
			{
				return localIndex2 == localIndex;
			}
			return false;
		}

		private static bool StoresLocal(CodeInstruction instruction, int localIndex)
		{
			if (TryGetLocalIndex(instruction, load: false, out var localIndex2))
			{
				return localIndex2 == localIndex;
			}
			return false;
		}

		private static bool TryGetStoredLocal(CodeInstruction instruction, out int localIndex)
		{
			return TryGetLocalIndex(instruction, load: false, out localIndex);
		}

		private static bool TryGetLocalIndex(CodeInstruction instruction, bool load, out int localIndex)
		{
			localIndex = -1;
			OpCode opcode = instruction.opcode;
			if (load)
			{
				if (opcode == OpCodes.Ldloc_0)
				{
					localIndex = 0;
					return true;
				}
				if (opcode == OpCodes.Ldloc_1)
				{
					localIndex = 1;
					return true;
				}
				if (opcode == OpCodes.Ldloc_2)
				{
					localIndex = 2;
					return true;
				}
				if (opcode == OpCodes.Ldloc_3)
				{
					localIndex = 3;
					return true;
				}
				if (opcode != OpCodes.Ldloc && opcode != OpCodes.Ldloc_S)
				{
					return false;
				}
			}
			else
			{
				if (opcode == OpCodes.Stloc_0)
				{
					localIndex = 0;
					return true;