Decompiled source of RepairRequiresMaterials v1.0.0

RepairRequiresMaterials.dll

Decompiled 10 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.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.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;
		}
	}
}
namespace RepairRequiresMaterials
{
	internal static class AdminCommands
	{
		private const string SetDurabilityCommand = "rrm_setdurability";

		private static bool _registered;

		internal static void Register()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_003d: Expected O, but got Unknown
			//IL_0038: 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, new ConsoleOptionsFetcher(GetDurabilityOptions), false, false, false);
			}
		}

		private static object SetInventoryEquipmentDurability(ConsoleEventArgs args)
		{
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			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.";
			}
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.LocalPlayerIsAdminOrHost())
			{
				return "Administrator or host privileges are required.";
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			int num2 = 0;
			int num3 = 0;
			float num4 = num / 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))
				{
					num2++;
					float num5 = Mathf.Clamp(maxDurability * num4, 0f, maxDurability);
					if (!allItem.m_durability.Equals(num5))
					{
						allItem.m_durability = num5;
						num3++;
					}
				}
			}
			if (num3 > 0)
			{
				inventory.Changed();
			}
			string text = num.ToString("0.##", CultureInfo.InvariantCulture);
			Terminal context = args.Context;
			if (context != null)
			{
				context.AddString(string.Format("{0}: set {1} of {2} eligible equipment items to {3}% durability.", "RepairRequiresMaterials", num3, num2, text));
			}
			return true;
		}

		private static List<string> GetDurabilityOptions()
		{
			return new List<string> { "0", "25", "50", "75", "100" };
		}
	}
	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."));
			}
		}
	}
	internal static class CraftingFreeRepairSystem
	{
		private enum TicketOutcome
		{
			None,
			Free,
			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.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();
			string text = BuildPlanFingerprint(stationPreview);
			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, text);
				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());
			}
			if (ticketState != null && ticketState.Outcome == TicketOutcome.Free)
			{
				if (!string.Equals(ticketState.PlanFingerprint, text, StringComparison.Ordinal))
				{
					ticketState.Outcome = TicketOutcome.Paid;
					if (!WriteState(item, inventory, ticketState))
					{
						return stationPreview;
					}
					return stationPreview.WithPayment(RepairPaymentKind.StationMaterials, ticketState.Serialize());
				}
				RepairPaymentKind paymentKind = ((!IsFeatureEnabled()) ? RepairPaymentKind.StationMaterials : RepairPaymentKind.CraftingSkillFree);
				return stationPreview.WithPayment(paymentKind, 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);
			ticketState.Outcome = ((GetDeterministicRoll(ticketState.ItemId, ticketState.Cycle) < num) ? TicketOutcome.Free : TicketOutcome.Paid);
			ticketState.PlanFingerprint = text;
			if (!WriteState(item, inventory, ticketState))
			{
				return stationPreview;
			}
			return stationPreview.WithPayment((ticketState.Outcome != TicketOutcome.Free) ? RepairPaymentKind.StationMaterials : RepairPaymentKind.CraftingSkillFree, ticketState.Serialize());
		}

		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, 
				'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 = NormalizeNonNegative(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 NormalizeNonNegative(float value)
		{
			if (float.IsNaN(value) || value <= 0f)
			{
				return 0.0;
			}
			return value;
		}

		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_02c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c8: Expected O, but got Unknown
			//IL_02e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ec: Expected O, but got Unknown
			//IL_02f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Expected O, but got Unknown
			//IL_032e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0338: Expected O, but got Unknown
			//IL_0365: Unknown result type (might be due to invalid IL or missing references)
			//IL_036f: Expected O, but got Unknown
			//IL_037c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0386: Expected O, but got Unknown
			//IL_03b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bd: Expected O, but got Unknown
			//IL_03de: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e8: 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 || num >= num2)
			{
				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 index = num2 + 1;
			int index2 = num2 + 2;
			int index3 = num2 + 3;
			int num3 = num2 + 4;
			if (num3 >= codes.Count || !TryGetStoredLocal(codes[index], out var localIndex2) || !IsLoadConstantZero(codes[index2]) || !TryGetStoredLocal(codes[index3], out var localIndex3) || !LoadsLocal(codes[num3], localIndex2))
			{
				failure = "the vanilla crafting-bonus entry pattern changed";
				return false;
			}
			int num4 = FindFieldLoad(codes, CraftBonusChanceField, num3);
			int num5 = FindFieldLoad(codes, CraftBonusAmountField, num3);
			int num6 = FindCall(codes, RandomValueGetter, num3);
			if (num6 <= num3 || num4 <= num6 || num5 <= num4)
			{
				failure = "could not locate the ordered vanilla crafting-bonus roll";
				return false;
			}
			int num7 = num5 - 2;
			int index4 = num5 + 2;
			int index5 = num5 + 3;
			int index6 = num5 + 4;
			int num8 = num5 + 6;
			if (num7 < num3 || num8 >= codes.Count || !LoadsLocal(codes[num7], localIndex3) || codes[num5 - 1].opcode != OpCodes.Ldarg_0 || codes[num5 + 1].opcode != OpCodes.Add || !StoresLocal(codes[index4], localIndex3) || !LoadsLocal(codes[index5], localIndex) || !LoadsLocal(codes[index6], localIndex3) || codes[num5 + 5].opcode != OpCodes.Add || !StoresLocal(codes[num8], localIndex))
			{
				failure = "the vanilla crafting-bonus accumulation pattern changed";
				return false;
			}
			int num9 = FindFieldLoad(codes, CraftUpgradeItemField, num8 + 1);
			int num10 = num9 - 1;
			if (num9 <= num3 || num10 < 0 || num9 + 1 >= codes.Count || codes[num10].opcode != OpCodes.Ldarg_0 || !IsBranchTrue(codes[num9 + 1]))
			{
				failure = "could not locate the post-bonus inventory-capacity check";
				return false;
			}
			if (num6 >= num10 || num4 >= num10 || num8 >= num10)
			{
				failure = "the vanilla crafting-bonus anchors crossed the capacity-check boundary";
				return false;
			}
			Label label = generator.DefineLabel();
			Label label2;
			if (codes[num10].labels.Count > 0)
			{
				label2 = codes[num10].labels[0];
			}
			else
			{
				label2 = generator.DefineLabel();
				codes[num10].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[num3]),
				CloneWithoutMetadata(codes[index5]),
				new CodeInstruction(OpCodes.Call, (object)BonusHelperMethod),
				CloneWithoutMetadata(codes[index3]),
				CloneWithoutMetadata(codes[index6]),
				new CodeInstruction(OpCodes.Ldc_I4_0, (object)null),
				new CodeInstruction(OpCodes.Blt, (object)label),
				CloneWithoutMetadata(codes[index5]),
				CloneWithoutMetadata(codes[index6]),
				new CodeInstruction(OpCodes.Add, (object)null),
				CloneWithoutMetadata(codes[num + 1]),
				new CodeInstruction(OpCodes.Br, (object)label2),
				val,
				CloneWithoutMetadata(codes[index3])
			};
			list[0].labels.AddRange(codes[num3].labels);
			codes[num3].labels.Clear();
			list[0].blocks.AddRange(codes[num3].blocks);
			codes[num3].blocks.Clear();
			codes.InsertRange(num3, 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 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;
				}
				if (opcode == OpCodes.Stloc_1)
				{
					localIndex = 1;
					return true;
				}
				if (opcode == OpCodes.Stloc_2)
				{
					localIndex = 2;
					return true;
				}
				if (opcode == OpCodes.Stloc_3)
				{
					localIndex = 3;
					return true;
				}
				if (opcode != OpCodes.Stloc && opcode != OpCodes.Stloc_S)
				{
					return false;
				}
			}
			object operand = instruction.operand;
			if (!(operand is LocalBuilder localBuilder))
			{
				if (!(operand is LocalVariableInfo localVariableInfo))
				{
					if (!(operand is byte b))
					{
						if (operand is int num)
						{
							localIndex = num;
							return true;
						}
						return false;
					}
					localIndex = b;
					return true;
				}
				localIndex = localVariableInfo.LocalIndex;
				return true;
			}
			localIndex = localBuilder.LocalIndex;
			return true;
		}

		private static bool IsLoadConstantZero(CodeInstruction instruction)
		{
			if (!(instruction.opcode == OpCodes.Ldc_I4_0) && (!(instruction.opcode == OpCodes.Ldc_I4) || !object.Equals(instruction.operand, 0)))
			{
				if (instruction.opcode == OpCodes.Ldc_I4_S)
				{
					return Convert.ToInt32(instruction.operand) == 0;
				}
				return false;
			}
			return true;
		}

		private static bool IsBranchTrue(CodeInstruction instruction)
		{
			if (!(instruction.opcode == OpCodes.Brtrue))
			{
				return instruction.opcode == OpCodes.Brtrue_S;
			}
			return true;
		}

		private static CodeInstruction CloneWithoutMetadata(CodeInstruction source)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			return new CodeInstruction(source.opcode, source.operand);
		}

		private static void LogPatternFailure(string reason)
		{
			if (!_patternWarningLogged)
			{
				_patternWarningLogged = true;
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Per-item Crafting bonus patch was not applied; vanilla behavior remains active (" + reason + ")."));
			}
		}
	}
	internal static class CraftingSkillTooltipText
	{
		internal const string HeadingToken = "$rrm_skill_crafting_heading";

		internal const string FreeRepairToken = "$rrm_skill_crafting_free_repair";

		internal const string BonusOutputToken = "$rrm_skill_crafting_bonus_output";

		internal const string EquipSpeedToken = "$rrm_skill_crafting_equip_speed";

		internal static string Append(string? original, bool freeRepairEnabled, float freeRepairChanceAtLevel0, float freeRepairChanceAtLevel100, float bonusOutputChanceAtLevel100, float equipTimeReductionAtLevel100)
		{
			if (original == null)
			{
				original = string.Empty;
			}
			if (HasRepairRequiresMaterialsHeading(original))
			{
				return original;
			}
			float value = (float)(CraftingFreeRepairSystem.CalculateFreeRepairChance(0f, freeRepairChanceAtLevel0, freeRepairChanceAtLevel100) * 100.0);
			float num = (float)(CraftingFreeRepairSystem.CalculateFreeRepairChance(1f, freeRepairChanceAtLevel0, freeRepairChanceAtLevel100) * 100.0);
			float num2 = NormalizePercent(bonusOutputChanceAtLevel100, 25f);
			float num3 = NormalizePercent(equipTimeReductionAtLevel100, 100f);
			bool flag = freeRepairEnabled && num > 0f;
			bool flag2 = num2 > 0f;
			bool flag3 = num3 > 0f;
			if (!flag && !flag2 && !flag3)
			{
				return original;
			}
			StringBuilder stringBuilder = new StringBuilder("$rrm_skill_crafting_heading");
			if (flag)
			{
				stringBuilder.Append('\n').Append(RepairRequiresMaterialsLocalization.Localize("$rrm_skill_crafting_free_repair", FormatPercent(value), FormatPercent(num)));
			}
			if (flag2)
			{
				stringBuilder.Append('\n').Append(RepairRequiresMaterialsLocalization.Localize("$rrm_skill_crafting_bonus_output", FormatPercent(num2)));
			}
			if (flag3)
			{
				stringBuilder.Append('\n').Append(RepairRequiresMaterialsLocalization.Localize("$rrm_skill_crafting_equip_speed", FormatPercent(num3)));
			}
			if (original.Length <= 0)
			{
				return stringBuilder.ToString();
			}
			return original + "\n\n" + stringBuilder;
		}

		internal static bool MatchesSkillDescription(string? tooltipText, string? skillDescription)
		{
			if (!string.IsNullOrWhiteSpace(tooltipText) && !string.IsNullOrWhiteSpace(skillDescription))
			{
				return tooltipText.IndexOf(skillDescription, StringComparison.Ordinal) >= 0;
			}
			return false;
		}

		internal static bool HasRepairRequiresMaterialsHeading(string? tooltipText)
		{
			if (!string.IsNullOrEmpty(tooltipText))
			{
				return tooltipText.IndexOf("$rrm_skill_crafting_heading", StringComparison.Ordinal) >= 0;
			}
			return false;
		}

		private static float NormalizePercent(float value, float maximum)
		{
			if (float.IsNaN(value) || value <= 0f)
			{
				return 0f;
			}
			if (!float.IsPositiveInfinity(value) && !(value >= maximum))
			{
				return value;
			}
			return maximum;
		}

		private static string FormatPercent(float value)
		{
			return value.ToString("0.##", CultureInfo.InvariantCulture);
		}
	}
	[HarmonyPatch(typeof(UITooltip), "UpdateTextElements")]
	internal static class CraftingSkillTooltipAlignmentPatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(UITooltip __instance)
		{
			if ((Object)(object)__instance == (Object)null || !CraftingSkillTooltipText.HasRepairRequiresMaterialsHeading(__instance.m_text) || ((Object)(object)UITooltip.m_current != (Object)null && (Object)(object)UITooltip.m_current != (Object)(object)__instance) || (Object)(object)UITooltip.m_tooltip == (Object)null)
			{
				return;
			}
			TMP_Text[] componentsInChildren = UITooltip.m_tooltip.GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, "Text", StringComparison.Ordinal))
				{
					val.horizontalAlignment = (HorizontalAlignmentOptions)1;
					break;
				}
			}
		}
	}
	[HarmonyPatch(typeof(SkillsDialog), "Setup")]
	internal static class CraftingSkillTooltipPatch
	{
		private static bool _failureLogged;

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		[HarmonyAfter(new string[] { "randyknapp.mods.epicloot" })]
		private static void Postfix(SkillsDialog __instance, Player player)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Invalid comparison between Unknown and I4
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance == (Object)null || (Object)(object)player == (Object)null)
			{
				return;
			}
			try
			{
				Skills skills = ((Character)player).GetSkills();
				List<Skill> list = ((skills != null) ? skills.GetSkillList() : null);
				if (list == null)
				{
					return;
				}
				Skill val = null;
				int craftingIndex = -1;
				for (int i = 0; i < list.Count; i++)
				{
					Skill val2 = list[i];
					if (val2 != null && (int)(val2.m_info?.m_skill).GetValueOrDefault() == 107)
					{
						val = val2;
						craftingIndex = i;
						break;
					}
				}
				if (val?.m_info == null)
				{
					return;
				}
				UITooltip val3 = FindCraftingTooltip(__instance, craftingIndex, val.m_info.m_description);
				if (!((Object)(object)val3 == (Object)null))
				{
					bool freeRepairEnabled = RepairRequiresMaterialsPlugin.EnableCraftingSkillFreeRepairs.Value.IsOn();
					string text = CraftingSkillTooltipText.Append(val3.m_text, freeRepairEnabled, RepairRequiresMaterialsPlugin.CraftingSkillFreeRepairChanceAtLevel0.Value, RepairRequiresMaterialsPlugin.CraftingSkillFreeRepairChanceAtLevel100.Value, RepairRequiresMaterialsPlugin.CraftingBonusOutputChanceAtLevel100.Value, RepairRequiresMaterialsPlugin.CraftingEquipTimeReductionAtLevel100.Value);
					if (!string.Equals(text, val3.m_text, StringComparison.Ordinal))
					{
						val3.Set(val3.m_topic, text, val3.m_anchor, val3.m_fixedPosition);
					}
				}
			}
			catch (Exception ex)
			{
				if (!_failureLogged)
				{
					_failureLogged = true;
					RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Could not extend the Crafting skill tooltip: " + ex.GetBaseException().Message));
				}
			}
		}

		private static UITooltip? FindCraftingTooltip(SkillsDialog dialog, int craftingIndex, string craftingDescription)
		{
			if (dialog.m_elements != null && craftingIndex >= 0 && craftingIndex < dialog.m_elements.Count)
			{
				GameObject obj = dialog.m_elements[craftingIndex];
				UITooltip val = ((obj != null) ? obj.GetComponentInChildren<UITooltip>() : null);
				if ((Object)(object)val != (Object)null && CraftingSkillTooltipText.MatchesSkillDescription(val.m_text, craftingDescription))
				{
					return val;
				}
			}
			InventoryGui componentInParent = ((Component)dialog).GetComponentInParent<InventoryGui>();
			if ((Object)(object)componentInParent == (Object)null)
			{
				return null;
			}
			UITooltip[] componentsInChildren = ((Component)componentInParent).GetComponentsInChildren<UITooltip>(true);
			UITooltip val2 = null;
			UITooltip[] array = componentsInChildren;
			foreach (UITooltip val3 in array)
			{
				if ((Object)(object)val3 != (Object)null && CraftingSkillTooltipText.MatchesSkillDescription(val3.m_text, craftingDescription))
				{
					if (((Component)val3).gameObject.activeInHierarchy)
					{
						return val3;
					}
					if (val2 == null)
					{
						val2 = val3;
					}
				}
			}
			return val2;
		}
	}
	internal static class EquipmentTypeRules
	{
		internal static bool IsEquipment(ItemType itemType)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected I4, but got Unknown
			return (itemType - 3) switch
			{
				16 => true, 
				0 => true, 
				11 => true, 
				19 => true, 
				1 => true, 
				2 => true, 
				12 => true, 
				3 => true, 
				4 => true, 
				8 => true, 
				14 => true, 
				15 => true, 
				21 => true, 
				_ => false, 
			};
		}
	}
	internal enum IncineratorDismantleResponse
	{
		Failed,
		Success,
		NoEligibleItems,
		NoRoom,
		Busy,
		NoAccess,
		InventoryChanged
	}
	internal sealed class IncineratorDismantleController : MonoBehaviour
	{
		private const string RequestRpc = "sighsorry.RepairRequiresMaterials.RequestIncineratorDismantle";

		private const string ResponseRpc = "sighsorry.RepairRequiresMaterials.IncineratorDismantleResponse";

		private const int RequestSchemaVersion = 1;

		private const int MaxKnownRecipeCount = 4096;

		private const int RequestHeaderBytes = 16;

		private const int RecipeTokenBytes = 16;

		private const int MaxRequestBytes = 65552;

		private Incinerator? _incinerator;

		private ZNetView? _nview;

		private bool _registered;

		private bool _operationInProgress;

		private void Start()
		{
			TryRegisterRpcs();
		}

		private void OnDisable()
		{
			ReleaseOperation();
		}

		internal void Initialize(Incinerator incinerator)
		{
			_incinerator = incinerator;
			_nview = ((Component)incinerator).GetComponent<ZNetView>();
			TryRegisterRpcs();
		}

		internal bool RequestDismantle(Player player)
		{
			TryRegisterRpcs();
			if ((Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.HasOwner())
			{
				return false;
			}
			if (!TryBuildRequest(player, out ZPackage request) || request == null)
			{
				return false;
			}
			_nview.InvokeRPC("sighsorry.RepairRequiresMaterials.RequestIncineratorDismantle", new object[1] { request });
			return true;
		}

		private void TryRegisterRpcs()
		{
			if (!_registered && !((Object)(object)_nview == (Object)null) && _nview.IsValid())
			{
				_nview.Register<ZPackage>("sighsorry.RepairRequiresMaterials.RequestIncineratorDismantle", (Action<long, ZPackage>)RpcRequestDismantle);
				_nview.Register<int, int>("sighsorry.RepairRequiresMaterials.IncineratorDismantleResponse", (Action<long, int, int>)RpcDismantleResponse);
				_registered = true;
			}
		}

		private void RpcRequestDismantle(long senderUid, ZPackage request)
		{
			if ((Object)(object)_incinerator == (Object)null || (Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.IsOwner())
			{
				return;
			}
			if (!RepairRequiresMaterialsPlugin.EnableIncineratorDismantling.Value.IsOn())
			{
				SendResponse(senderUid, IncineratorDismantleResponse.Failed);
				return;
			}
			if (_operationInProgress || _incinerator.isInUse || _incinerator.m_container.IsInUse() || _incinerator.m_container.m_loading)
			{
				SendResponse(senderUid, IncineratorDismantleResponse.Busy);
				return;
			}
			if (!TryReadRequestHeader(request, out var playerId, out var knownRecipeCount))
			{
				SendResponse(senderUid, IncineratorDismantleResponse.Failed);
				return;
			}
			if (!TryValidateRequester(senderUid, playerId, requireProximity: true))
			{
				SendResponse(senderUid, IncineratorDismantleResponse.NoAccess);
				return;
			}
			if (!TryReadKnownRecipeTokens(request, knownRecipeCount, out HashSet<IncineratorKnownRecipeToken> knownRecipeTokens) || knownRecipeTokens == null)
			{
				SendResponse(senderUid, IncineratorDismantleResponse.Failed);
				return;
			}
			_operationInProgress = true;
			_incinerator.isInUse = true;
			try
			{
				Inventory inventory = _incinerator.m_container.GetInventory();
				IncineratorDismantleRollSeed rollSeed = IncineratorDismantleRollSeed.CreateRandom();
				if (!IncineratorDismantleCostSystem.TryBuildPlan(inventory, knownRecipeTokens, rollSeed, out IncineratorDismantlePlan plan) || plan == null)
				{
					SendResponse(senderUid, IncineratorDismantleResponse.NoEligibleItems);
					ReleaseOperation();
				}
				else if (!IncineratorDismantleCostSystem.CanApplyPlan(inventory, plan))
				{
					SendResponse(senderUid, IncineratorDismantleResponse.NoRoom);
					ReleaseOperation();
				}
				else
				{
					byte[] fingerprint = IncineratorDismantleCostSystem.GetFingerprint(inventory);
					((MonoBehaviour)this).StartCoroutine(DismantleCoroutine(senderUid, playerId, fingerprint, knownRecipeTokens, rollSeed));
				}
			}
			catch (Exception ex)
			{
				RepairRequiresMaterialsPlugin.Log.LogError((object)("Could not start incinerator dismantling: " + ex.GetType().Name + ": " + ex.Message));
				SendResponse(senderUid, IncineratorDismantleResponse.Failed);
				ReleaseOperation();
			}
		}

		private IEnumerator DismantleCoroutine(long senderUid, long playerId, byte[] expectedFingerprint, HashSet<IncineratorKnownRecipeToken> knownRecipeTokens, IncineratorDismantleRollSeed rollSeed)
		{
			bool leverPulled = false;
			try
			{
				if ((Object)(object)_incinerator == (Object)null || (Object)(object)_nview == (Object)null)
				{
					yield break;
				}
				_nview.InvokeRPC(ZNetView.Everybody, "RPC_AnimateLever", Array.Empty<object>());
				leverPulled = true;
				_incinerator.m_leverEffects.Create(((Component)this).transform.position, ((Component)this).transform.rotation, (Transform)null, 1f, -1);
				yield return (object)new WaitForSeconds(Random.Range(_incinerator.m_effectDelayMin, _incinerator.m_effectDelayMax));
				_nview.InvokeRPC(ZNetView.Everybody, "RPC_AnimateLeverReturn", Array.Empty<object>());
				leverPulled = false;
				if (!_operationInProgress || !TryValidateRequester(senderUid, playerId, requireProximity: false) || _incinerator.m_container.IsInUse() || _incinerator.m_container.m_loading)
				{
					SendResponse(senderUid, IncineratorDismantleResponse.NoAccess);
					yield break;
				}
				Inventory inventory = _incinerator.m_container.GetInventory();
				if (!expectedFingerprint.SequenceEqual(IncineratorDismantleCostSystem.GetFingerprint(inventory)))
				{
					SendResponse(senderUid, IncineratorDismantleResponse.InventoryChanged);
					yield break;
				}
				if (!IncineratorDismantleCostSystem.TryBuildPlan(inventory, knownRecipeTokens, rollSeed, out IncineratorDismantlePlan plan) || plan == null)
				{
					SendResponse(senderUid, IncineratorDismantleResponse.NoEligibleItems);
					yield break;
				}
				if (!IncineratorDismantleCostSystem.CanApplyPlan(inventory, plan))
				{
					SendResponse(senderUid, IncineratorDismantleResponse.NoRoom);
					yield break;
				}
				if (!IncineratorDismantleCostSystem.TryApplyPlan(_incinerator.m_container, inventory, plan))
				{
					SendResponse(senderUid, IncineratorDismantleResponse.Failed);
					yield break;
				}
				if ((Object)(object)_incinerator.m_lightingAOEs != (Object)null)
				{
					Object.Instantiate<GameObject>(_incinerator.m_lightingAOEs, ((Component)this).transform.position, ((Component)this).transform.rotation);
				}
				SendResponse(senderUid, IncineratorDismantleResponse.Success, plan.SourceUnitCount);
				yield return (object)new WaitForSeconds(4f);
			}
			finally
			{
				IncineratorDismantleController incineratorDismantleController = this;
				if (leverPulled && (Object)(object)incineratorDismantleController._nview != (Object)null && incineratorDismantleController._nview.IsValid())
				{
					incineratorDismantleController._nview.InvokeRPC(ZNetView.Everybody, "RPC_AnimateLeverReturn", Array.Empty<object>());
				}
				incineratorDismantleController.ReleaseOperation();
			}
		}

		private bool TryValidateRequester(long senderUid, long playerId, bool requireProximity)
		{
			//IL_0100: 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_00d8: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_incinerator == (Object)null || (Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.IsOwner() || !_incinerator.m_container.IsOwner() || playerId == 0L)
			{
				return false;
			}
			Player player = Player.GetPlayer(playerId);
			if ((Object)(object)player == (Object)null || ((Character)player).IsDead() || (Object)(object)((Character)player).m_nview == (Object)null || !((Character)player).m_nview.IsValid() || ((Character)player).GetOwner() != senderUid)
			{
				return false;
			}
			if (requireProximity)
			{
				Transform val = (((Object)(object)_incinerator.m_incinerateSwitch != (Object)null) ? ((Component)_incinerator.m_incinerateSwitch).transform : ((Component)this).transform);
				float num = Math.Max(0f, player.m_maxInteractDistance) + 1f;
				if (Vector3.Distance(((Character)player).GetEyePoint(), val.position) > num)
				{
					return false;
				}
			}
			if (!_incinerator.m_container.CheckAccess(playerId) || !HasWardAccess(((Component)this).transform.position, playerId))
			{
				return false;
			}
			return true;
		}

		private bool TryBuildRequest(Player player, out ZPackage? request)
		{
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Expected O, but got Unknown
			request = null;
			if ((Object)(object)player == (Object)null || player.GetPlayerID() == 0L || (Object)(object)_incinerator == (Object)null || (Object)(object)_incinerator.m_container == (Object)null || (Object)(object)_incinerator.m_container.m_nview == (Object)null || !_incinerator.m_container.m_nview.IsValid())
			{
				return false;
			}
			HashSet<IncineratorKnownRecipeToken> hashSet = new HashSet<IncineratorKnownRecipeToken>();
			_incinerator.m_container.CheckForChanges();
			foreach (ItemData allItem in _incinerator.m_container.GetInventory().GetAllItems())
			{
				string text = allItem?.m_shared?.m_name;
				if (IncineratorDismantleCostSystem.IsDismantleCandidate(allItem) && !string.IsNullOrEmpty(text) && player.IsRecipeKnown(text))
				{
					hashSet.Add(IncineratorKnownRecipeToken.Create(text));
				}
			}
			if (hashSet.Count > 4096)
			{
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)$"Known-recipe dismantle request exceeded the {4096} recipe limit.");
				return false;
			}
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(player.GetPlayerID());
			val.Write(hashSet.Count);
			foreach (IncineratorKnownRecipeToken item in from value in hashSet
				orderby value.First, value.Second
				select value)
			{
				val.Write(item.First);
				val.Write(item.Second);
			}
			if (val.Size() > 65552)
			{
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)$"Known-recipe dismantle request exceeded the {65552}-byte limit.");
				return false;
			}
			request = val;
			return true;
		}

		private static bool TryReadRequestHeader(ZPackage request, out long playerId, out int knownRecipeCount)
		{
			playerId = 0L;
			knownRecipeCount = 0;
			if (request == null || request.Size() <= 0 || request.Size() > 65552)
			{
				return false;
			}
			try
			{
				request.SetPos(0);
				if (request.ReadInt() != 1)
				{
					return false;
				}
				playerId = request.ReadLong();
				knownRecipeCount = request.ReadInt();
				if (playerId == 0L || knownRecipeCount < 0 || knownRecipeCount > 4096)
				{
					return false;
				}
				int num = 16 + knownRecipeCount * 16;
				return request.Size() == num;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static bool TryReadKnownRecipeTokens(ZPackage request, int knownRecipeCount, out HashSet<IncineratorKnownRecipeToken>? knownRecipeTokens)
		{
			knownRecipeTokens = null;
			if (request == null || knownRecipeCount < 0 || knownRecipeCount > 4096 || request.Size() != 16 + knownRecipeCount * 16)
			{
				return false;
			}
			try
			{
				request.SetPos(16);
				HashSet<IncineratorKnownRecipeToken> hashSet = new HashSet<IncineratorKnownRecipeToken>();
				for (int i = 0; i < knownRecipeCount; i++)
				{
					IncineratorKnownRecipeToken item = new IncineratorKnownRecipeToken(request.ReadULong(), request.ReadULong());
					if (!hashSet.Add(item))
					{
						return false;
					}
				}
				if (request.GetPos() != request.Size())
				{
					return false;
				}
				knownRecipeTokens = hashSet;
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static bool HasWardAccess(Vector3 position, long playerId)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			bool flag2 = false;
			foreach (PrivateArea item in PrivateArea.m_allAreas.ToList())
			{
				if (!((Object)(object)item == (Object)null) && item.IsEnabled() && item.IsInside(position, 0f))
				{
					bool flag3 = (Object)(object)item.m_piece != (Object)null && (item.m_piece.GetCreator() == playerId || item.IsPermitted(playerId));
					flag = flag || flag3;
					flag2 = flag2 || !flag3;
				}
			}
			if (!flag)
			{
				return !flag2;
			}
			return true;
		}

		private void SendResponse(long targetUid, IncineratorDismantleResponse response, int dismantledCount = 0)
		{
			if ((Object)(object)_nview != (Object)null && _nview.IsValid())
			{
				_nview.InvokeRPC(targetUid, "sighsorry.RepairRequiresMaterials.IncineratorDismantleResponse", new object[2]
				{
					(int)response,
					dismantledCount
				});
			}
		}

		private static void RpcDismantleResponse(long senderUid, int responseValue, int dismantledCount)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				((Character)localPlayer).Message((MessageType)2, (IncineratorDismantleResponse)(Enum.IsDefined(typeof(IncineratorDismantleResponse), responseValue) ? responseValue : 0) switch
				{
					IncineratorDismantleResponse.Success => RepairRequiresMaterialsLocalization.Localize("$rrm_dismantle_success", Math.Max(0, dismantledCount)), 
					IncineratorDismantleResponse.NoEligibleItems => "$rrm_dismantle_no_equipment", 
					IncineratorDismantleResponse.NoRoom => "$rrm_dismantle_no_room", 
					IncineratorDismantleResponse.Busy => "$rrm_dismantle_busy", 
					IncineratorDismantleResponse.NoAccess => "$rrm_dismantle_no_access", 
					IncineratorDismantleResponse.InventoryChanged => "$rrm_dismantle_inventory_changed", 
					_ => "$rrm_dismantle_failed", 
				}, 0, (Sprite)null);
			}
		}

		private void ReleaseOperation()
		{
			if (_operationInProgress && (Object)(object)_incinerator != (Object)null)
			{
				_incinerator.isInUse = false;
			}
			_operationInProgress = false;
		}
	}
	internal readonly struct IncineratorKnownRecipeToken : IEquatable<IncineratorKnownRecipeToken>
	{
		private const string HashDomain = "sighsorry.RepairRequiresMaterials.KnownDismantleRecipe.v1\0";

		internal ulong First { get; }

		internal ulong Second { get; }

		internal IncineratorKnownRecipeToken(ulong first, ulong second)
		{
			First = first;
			Second = second;
		}

		internal static IncineratorKnownRecipeToken Create(string recipeName)
		{
			byte[] array;
			using (SHA256 sHA = SHA256.Create())
			{
				array = sHA.ComputeHash(Encoding.UTF8.GetBytes("sighsorry.RepairRequiresMaterials.KnownDismantleRecipe.v1\0" + recipeName));
			}
			ulong num = 0uL;
			ulong num2 = 0uL;
			for (int i = 0; i < 8; i++)
			{
				num = (num << 8) | array[i];
				num2 = (num2 << 8) | array[i + 8];
			}
			return new IncineratorKnownRecipeToken(num, num2);
		}

		public bool Equals(IncineratorKnownRecipeToken other)
		{
			if (First == other.First)
			{
				return Second == other.Second;
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			if (obj is IncineratorKnownRecipeToken other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (First.GetHashCode() * 397) ^ Second.GetHashCode();
		}
	}
	internal readonly struct IncineratorDismantleRollSeed
	{
		internal ulong First { get; }

		internal ulong Second { get; }

		internal IncineratorDismantleRollSeed(ulong first, ulong second)
		{
			First = first;
			Second = second;
		}

		internal static IncineratorDismantleRollSeed CreateRandom()
		{
			byte[] array = new byte[16];
			using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create())
			{
				randomNumberGenerator.GetBytes(array);
			}
			ulong num = 0uL;
			ulong num2 = 0uL;
			for (int i = 0; i < 8; i++)
			{
				num = (num << 8) | array[i];
				num2 = (num2 << 8) | array[i + 8];
			}
			return new IncineratorDismantleRollSeed(num, num2);
		}
	}
	internal sealed class IncineratorDismantleOutput
	{
		internal ItemDrop Resource { get; }

		internal string PrefabName { get; }

		internal int Amount { get; }

		internal IncineratorDismantleOutput(ItemDrop resource, string prefabName, int amount)
		{
			Resource = resource;
			PrefabName = prefabName;
			Amount = amount;
		}
	}
	internal sealed class IncineratorDismantlePlan
	{
		internal IReadOnlyList<ItemData> SourceItems { get; }

		internal IReadOnlyList<IncineratorDismantleOutput> Outputs { get; }

		internal int SourceUnitCount { get; }

		internal IncineratorDismantlePlan(IReadOnlyList<ItemData> sourceItems, IReadOnlyList<IncineratorDismantleOutput> outputs)
		{
			SourceItems = sourceItems;
			Outputs = outputs;
			int num = 0;
			foreach (ItemData sourceItem in sourceItems)
			{
				int num2 = Math.Max(0, sourceItem.m_stack);
				num = ((num >= int.MaxValue - num2) ? int.MaxValue : (num + num2));
			}
			SourceUnitCount = num;
		}
	}
	internal static class IncineratorDismantleCostSystem
	{
		private sealed class RecipeMaterialAmount
		{
			internal ItemDrop Resource { get; }

			internal string PrefabName { get; }

			internal decimal BaseAmount { get; set; }

			internal decimal UpgradeAmount { get; set; }

			internal RecipeMaterialAmount(ItemDrop resource, string prefabName)
			{
				Resource = resource;
				PrefabName = prefabName;
			}
		}

		private sealed class RawMaterialTotal
		{
			internal ItemDrop Resource { get; }

			internal string PrefabName { get; }

			internal decimal Amount { get; set; }

			internal RawMaterialTotal(ItemDrop resource, string prefabName)
			{
				Resource = resource;
				PrefabName = prefabName;
			}
		}

		private sealed class ItemReferenceComparer : IEqualityComparer<ItemData>
		{
			internal static readonly ItemReferenceComparer Instance = new ItemReferenceComparer();

			public bool Equals(ItemData? x, ItemData? y)
			{
				return x == y;
			}

			public int GetHashCode(ItemData obj)
			{
				return RuntimeHelpers.GetHashCode(obj);
			}
		}

		private const string FractionalReturnHashDomain = "sighsorry.RepairRequiresMaterials.DismantleFractionalReturn.v1\0";

		private const decimal UInt64Range = 18446744073709551616m;

		private static volatile PrefabPatternMatcher _additionalDismantleablePrefabs = PrefabPatternMatcher.Empty;

		internal static void SetAdditionalDismantleablePrefabPatterns(string? patterns)
		{
			_additionalDismantleablePrefabs = PrefabPatternMatcher.Parse(patterns);
		}

		internal static bool TryBuildPlan(Inventory inventory, HashSet<IncineratorKnownRecipeToken>? knownRecipeTokens, IncineratorDismantleRollSeed rollSeed, out IncineratorDismantlePlan? plan)
		{
			plan = null;
			if (inventory == null)
			{
				return false;
			}
			decimal num = (decimal)Mathf.Clamp(RepairRequiresMaterialsPlugin.DismantleBaseReturnPercent.Value, 0f, 100f) / 100m;
			decimal num2 = (decimal)Mathf.Clamp(RepairRequiresMaterialsPlugin.DismantleUpgradeReturnPercent.Value, 0f, 100f) / 100m;
			if (num <= 0m && num2 <= 0m)
			{
				return false;
			}
			Dictionary<string, RawMaterialTotal> dictionary = new Dictionary<string, RawMaterialTotal>(StringComparer.Ordinal);
			List<ItemData> list = new List<ItemData>();
			foreach (ItemData item in inventory.GetAllItems().ToList())
			{
				if (!TryGetRecipeMaterials(item, knownRecipeTokens, out List<RecipeMaterialAmount> selectedMaterials) || selectedMaterials == null)
				{
					continue;
				}
				bool flag = false;
				foreach (RecipeMaterialAmount item2 in selectedMaterials)
				{
					decimal num3 = item2.BaseAmount * num + item2.UpgradeAmount * num2;
					if (!(num3 <= 0m))
					{
						flag = true;
						if (!dictionary.TryGetValue(item2.PrefabName, out var value))
						{
							value = new RawMaterialTotal(item2.Resource, item2.PrefabName);
							dictionary.Add(item2.PrefabName, value);
						}
						value.Amount += num3;
					}
				}
				if (flag)
				{
					list.Add(item);
				}
			}
			if (list.Count == 0)
			{
				return false;
			}
			List<IncineratorDismantleOutput> list2 = new List<IncineratorDismantleOutput>(dictionary.Count);
			foreach (RawMaterialTotal item3 in dictionary.Values.OrderBy<RawMaterialTotal, string>((RawMaterialTotal rawMaterialTotal) => rawMaterialTotal.PrefabName, StringComparer.Ordinal))
			{
				decimal num4 = decimal.Floor(item3.Amount);
				int num5;
				if (num4 >= 2147483647m)
				{
					num5 = int.MaxValue;
				}
				else
				{
					num5 = decimal.ToInt32(num4);
					decimal num6 = item3.Amount - num4;
					if (num6 > 0m && ShouldRoundFractionUp(rollSeed, item3.PrefabName, num6))
					{
						num5++;
					}
				}
				if (num5 > 0)
				{
					list2.Add(new IncineratorDismantleOutput(item3.Resource, item3.PrefabName, num5));
				}
			}
			plan = new IncineratorDismantlePlan(list, list2);
			return true;
		}

		private static bool ShouldRoundFractionUp(IncineratorDismantleRollSeed rollSeed, string materialPrefabName, decimal fraction)
		{
			if (fraction <= 0m)
			{
				return false;
			}
			if (fraction >= 1m)
			{
				return true;
			}
			byte[] bytes = Encoding.UTF8.GetBytes("sighsorry.RepairRequiresMaterials.DismantleFractionalReturn.v1\0" + materialPrefabName);
			byte[] array = new byte[16 + bytes.Length];
			WriteUInt64BigEndian(array, 0, rollSeed.First);
			WriteUInt64BigEndian(array, 8, rollSeed.Second);
			Buffer.BlockCopy(bytes, 0, array, 16, bytes.Length);
			byte[] array2;
			using (SHA256 sHA = SHA256.Create())
			{
				array2 = sHA.ComputeHash(array);
			}
			ulong num = 0uL;
			for (int i = 0; i < 8; i++)
			{
				num = (num << 8) | array2[i];
			}
			return (decimal)num / 18446744073709551616m < fraction;
		}

		private static void WriteUInt64BigEndian(byte[] destination, int offset, ulong value)
		{
			for (int num = 7; num >= 0; num--)
			{
				destination[offset + num] = (byte)value;
				value >>= 8;
			}
		}

		internal static bool CanApplyPlan(Inventory inventory, IncineratorDismantlePlan plan)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			Inventory val = new Inventory(inventory.GetName(), inventory.GetBkg(), inventory.GetWidth(), inventory.GetHeight());
			HashSet<ItemData> hashSet = new HashSet<ItemData>(plan.SourceItems, ItemReferenceComparer.Instance);
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (!hashSet.Contains(allItem))
				{
					val.GetAllItems().Add(allItem.Clone());
				}
			}
			val.Changed();
			foreach (IncineratorDismantleOutput output in plan.Outputs)
			{
				if (!HasCompatibleSharedName(val, output) || !TryAddOutput(val, output))
				{
					return false;
				}
			}
			return true;
		}

		internal static bool TryApplyPlan(Container container, Inventory inventory, IncineratorDismantlePlan plan)
		{
			if ((Object)(object)container == (Object)null || inventory == null || container.m_loading)
			{
				return false;
			}
			List<ItemData> list = (from item in inventory.GetAllItems()
				select item.Clone()).ToList();
			bool loading = container.m_loading;
			bool result = false;
			container.m_loading = true;
			try
			{
				foreach (ItemData sourceItem in plan.SourceItems)
				{
					if (!inventory.RemoveItem(sourceItem))
					{
						throw new InvalidOperationException("The incinerator inventory changed before dismantling completed.");
					}
				}
				foreach (IncineratorDismantleOutput output in plan.Outputs)
				{
					if (!TryAddOutput(inventory, output))
					{
						throw new InvalidOperationException("The incinerator could not accept dismantle output '" + output.PrefabName + "'.");
					}
				}
				result = true;
			}
			catch (Exception ex)
			{
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Dismantle transaction rolled back: " + ex.GetType().Name + ": " + ex.Message));
				inventory.GetAllItems().Clear();
				foreach (ItemData item in list)
				{
					inventory.GetAllItems().Add(item);
				}
			}
			finally
			{
				container.m_loading = loading;
				inventory.Changed();
			}
			return result;
		}

		internal static byte[] GetFingerprint(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();
		}

		internal static bool IsDismantleCandidate(ItemData? item)
		{
			string prefabName;
			bool isEquipment;
			return TryGetDismantleCandidatePrefab(item, out prefabName, out isEquipment);
		}

		private static bool TryGetRecipeMaterials(ItemData item, HashSet<IncineratorKnownRecipeToken>? knownRecipeTokens, out List<RecipeMaterialAmount>? selectedMaterials)
		{
			selectedMaterials = null;
			if (!TryGetDismantleCandidatePrefab(item, out string prefabName, out bool isEquipment) || string.IsNullOrEmpty(item.m_shared.m_name) || knownRecipeTokens == null || !knownRecipeTokens.Contains(IncineratorKnownRecipeToken.Create(item.m_shared.m_name)))
			{
				return false;
			}
			List<List<RecipeMaterialAmount>> list = new List<List<RecipeMaterialAmount>>();
			foreach (Recipe recipe in RepairRecipeCatalog.GetRecipes(item))
			{
				if (!((Object)(object)recipe == (Object)null) && recipe.m_enabled && recipe.m_amount > 0 && (!isEquipment || recipe.m_amount == 1) && !recipe.m_requireOnlyOneIngredient && TryBuildRecipeMaterialAmounts(item, prefabName, recipe, out List<RecipeMaterialAmount> materials) && materials != null)
				{
					list.Add(materials);
				}
			}
			if (list.Count == 0)
			{
				return false;
			}
			List<RecipeMaterialAmount> list2 = list[0];
			for (int i = 1; i < list.Count; i++)
			{
				if (!HaveEquivalentMaterialCosts(list2, list[i]))
				{
					return false;
				}
			}
			selectedMaterials = list2;
			return true;
		}

		private static bool TryBuildRecipeMaterialAmounts(ItemData item, string itemPrefabName, Recipe recipe, out List<RecipeMaterialAmount>? materials)
		{
			materials = null;
			Dictionary<string, RecipeMaterialAmount> dictionary = new Dictionary<string, RecipeMaterialAmount>(StringComparer.Ordinal);
			int val = Math.Max(1, item.m_shared.m_maxQuality);
			int num = Math.Max(1, Math.Min(item.m_quality, val));
			int sourceStack = Math.Max(1, item.m_stack);
			int amount = recipe.m_amount;
			Requirement[] array = recipe.m_resources ?? Array.Empty<Requirement>();
			foreach (Requirement val2 in array)
			{
				if (val2 == null || (Object)(object)val2.m_resItem == (Object)null)
				{
					return false;
				}
				ItemDrop resItem = val2.m_resItem;
				string text = ResolveItemDropPrefabName(resItem);
				if (text.Length == 0 || string.Equals(text, itemPrefabName, StringComparison.Ordinal))
				{
					return false;
				}
				decimal num2 = ScaleRecipeAmount(Math.Max(0, val2.GetAmount(1)), sourceStack, amount);
				decimal amountPerCraft = default(decimal);
				for (int j = 2; j <= num; j++)
				{
					amountPerCraft += (decimal)Math.Max(0, val2.GetAmount(j));
				}
				decimal num3 = ScaleRecipeAmount(amountPerCraft, sourceStack, amount);
				if (!(num2 <= 0m) || !(num3 <= 0m))
				{
					if (!dictionary.TryGetValue(text, out var value))
					{
						value = new RecipeMaterialAmount(resItem, text);
						dictionary.Add(text, value);
					}
					value.BaseAmount += num2;
					value.UpgradeAmount += num3;
				}
			}
			if (dictionary.Count == 0)
			{
				return false;
			}
			materials = dictionary.Values.OrderBy<RecipeMaterialAmount, string>((RecipeMaterialAmount material) => material.PrefabName, StringComparer.Ordinal).ToList();
			return true;
		}

		internal static decimal ScaleRecipeAmount(decimal amountPerCraft, int sourceStack, int recipeOutputAmount)
		{
			if (!(amountPerCraft <= 0m) && sourceStack > 0 && recipeOutputAmount > 0)
			{
				return amountPerCraft * (decimal)sourceStack / (decimal)recipeOutputAmount;
			}
			return 0m;
		}

		private static bool HaveEquivalentMaterialCosts(IReadOnlyList<RecipeMaterialAmount> first, IReadOnlyList<RecipeMaterialAmount> second)
		{
			if (first.Count != second.Count)
			{
				return false;
			}
			for (int i = 0; i < first.Count; i++)
			{
				RecipeMaterialAmount recipeMaterialAmount = first[i];
				RecipeMaterialAmount recipeMaterialAmount2 = second[i];
				if (!string.Equals(recipeMaterialAmount.PrefabName, recipeMaterialAmount2.PrefabName, StringComparison.Ordinal) || recipeMaterialAmount.BaseAmount != recipeMaterialAmount2.BaseAmount || recipeMaterialAmount.UpgradeAmount != recipeMaterialAmount2.UpgradeAmount)
				{
					return false;
				}
			}
			return true;
		}

		private static bool HasCompatibleSharedName(Inventory inventory, IncineratorDismantleOutput output)
		{
			string name = output.Resource.m_itemData.m_shared.m_name;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (string.Equals(allItem.m_shared.m_name, name, StringComparison.Ordinal) && !string.Equals(CleanPrefabName(((Object)(object)allItem.m_dropPrefab != (Object)null) ? ((Object)allItem.m_dropPrefab).name : string.Empty), output.PrefabName, StringComparison.Ordinal))
				{
					return false;
				}
			}
			return true;
		}

		private static bool TryAddOutput(Inventory inventory, IncineratorDismantleOutput output)
		{
			GameObject gameObject = ((Component)output.Resource).gameObject;
			int val = Math.Max(1, output.Resource.m_itemData.m_shared.m_maxStackSize);
			int num = output.Amount;
			while (num > 0)
			{
				int num2 = Math.Min(num, val);
				if (!inventory.AddItem(gameObject, num2))
				{
					return false;
				}
				num -= num2;
			}
			return true;
		}

		private static bool IsBlacklisted(string prefabName)
		{
			string value = RepairRequiresMaterialsPlugin.DismantleBlacklist.Value;
			if (string.IsNullOrWhiteSpace(value))
			{
				return false;
			}
			string[] array = value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				if (string.Equals(CleanPrefabName(array[i]), prefabName, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private static bool TryGetDismantleCandidatePrefab(ItemData? item, out string prefabName, out bool isEquipment)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			prefabName = string.Empty;
			isEquipment = false;
			if (item == null || item.m_shared.m_questItem || item.m_stack <= 0)
			{
				return false;
			}
			prefabName = CleanPrefabName(((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : string.Empty);
			if (prefabName.Length == 0 || IsBlacklisted(prefabName))
			{
				return false;
			}
			isEquipment = EquipmentTypeRules.IsEquipment(item.m_shared.m_itemType);
			if (isEquipment)
			{
				if (item.m_shared.m_maxStackSize == 1)
				{
					return item.m_stack == 1;
				}
				return false;
			}
			return _additionalDismantleablePrefabs.IsMatch(prefabName);
		}

		private static string ResolveItemDropPrefabName(ItemDrop itemDrop)
		{
			string text = (((Object)(object)itemDrop.m_itemData.m_dropPrefab != (Object)null) ? ((Object)itemDrop.m_itemData.m_dropPrefab).name : string.Empty);
			return CleanPrefabName(string.IsNullOrWhiteSpace(text) ? ((Object)itemDrop).name : text);
		}

		private static string CleanPrefabName(string? value)
		{
			string text = value?.Trim() ?? string.Empty;
			if (!text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase))
			{
				return text;
			}
			return text.Substring(0, text.Length - "(Clone)".Length).Trim();
		}
	}
	[HarmonyPatch]
	internal static class IncineratorBuildRecipeLifecyclePatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			yield return AccessTools.Method(typeof(ZNetScene), "Awake", (Type[])null, (Type[])null);
			yield return AccessTools.Method(typeof(ObjectDB), "UpdateRegisters", (Type[])null, (Type[])null);
		}

		[HarmonyPriority(0)]
		private static void Postfix()
		{
			IncineratorBuildRecipeSystem.Apply();
		}
	}
	internal static class IncineratorBuildRecipeSystem
	{
		internal const string DefaultRecipe = "Iron:8,Copper:4,Thunderstone:1";

		private const string IncineratorPrefabName = "incinerator";

		private static Piece? _trackedPiece;

		private static Requirement[] _originalRequirements = Array.Empty<Requirement>();

		private static string _lastWarningKey = string.Empty;

		internal static void Apply()
		{
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			GameObject prefab = instance.GetPrefab("incinerator");
			if ((Object)(object)prefab == (Object)null)
			{
				return;
			}
			Piece component = prefab.GetComponent<Piece>();
			if ((Object)(object)component == (Object)null || (Object)(object)prefab.GetComponent<Incinerator>() == (Object)null)
			{
				return;
			}
			TrackOriginalRecipe(component);
			ObjectDB instance2 = ObjectDB.instance;
			if (!((Object)(object)instance2 == (Object)null))
			{
				string text = RepairRequiresMaterialsPlugin.IncineratorBuildRecipe.Value?.Trim() ?? string.Empty;
				Requirement[] requirements;
				string error;
				if (text.Length == 0)
				{
					ApplyRequirements(component, CloneRequirements(_originalRequirements));
					_lastWarningKey = string.Empty;
				}
				else if (text.Equals("None", StringComparison.OrdinalIgnoreCase) || text.Equals("Free", StringComparison.OrdinalIgnoreCase) || text == "-")
				{
					ApplyRequirements(component, Array.Empty<Requirement>());
					_lastWarningKey = string.Empty;
				}
				else if (!TryCreateRequirements(instance2, text, out requirements, out error))
				{
					ApplyRequirements(component, CloneRequirements(_originalRequirements));
					WarnInvalidRecipeOnce(text, error);
				}
				else
				{
					ApplyRequirements(component, requirements);
					_lastWarningKey = string.Empty;
				}
			}
		}

		private static void TrackOriginalRecipe(Piece piece)
		{
			if (_trackedPiece != piece)
			{
				_trackedPiece = piece;
				_originalRequirements = CloneRequirements(piece.m_resources);
				_lastWarningKey = string.Empty;
			}
		}

		private static bool TryCreateRequirements(ObjectDB objectDb, string recipe, out Requirement[] requirements, out string error)
		{
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Expected O, but got Unknown
			requirements = Array.Empty<Requirement>();
			error = string.Empty;
			string[] array = recipe.Split(new char[4] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length == 0)
			{
				error = "the recipe has no ingredients";
				return false;
			}
			List<Requirement> list = new List<Requirement>(array.Length);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				string text = array2[i].Trim();
				int num = text.IndexOf(':');
				if (num <= 0 || num != text.LastIndexOf(':') || num >= text.Length - 1)
				{
					error = "'" + text + "' is not an ItemPrefab:Amount entry";
					return false;
				}
				string text2 = text.Substring(0, num).Trim();
				string s = text.Substring(num + 1).Trim();
				if (text2.Length == 0 || !int.TryParse(s, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0)
				{
					error = "'" + text + "' does not contain an exact item prefab and a positive integer amount";
					return false;
				}
				GameObject itemPrefab = objectDb.GetItemPrefab(text2);
				ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null);
				if ((Object)(object)val == (Object)null)
				{
					error = "item prefab '" + text2 + "' is not registered";
					return false;
				}
				string text3 = val.m_itemData.m_shared.m_name?.Trim() ?? string.Empty;
				if (!hashSet.Add(text2) || text3.Length == 0 || !hashSet2.Add(text3))
				{
					error = "item prefab '" + text2 + "' duplicates another ingredient";
					return false;
				}
				list.Add(new Requirement
				{
					m_resItem = val,
					m_amount = result,
					m_extraAmountOnlyOneIngredient = 0,
					m_amountPerLevel = 1,
					m_recover = true
				});
			}
			requirements = list.ToArray();
			return true;
		}

		private static void ApplyRequirements(Piece piece, Requirement[] requirements)
		{
			piece.m_resources = requirements;
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != null)
			{
				localPlayer.UpdateAvailablePiecesList();
			}
		}

		private static Requirement[] CloneRequirements(Requirement[]? source)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			if (source == null || source.Length == 0)
			{
				return Array.Empty<Requirement>();
			}
			Requirement[] array = (Requirement[])(object)new Requirement[source.Length];
			for (int i = 0; i < source.Length; i++)
			{
				Requirement val = source[i];
				array[i] = new Requirement
				{
					m_resItem = val.m_resItem,
					m_amount = val.m_amount,
					m_extraAmountOnlyOneIngredient = val.m_extraAmountOnlyOneIngredient,
					m_amountPerLevel = val.m_amountPerLevel,
					m_recover = val.m_recover
				};
			}
			return array;
		}

		private static void WarnInvalidRecipeOnce(string recipe, string error)
		{
			string text = recipe + "\0" + error;
			if (!string.Equals(_lastWarningKey, text, StringComparison.Ordinal))
			{
				_lastWarningKey = text;
				RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Invalid Incinerator Build Recipe '" + recipe + "': " + error + ". Restored the original recipe."));
			}
		}
	}
	[HarmonyPatch(typeof(Incinerator), "Awake")]
	internal static class IncineratorDismantleAwakePatch
	{
		[HarmonyPriority(0)]
		private static void Postfix(Incinerator __instance)
		{
			if (IncineratorDismantlePatches.IsSupportedIncinerator(__instance))
			{
				IncineratorDismantleController incineratorDismantleController = ((Component)__instance).GetComponent<IncineratorDismantleController>();
				if ((Object)(object)incineratorDismantleController == (Object)null)
				{
					incineratorDismantleController = ((Component)__instance).gameObject.AddComponent<IncineratorDismantleController>();
				}
				incineratorDismantleController.Initialize(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Switch), "Interact")]
	internal static class IncineratorDismantleSwitchInteractPatch
	{
		[HarmonyPriority(800)]
		private static bool Prefix(Switch __instance, Humanoid character, bool hold, ref bool __result)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			if (!RepairRequiresMaterialsPlugin.EnableIncineratorDismantling.Value.IsOn() || !IncineratorDismantlePatches.TryGetIncineratorLever(__instance, out Incinerator incinerator) || (Object)(object)incinerator == (Object)null)
			{
				return true;
			}
			KeyCode value = RepairRequiresMaterialsPlugin.DismantleModifierKey.Value;
			if ((int)value == 0 || !ZInput.GetKey(value, false))
			{
				return true;
			}
			__result = false;
			if (hold)
			{
				return false;
			}
			Player val = (Player)(object)((character is Player) ? character : null);
			if (val == null)
			{
				return false;
			}
			if (!PrivateArea.CheckAccess(((Component)incinerator).transform.position, 0f, true, false) || !incinerator.m_container.CheckAccess(val.GetPlayerID()))
			{
				((Character)val).Message((MessageType)2, "$piece_noaccess", 0, (Sprite)null);
				return false;
			}
			IncineratorDismantleController component = ((Component)incinerator).GetComponent<IncineratorDismantleController>();
			if ((Object)(object)component == (Object)null || !component.RequestDismantle(val))
			{
				((Character)val).Message((MessageType)2, "$rrm_dismantle_unavailable", 0, (Sprite)null);
				return false;
			}
			__result = true;
			return false;
		}
	}
	[HarmonyPatch(typeof(Incinerator), "GetLeverHoverText")]
	internal static class IncineratorDismantleHoverTextPatch
	{
		[HarmonyPriority(0)]
		private static void Postfix(Incinerator __instance, ref string __result)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			if (RepairRequiresMaterialsPlugin.EnableIncineratorDismantling.Value.IsOn() && IncineratorDismantlePatches.IsSupportedIncinerator(__instance) && (int)RepairRequiresMaterialsPlugin.DismantleModifierKey.Value != 0 && PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, false, false))
			{
				string text = ((object)RepairRequiresMaterialsPlugin.DismantleModifierKey.Value/*cast due to .constrained prefix*/).ToString();
				_