Decompiled source of CookingCompatibility v0.1.9

plugins/CookingCompatibility/CookingCompatibility.dll

Decompiled 13 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using ConditionalConfigSync;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using Vercadi.RunicEnchantingCompatibility;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("CookingCompatibility")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.9.0")]
[assembly: AssemblyInformationalVersion("0.1.9")]
[assembly: AssemblyProduct("CookingCompatibility")]
[assembly: AssemblyTitle("CookingCompatibility")]
[assembly: AssemblyVersion("0.1.9.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CookingCompatibility
{
	internal static class ClientHandshake
	{
		internal const string MinimumPeerVersion = "0.1.4";

		internal const float TimeoutSeconds = 60f;

		internal static IEnumerator Run<TPeer>(Func<bool> currentSession, Func<TPeer> serverPeer, Action<TPeer> sendHello, Func<bool> acknowledged, Func<float> clock, Func<object> wait, Action<string> fail) where TPeer : class
		{
			float deadline = clock() + 60f;
			bool sent = false;
			while (currentSession() && (!sent || !acknowledged()))
			{
				if (clock() >= deadline)
				{
					fail(sent ? "Server did not acknowledge the cooking recipe fingerprint." : "Timed out waiting for the server connection before the cooking recipe handshake.");
					break;
				}
				if (!sent)
				{
					TPeer val = serverPeer();
					if (val != null)
					{
						sent = true;
						try
						{
							sendHello(val);
						}
						catch (Exception ex)
						{
							if (currentSession())
							{
								fail("Could not send the cooking recipe fingerprint: " + ex.Message);
							}
							break;
						}
						if (!currentSession() || acknowledged())
						{
							break;
						}
					}
				}
				yield return wait();
			}
		}
	}
	internal sealed class ContentSession : IDisposable
	{
		internal readonly HashSet<string> ProtectedNames = new HashSet<string>(StringComparer.Ordinal);

		internal readonly List<string> Report = new List<string>();

		private readonly List<Recipe> recipes = new List<Recipe>();

		private readonly Dictionary<CookingStation, ItemConversion> conversions = new Dictionary<CookingStation, ItemConversion>();

		private readonly ObjectDB db;

		private readonly ZNetScene scene;

		private ItemDrop hareFrom;

		private ItemDrop hareTo;

		private bool grill;

		internal string Fingerprint { get; private set; }

		internal int RecipeCount => recipes.Count;

		internal ContentSession(ObjectDB db, ZNetScene scene)
		{
			this.db = db;
			this.scene = scene;
		}

		internal void Prepare(ISet<string> supported, ISet<string> enabled, bool recyclingSafe)
		{
			List<string> list = new List<string> { "CookingCompatibility/0.1.0/protocol1" };
			string[] modules = Rules.Modules;
			foreach (string text in modules)
			{
				list.Add("setting|" + text + "|" + enabled.Contains(text));
			}
			RecipeRule[] array = Rules.Recipes;
			foreach (RecipeRule recipeRule in array)
			{
				string text2 = Rules.Unavailable(recipeRule, supported, enabled, recyclingSafe);
				if (text2 == null)
				{
					text2 = AddRecipe(recipeRule, list);
				}
				if (text2 != null)
				{
					Report.Add(recipeRule.Id + ": " + text2);
				}
			}
			if (enabled.Contains("Hare grilling") && supported.Contains("blacks7ar.CookingAdditions") && supported.Contains("blacks7ar.OdinsHares"))
			{
				hareFrom = Item("BOH_RawHare_Meat");
				hareTo = Item("BOH_CookedHare_Meat");
				GameObject prefab = scene.GetPrefab("BCA_Griller");
				CookingStation val = (Object.op_Implicit((Object)(object)prefab) ? prefab.GetComponent<CookingStation>() : null);
				GameObject prefab2 = scene.GetPrefab("piece_cookingstation");
				ItemConversion[] array2 = ((prefab2 != null) ? prefab2.GetComponent<CookingStation>() : null)?.m_conversion?.Where((ItemConversion c) => (Object)(object)c.m_from == (Object)(object)hareFrom && (Object)(object)c.m_to == (Object)(object)hareTo).ToArray();
				if (Object.op_Implicit((Object)(object)hareFrom) && Object.op_Implicit((Object)(object)hareTo) && Object.op_Implicit((Object)(object)val) && array2 != null && array2.Length == 1 && Math.Abs(array2[0].m_cookTime - 25f) < 0.001f)
				{
					ItemConversion[] array3 = val.m_conversion?.Where((ItemConversion c) => (Object)(object)c.m_from == (Object)(object)hareFrom).ToArray();
					if (array3 != null && array3.Length == 0)
					{
						grill = true;
						ApplyStation(val);
						list.Add("grill|BCA_Griller|BOH_RawHare_Meat|BOH_CookedHare_Meat|25");
						Report.Add("Hare grilling: active, 25 seconds");
					}
					else
					{
						Report.Add("Hare grilling: existing conversion; left unchanged");
					}
				}
				else
				{
					Report.Add("Hare grilling: missing/changed native conversion or station; skipped");
				}
			}
			else
			{
				Report.Add("Hare grilling: disabled, absent or unsupported optional dependency");
			}
			list.AddRange(ProtectedNames.Select((string p) => "no-refund|" + p));
			Fingerprint = Rules.Digest(list);
		}

		private string AddRecipe(RecipeRule rule, List<string> signature)
		{
			ItemDrop output = Item(rule.Output);
			ItemDrop from = Item(rule.From);
			ItemDrop to = Item(rule.To);
			GameObject prefab = scene.GetPrefab(rule.Station);
			CraftingStation val = (Object.op_Implicit((Object)(object)prefab) ? prefab.GetComponent<CraftingStation>() : null);
			if (!Object.op_Implicit((Object)(object)output) || !Object.op_Implicit((Object)(object)from) || !Object.op_Implicit((Object)(object)to) || !Object.op_Implicit((Object)(object)val) || (rule.Finished != null && !Object.op_Implicit((Object)(object)Item(rule.Finished))))
			{
				return "required item/station missing";
			}
			Recipe[] array = db.m_recipes.Where((Recipe r) => Object.op_Implicit((Object)(object)r) && (Object)(object)r.m_item == (Object)(object)output && !((Object)r).name.StartsWith("Recipe_Vercadi_Cooking_", StringComparison.Ordinal)).ToArray();
			if (array.Length != 1)
			{
				return "native recipe absent or ambiguous";
			}
			Recipe val2 = array[0];
			if (!val2.m_enabled || val2.m_noCraftOnlyUpgrade || val2.m_requireOnlyOneIngredient || val2.m_amount <= 0 || val2.m_amount > 1000 || val2.m_minStationLevel < 1)
			{
				return "native recipe disabled or unsupported";
			}
			if ((Object)(object)val2.m_craftingStation != (Object)(object)val || val2.m_resources == null)
			{
				return "native station changed or invalid costs";
			}
			string[] array2 = val2.m_resources.Select((Requirement r) => (!Object.op_Implicit((Object)(object)r?.m_resItem)) ? null : ((Object)((Component)r.m_resItem).gameObject).name).ToArray();
			int[] amounts = val2.m_resources.Select((Requirement r) => r?.m_amount ?? 0).ToArray();
			if (!Rules.ValidCosts(array2, amounts, rule.From, rule.Finished != null) || array2.Contains(rule.To))
			{
				return "native ingredients changed or invalid";
			}
			if (rule.Finished == null && output.m_itemData.m_shared.m_food <= 0f && output.m_itemData.m_shared.m_foodStamina <= 0f && output.m_itemData.m_shared.m_foodEitr <= 0f)
			{
				return "output is not edible food";
			}
			string name = "Recipe_Vercadi_Cooking_" + rule.Id;
			if (db.m_recipes.Any((Recipe r) => Object.op_Implicit((Object)(object)r) && ((Object)r).name == name))
			{
				return "recipe identifier already present";
			}
			Recipe val3 = Object.Instantiate<Recipe>(val2);
			((Object)val3).name = name;
			val3.m_resources = val2.m_resources.Select(delegate(Requirement r)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: Expected O, but got Unknown
				Requirement val4 = new Requirement();
				FieldInfo[] fields = typeof(Requirement).GetFields(BindingFlags.Instance | BindingFlags.Public);
				foreach (FieldInfo fieldInfo in fields)
				{
					fieldInfo.SetValue(val4, fieldInfo.GetValue(r));
				}
				if ((Object)(object)r.m_resItem == (Object)(object)from)
				{
					val4.m_resItem = to;
				}
				return val4;
			}).ToArray();
			string content = Describe(val3);
			if (db.m_recipes.Any((Recipe r) => Object.op_Implicit((Object)(object)r) && (Object)(object)r.m_item == (Object)(object)output && Describe(r) == content))
			{
				Object.Destroy((Object)(object)val3);
				return "equivalent recipe already present";
			}
			recipes.Add(val3);
			db.m_recipes.Add(val3);
			ProtectedNames.Add(rule.Output);
			if (rule.Finished != null)
			{
				ProtectedNames.Add(rule.Finished);
			}
			signature.Add(name + "|" + content);
			Report.Add(rule.Id + ": active");
			return null;
		}

		private ItemDrop Item(string name)
		{
			GameObject itemPrefab = db.GetItemPrefab(name);
			if (itemPrefab == null)
			{
				return null;
			}
			return itemPrefab.GetComponent<ItemDrop>();
		}

		private static string Describe(Recipe recipe)
		{
			List<string> list = new List<string>();
			foreach (FieldInfo item in typeof(Recipe).GetFields(BindingFlags.Instance | BindingFlags.Public).OrderBy<FieldInfo, string>((FieldInfo f) => f.Name, StringComparer.Ordinal))
			{
				if (!(item.Name == "m_resources"))
				{
					list.Add(item.Name + "=" + Value(item.GetValue(recipe)));
				}
			}
			if (recipe.m_resources == null)
			{
				return "invalid";
			}
			Requirement[] resources = recipe.m_resources;
			foreach (Requirement r in resources)
			{
				if (r == null)
				{
					return "invalid";
				}
				list.Add("requirement:" + string.Join(",", from f in typeof(Requirement).GetFields(BindingFlags.Instance | BindingFlags.Public).OrderBy<FieldInfo, string>((FieldInfo f) => f.Name, StringComparer.Ordinal)
					select f.Name + "=" + Value(f.GetValue(r))));
			}
			return string.Join("|", list);
		}

		private static string Value(object value)
		{
			if (value == null)
			{
				return "null";
			}
			Component val = (Component)((value is Component) ? value : null);
			if (val != null)
			{
				if (!Object.op_Implicit((Object)(object)val))
				{
					return "null";
				}
				return ((Object)val.gameObject).name;
			}
			Object val2 = (Object)((value is Object) ? value : null);
			if (val2 != null)
			{
				if (!Object.op_Implicit(val2))
				{
					return "null";
				}
				return val2.name;
			}
			if (value is IFormattable formattable)
			{
				return formattable.ToString(null, CultureInfo.InvariantCulture);
			}
			return value.ToString();
		}

		internal void ApplyStation(CookingStation station)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			if (grill && Object.op_Implicit((Object)(object)station) && !(Rules.Prefab(((Object)((Component)station).gameObject).name) != "BCA_Griller") && station.m_conversion != null && !conversions.ContainsKey(station) && !station.m_conversion.Any((ItemConversion c) => (Object)(object)c.m_from == (Object)(object)hareFrom))
			{
				ItemConversion val = new ItemConversion
				{
					m_from = hareFrom,
					m_to = hareTo,
					m_cookTime = 25f
				};
				station.m_conversion = station.m_conversion.Concat((IEnumerable<ItemConversion>)(object)new ItemConversion[1] { val }).ToList();
				conversions.Add(station, val);
			}
		}

		internal void ApplyExistingStations()
		{
			CookingStation[] array = Object.FindObjectsByType<CookingStation>((FindObjectsSortMode)0);
			foreach (CookingStation station in array)
			{
				ApplyStation(station);
			}
		}

		public void Dispose()
		{
			foreach (KeyValuePair<CookingStation, ItemConversion> entry in conversions)
			{
				if (Object.op_Implicit((Object)(object)entry.Key) && entry.Key.m_conversion != null)
				{
					entry.Key.m_conversion = entry.Key.m_conversion.Where((ItemConversion c) => c != entry.Value).ToList();
				}
			}
			conversions.Clear();
			foreach (Recipe recipe in recipes)
			{
				if (Object.op_Implicit((Object)(object)db))
				{
					db.m_recipes.Remove(recipe);
				}
				if (Object.op_Implicit((Object)(object)recipe))
				{
					Object.Destroy((Object)(object)recipe);
				}
			}
			recipes.Clear();
			ProtectedNames.Clear();
			grill = false;
		}
	}
	internal static class CopperShaderGuard
	{
		private static FieldInfo queuedPrefabs;

		private static readonly Dictionary<Material, Shader> remembered = new Dictionary<Material, Shader>();

		private static Action<string> log;

		private static bool warned;

		internal static void Install(Assembly cooking, Harmony harmony, Action<string> report)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			//IL_00c4: Expected O, but got Unknown
			if ((int)SystemInfo.graphicsDeviceType != 4)
			{
				Type? type = cooking.GetType("CookingAdditions.Functions.ShaderReplacer", throwOnError: true);
				MethodInfo method = type.GetMethod("ReplaceShaderPatch", BindingFlags.Static | BindingFlags.NonPublic);
				FieldInfo field = type.GetField("GOToSwap", BindingFlags.Static | BindingFlags.NonPublic);
				if (method == null || method.ReturnType != typeof(void) || method.GetParameters().Length != 0 || field == null || field.FieldType != typeof(List<GameObject>))
				{
					throw new NotSupportedException("CookingAdditions copper shader binding changed.");
				}
				queuedPrefabs = field;
				log = report;
				harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(CopperShaderGuard), "BeforeSwap", (Type[])null), new HarmonyMethod(typeof(CopperShaderGuard), "AfterSwap", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				report("Cooking copper shader guard prepared for grill and pot materials.");
			}
		}

		private static bool Usable(Shader shader)
		{
			if (Object.op_Implicit((Object)(object)shader) && ((Object)shader).name == "Standard" && shader.isSupported)
			{
				return shader.passCount > 0;
			}
			return false;
		}

		private static bool Target(Material material)
		{
			if (Object.op_Implicit((Object)(object)material))
			{
				return ((Object)material).name == "bca_copper";
			}
			return false;
		}

		private static void BeforeSwap(out List<Material> __state)
		{
			__state = new List<Material>();
			try
			{
				List<Material> list = new List<Material>();
				foreach (KeyValuePair<Material, Shader> item in remembered)
				{
					if (!Object.op_Implicit((Object)(object)item.Key) || !Usable(item.Value))
					{
						list.Add(item.Key);
					}
				}
				foreach (Material item2 in list)
				{
					remembered.Remove(item2);
				}
				HashSet<Material> hashSet = new HashSet<Material>();
				if (!(queuedPrefabs.GetValue(null) is List<GameObject> list2))
				{
					return;
				}
				foreach (GameObject item3 in list2)
				{
					if (!Object.op_Implicit((Object)(object)item3))
					{
						continue;
					}
					Renderer[] componentsInChildren = item3.GetComponentsInChildren<Renderer>(true);
					foreach (Renderer val in componentsInChildren)
					{
						if (!Object.op_Implicit((Object)(object)val))
						{
							continue;
						}
						Material[] sharedMaterials = val.sharedMaterials;
						foreach (Material val2 in sharedMaterials)
						{
							if (Target(val2) && hashSet.Add(val2))
							{
								__state.Add(val2);
								if (!remembered.ContainsKey(val2) && Usable(val2.shader))
								{
									remembered[val2] = val2.shader;
								}
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				Warn("Copper shader capture skipped: " + ex.Message);
			}
		}

		private static void AfterSwap(List<Material> __state)
		{
			try
			{
				if (__state == null)
				{
					return;
				}
				foreach (Material item in __state)
				{
					if (!Target(item))
					{
						continue;
					}
					if (remembered.TryGetValue(item, out var value) && Usable(value))
					{
						if ((Object)(object)item.shader != (Object)(object)value)
						{
							item.shader = value;
						}
						continue;
					}
					if (Usable(item.shader))
					{
						remembered[item] = item.shader;
						continue;
					}
					Shader val = null;
					bool flag = false;
					Shader[] array = Resources.FindObjectsOfTypeAll<Shader>();
					foreach (Shader val2 in array)
					{
						if (Usable(val2))
						{
							if (Object.op_Implicit((Object)(object)val) && (Object)(object)val != (Object)(object)val2)
							{
								flag = true;
								break;
							}
							val = val2;
						}
					}
					if (!flag && Object.op_Implicit((Object)(object)val))
					{
						item.shader = val;
						remembered[item] = val;
					}
					else
					{
						Warn("No unambiguous supported Standard shader for the cooking copper material; leaving it unchanged.");
					}
				}
			}
			catch (Exception ex)
			{
				Warn("Copper shader restore skipped: " + ex.Message);
			}
		}

		private static void Warn(string message)
		{
			if (!warned)
			{
				warned = true;
				log?.Invoke(message);
			}
		}

		internal static void Clear()
		{
			remembered.Clear();
			queuedPrefabs = null;
			log = null;
			warned = false;
		}
	}
	internal static class DryingAdapter
	{
		private const BindingFlags All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static MethodInfo done;

		private static MethodInfo find;

		private static MethodInfo free;

		private static MethodInfo raining;

		private static FieldInfo conversions;

		private static FieldInfo from;

		private static FieldInfo view;

		private static FieldInfo distance;

		private static RunicBridge materials;

		private static bool busy;

		private static bool faulted;

		private static MethodInfo Method(Type t, string n, Type[] args = null)
		{
			if (args != null)
			{
				return t.GetMethod(n, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, args, null);
			}
			return t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Single((MethodInfo m) => m.Name == n);
		}

		internal static void Bind(Assembly cooking)
		{
			Type type = cooking.GetType("CookingAdditions.Functions.DryingStation", throwOnError: true);
			done = Method(type, "HaveDoneItem");
			find = Method(type, "FindCookableItem", new Type[1] { typeof(Inventory) });
			free = Method(type, "GetFreeSlot");
			raining = Method(type, "IsRaining");
			conversions = type.GetField("m_conversions", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			from = cooking.GetType("CookingAdditions.Functions.DryingStation+ItemConversion", throwOnError: true).GetField("m_from", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			view = type.GetField("m_nview", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			distance = typeof(Player).GetField("m_maxInteractDistance", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (done == null || find == null || free == null || raining == null || conversions == null || from == null || view == null || distance == null)
			{
				throw new MissingMemberException("Drying rack interaction contract changed.");
			}
		}

		internal static void Install(Assembly cooking, RunicBridge bridge)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Expected O, but got Unknown
			Bind(cooking);
			materials = bridge;
			Harmony val = new Harmony("vercadi.CookingCompatibility.Drying");
			try
			{
				val.Patch((MethodBase)raining, (HarmonyMethod)null, new HarmonyMethod(typeof(DryingAdapter), "Weather", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				val.Patch((MethodBase)Method(cooking.GetType("CookingAdditions.Functions.DryingStation", throwOnError: true), "OnInteract", new Type[1] { typeof(Humanoid) }), new HarmonyMethod(typeof(DryingAdapter), "Interact", (Type[])null)
				{
					priority = 0
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch
			{
				val.UnpatchSelf();
				throw;
			}
		}

		private static void Weather(ref bool __result)
		{
			if (Plugin.DryingWeatherEnabled)
			{
				__result = false;
			}
		}

		private static bool Interact(object __instance, Humanoid user, ref bool __result)
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.DryingStorageEnabled && !faulted && !busy && materials != null && materials.Enabled)
			{
				Player val = (Player)(object)((user is Player) ? user : null);
				if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && ((Character)val).IsOwner() && !((Character)val).IsDead() && !((Character)val).IsTeleporting())
				{
					Component val2 = (Component)((__instance is Component) ? __instance : null);
					if (!Object.op_Implicit((Object)(object)val2))
					{
						return true;
					}
					busy = true;
					try
					{
						object? value = view.GetValue(__instance);
						ZNetView val3 = (ZNetView)((value is ZNetView) ? value : null);
						float num = (float)distance.GetValue(val);
						if (!Object.op_Implicit((Object)(object)val3) || !val3.IsValid() || !materials.AllowsTable(val2.transform.position) || Vector3.Distance(((Component)val).transform.position, val2.transform.position) > num || (bool)done.Invoke(__instance, null) || (int)free.Invoke(__instance, null) < 0 || (bool)raining.Invoke(null, null) || find.Invoke(__instance, new object[1] { ((Humanoid)val).GetInventory() }) != null)
						{
							return true;
						}
						List<Cost> list = new List<Cost>();
						foreach (object item in (IEnumerable)conversions.GetValue(__instance))
						{
							if (list.Count >= 128)
							{
								throw new NotSupportedException("Drying conversion list exceeds review bound.");
							}
							object? value2 = from.GetValue(item);
							ItemDrop val4 = (ItemDrop)((value2 is ItemDrop) ? value2 : null);
							if (Object.op_Implicit((Object)(object)val4) && val4.m_itemData?.m_shared != null)
							{
								string id = materials.Resource(val4.m_itemData);
								if (!string.IsNullOrEmpty(id) && !list.Any((Cost c) => c.Resource == id))
								{
									list.Add(new Cost(val4.m_itemData.m_shared.m_name, id, 1));
								}
							}
						}
						if (list.Count == 0)
						{
							return true;
						}
						Dictionary<string, int> available = materials.Preview(val, list);
						int value3;
						Cost cost = list.FirstOrDefault((Cost c) => available.TryGetValue(c.Name, out value3) && value3 > 0);
						if (cost == null)
						{
							return true;
						}
						if (materials.Pull(val, new List<Cost> { cost }, out var reason))
						{
							return true;
						}
						((Character)val).Message((MessageType)2, reason, 0, (Sprite)null, false);
						__result = false;
						return false;
					}
					catch (Exception ex)
					{
						faulted = true;
						try
						{
							string text = materials.RecoveryRecord();
							if (text != null)
							{
								string text2 = Path.Combine(Paths.ConfigPath, "CookingCompatibility-recovery");
								Directory.CreateDirectory(text2);
								File.WriteAllText(Path.Combine(text2, DateTime.UtcNow.ToString("yyyyMMdd-HHmmss-fffffff") + ".txt"), text);
							}
						}
						catch (Exception ex2)
						{
							Plugin.MaterialFailure(ex2);
						}
						Plugin.MaterialFailure(ex);
						((Character)val).Message((MessageType)2, "Drying material pull stopped. Check the log before retrying.", 0, (Sprite)null, false);
						__result = false;
						return false;
					}
					finally
					{
						busy = false;
					}
				}
			}
			return true;
		}
	}
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("vercadi.CookingCompatibility", "Cooking Compatibility", "0.1.9")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency("_shudnal.ConditionalConfigSync", "1.0.9")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Id = "vercadi.CookingCompatibility";

		public const string Version = "0.1.9";

		private static Plugin instance;

		private Harmony harmony;

		private ConfigSync sync;

		private CustomSyncedValue<string> serverFingerprint;

		private readonly Dictionary<string, ConfigEntry<bool>> settings = new Dictionary<string, ConfigEntry<bool>>();

		private readonly HashSet<string> supported = new HashSet<string>(StringComparer.Ordinal);

		private ContentSession content;

		private Coroutine setup;

		private bool recyclingSafe;

		private bool ready;

		private bool failed;

		private ConfigEntry<bool> dryingStorage;

		private ConfigEntry<bool> dryingWeather;

		private ZNetScene currentScene;

		private ZNetPeer awaitingAck;

		private const string Hello = "vercadi.CookingCompatibility.Hello";

		private const string Ack = "vercadi.CookingCompatibility.Ack";

		private const string Error = "vercadi.CookingCompatibility.Error";

		private readonly Dictionary<ZNetPeer, string> handshakes = new Dictionary<ZNetPeer, string>();

		private readonly HashSet<ZNetPeer> accepted = new HashSet<ZNetPeer>();

		internal static bool DryingStorageEnabled
		{
			get
			{
				if (Object.op_Implicit((Object)(object)instance) && instance.ready)
				{
					return instance.dryingStorage.Value;
				}
				return false;
			}
		}

		internal static bool DryingWeatherEnabled
		{
			get
			{
				if (Object.op_Implicit((Object)(object)instance) && instance.ready)
				{
					return instance.dryingWeather.Value;
				}
				return false;
			}
		}

		internal static void MaterialFailure(Exception ex)
		{
			if (Object.op_Implicit((Object)(object)instance))
			{
				((BaseUnityPlugin)instance).Logger.LogError((object)("Drying storage disabled for this session after material error; no automatic retry. " + ex));
			}
		}

		private void Awake()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_0539: Unknown result type (might be due to invalid IL or missing references)
			//IL_0680: Unknown result type (might be due to invalid IL or missing references)
			//IL_068f: Unknown result type (might be due to invalid IL or missing references)
			//IL_069e: Unknown result type (might be due to invalid IL or missing references)
			//IL_05c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_05d1: Expected O, but got Unknown
			//IL_065e: Unknown result type (might be due to invalid IL or missing references)
			//IL_066c: Expected O, but got Unknown
			//IL_04b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c0: Expected O, but got Unknown
			//IL_051a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0530: Expected O, but got Unknown
			instance = this;
			sync = new ConfigSync("vercadi.CookingCompatibility")
			{
				DisplayName = "Cooking Compatibility",
				CurrentVersion = "0.1.9",
				MinimumRequiredVersion = "0.1.4",
				ModRequired = true
			};
			((ConditionalConfigSync)sync).AddLockingConfigEntry<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("General", "Lock server settings", true, "Only server administrators may change shared settings. Restart after changing recipe modules."));
			string[] modules = Rules.Modules;
			foreach (string text in modules)
			{
				string text2 = ((text == "Hare curing") ? "Allow Odin's hare meat to produce the existing strong CookingAdditions salted hare. Opt in deliberately; default off. Restart required." : ("Enable the reviewed " + text.ToLowerInvariant() + " integration when both content mods are installed. Preserve native costs and station requirements. Restart required."));
				ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("Modules", text, Rules.DefaultEnabled(text), text2);
				settings.Add(text, val);
				((ConditionalConfigSync)sync).AddConfigEntry<bool>(val, (ConfigSyncMode)0);
				val.SettingChanged += delegate
				{
					if (content != null)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)"Recipe setting changed: restart the server/game to apply. Active recipes remain fixed for this session.");
					}
				};
			}
			dryingStorage = ((BaseUnityPlugin)this).Config.Bind<bool>("Drying rack", "Use nearby and backpack materials", true, "On interaction, pull one existing prepared drying ingredient from the equipped closed backpack or eligible nearby chests. Requires reviewed RunicCrafting and RunicStorage; inventory room is required.");
			dryingWeather = ((BaseUnityPlugin)this).Config.Bind<bool>("Drying rack", "Allow loading during rain and snow", true, "Remove the leftover input restriction in CookingAdditions 1.3.3. Drying durations and outputs remain unchanged.");
			((ConditionalConfigSync)sync).AddConfigEntry<bool>(dryingStorage, (ConfigSyncMode)0);
			((ConditionalConfigSync)sync).AddConfigEntry<bool>(dryingWeather, (ConfigSyncMode)0);
			serverFingerprint = new CustomSyncedValue<string>((ConditionalConfigSync)(object)sync, "Resolved cooking recipe fingerprint", "", 0, (IEqualityComparer<string>)null);
			foreach (KeyValuePair<string, string> hash in Rules.Hashes)
			{
				if (!Chainloader.PluginInfos.TryGetValue(hash.Key, out var value))
				{
					continue;
				}
				using FileStream inputStream = File.OpenRead(((object)value.Instance).GetType().Assembly.Location);
				using SHA256 sHA = SHA256.Create();
				if (BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "") == hash.Value)
				{
					supported.Add(hash.Key);
				}
				else
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Unsupported optional binary: " + hash.Key + ". Its integrations will be skipped."));
				}
			}
			if (supported.Contains("blacks7ar.CookingAdditions"))
			{
				RunicBridge runicBridge = null;
				try
				{
					if (Chainloader.PluginInfos.TryGetValue("chazman.RunicCrafting", out var value2) && Chainloader.PluginInfos.TryGetValue("chazman.RunicStorage", out var value3))
					{
						RequireRunicPair(((object)value2.Instance).GetType().Assembly, ((object)value3.Instance).GetType().Assembly);
						runicBridge = new RunicBridge(((object)value2.Instance).GetType().Assembly, ((object)value3.Instance).GetType().Assembly);
						if (Chainloader.PluginInfos.TryGetValue("shudnal.ExtraSlots", out var value4))
						{
							runicBridge.SetSlotPolicy(new PlayerSlotPolicy(((object)value4.Instance).GetType().Assembly).Ordinary);
						}
						if (Chainloader.PluginInfos.TryGetValue("vapok.mods.adventurebackpacks", out var value5))
						{
							try
							{
								runicBridge.SetBackpack(new BackpackAccess(((object)value5.Instance).GetType().Assembly));
							}
							catch (Exception ex)
							{
								((BaseUnityPlugin)this).Logger.LogWarning((object)("Drying backpack support skipped; chest support remains. " + ex.Message));
							}
						}
					}
				}
				catch (Exception ex2)
				{
					runicBridge = null;
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Drying storage support unavailable. " + ex2.Message));
				}
				try
				{
					DryingAdapter.Install(((object)Chainloader.PluginInfos["blacks7ar.CookingAdditions"].Instance).GetType().Assembly, runicBridge);
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Drying rack adapter prepared: weather correction; storage bridge=" + (runicBridge != null) + ". Activates after shared scene setup."));
				}
				catch (Exception ex3)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Drying adapter skipped; recipe integrations remain available. " + ex3));
				}
			}
			recyclingSafe = !Chainloader.PluginInfos.ContainsKey("Azumatt.Recycle_N_Reclaim");
			if (supported.Contains("Azumatt.Recycle_N_Reclaim"))
			{
				try
				{
					RecyclerAdapter.Install(((object)Chainloader.PluginInfos["Azumatt.Recycle_N_Reclaim"].Instance).GetType().Assembly);
					recyclingSafe = true;
					((BaseUnityPlugin)this).Logger.LogInfo((object)"ACTIVE: Recycle N Reclaim 1.4.5 food refund protection. Inventory deletion remains available without a refund.");
				}
				catch (Exception ex4)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Recycler adapter unavailable; alternative recipes withheld. " + ex4));
				}
			}
			harmony = new Harmony("vercadi.CookingCompatibility");
			if (supported.Contains("blacks7ar.CookingAdditions") && ((BaseUnityPlugin)this).Config.Bind<bool>("Visuals", "Preserve cooking copper shader", true, "Client-only: preserve the supported Standard shader on CookingAdditions grill and pot copper materials when returning to the menu. Restart required.").Value)
			{
				try
				{
					CopperShaderGuard.Install(((object)Chainloader.PluginInfos["blacks7ar.CookingAdditions"].Instance).GetType().Assembly, new Harmony("vercadi.CookingCompatibility.CopperShader"), delegate(string message)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)message);
					});
				}
				catch (Exception ex5)
				{
					new Harmony("vercadi.CookingCompatibility.CopperShader").UnpatchSelf();
					CopperShaderGuard.Clear();
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Cooking copper shader guard unavailable; cooking integrations remain available. " + ex5.Message));
				}
			}
			try
			{
				harmony.Patch((MethodBase)AccessTools.Method(typeof(ZNetScene), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, Last("SceneReady"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(ZNetScene), "OnDestroy", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "SceneEnded", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(CookingStation), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, Last("StationReady"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(ZNet), "OnNewConnection", (Type[])null, (Type[])null), (HarmonyMethod)null, Last("PeerConnected"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(Game), "UpdateRespawn", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Plugin), "CanSpawn", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch
			{
				harmony.UnpatchSelf();
				new Harmony("vercadi.CookingCompatibility.Recycler").UnpatchSelf();
				new Harmony("vercadi.CookingCompatibility.Drying").UnpatchSelf();
				new Harmony("vercadi.CookingCompatibility.CopperShader").UnpatchSelf();
				CopperShaderGuard.Clear();
				throw;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Cooking Compatibility 0.1.9 loaded. Waiting for scene and server settings.");
		}

		private static string Hash(Assembly assembly)
		{
			using FileStream inputStream = File.OpenRead(assembly.Location);
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "");
		}

		private static void RequireRunicPair(Assembly crafting, Assembly storage)
		{
			string text = Hash(crafting);
			string text2 = Hash(storage);
			bool flag = text == "1CC583A36ED94D035F5257D353C30E2284DAD6ADD00B89665FB1DBA756CBC0CF" && text2 == "A768CF66E2BF1A2BC6AF808A3845A36B29494176CB4A0CB59F8AF51803E86A15";
			bool flag2 = text == "3D98A550F9A160F796AD3BDA3E9C47DE2E26211FB1449F53AB511FF883D041B0" && text2 == "C2EEE73F58A2F1AF7C19092FBE02AAC5D58A39BB92CA305997929F6AD0ACFEE2";
			if (!flag && !flag2)
			{
				throw new NotSupportedException("Unreviewed RunicCrafting / RunicStorage pair");
			}
		}

		private static HarmonyMethod Last(string method)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			return new HarmonyMethod(typeof(Plugin), method, (Type[])null)
			{
				priority = 0
			};
		}

		private static bool CanSpawn()
		{
			if (Object.op_Implicit((Object)(object)instance))
			{
				return instance.ready;
			}
			return true;
		}

		private static void SceneReady(ZNetScene __instance)
		{
			if (Object.op_Implicit((Object)(object)instance))
			{
				instance.ClearScene();
				instance.currentScene = __instance;
				instance.setup = ((MonoBehaviour)instance).StartCoroutine(instance.PrepareScene(__instance));
			}
		}

		private static void SceneEnded(ZNetScene __instance)
		{
			if (Object.op_Implicit((Object)(object)instance) && (Object)(object)instance.currentScene == (Object)(object)__instance)
			{
				instance.ClearScene();
			}
		}

		private static void StationReady(CookingStation __instance)
		{
			if (Object.op_Implicit((Object)(object)instance))
			{
				instance.content?.ApplyStation(__instance);
			}
		}

		internal static bool Protected(ItemData data)
		{
			if (Object.op_Implicit((Object)(object)instance) && instance.content != null && Object.op_Implicit((Object)(object)data?.m_dropPrefab))
			{
				return instance.content.ProtectedNames.Contains(Rules.Prefab(((Object)data.m_dropPrefab).name));
			}
			return false;
		}

		private IEnumerator PrepareScene(ZNetScene scene)
		{
			yield return null;
			yield return null;
			float deadline = Time.realtimeSinceStartup + 120f;
			while ((Object)(object)currentScene == (Object)(object)scene && Time.realtimeSinceStartup < deadline && (!Object.op_Implicit((Object)(object)ObjectDB.instance) || !Object.op_Implicit((Object)(object)ObjectDB.instance.GetItemPrefab("Wood")) || !Object.op_Implicit((Object)(object)ZNet.instance) || (!ZNet.instance.IsServer() && (!((ConditionalConfigSync)sync).InitialSyncDone || ((ConditionalConfigSync)sync).IsSourceOfTruth || !Rules.ValidDigest(serverFingerprint.Value)))))
			{
				yield return (object)new WaitForSeconds(0.1f);
			}
			if ((Object)(object)currentScene != (Object)(object)scene)
			{
				yield break;
			}
			if (Time.realtimeSinceStartup >= deadline)
			{
				Fail("Timed out waiting for cooking content or server configuration.");
				yield break;
			}
			ZNet network = ZNet.instance;
			float contentDeadline = Time.realtimeSinceStartup + 20f;
			do
			{
				try
				{
					content?.Dispose();
					content = new ContentSession(ObjectDB.instance, scene);
					content.Prepare(supported, new HashSet<string>(from p in settings
						where p.Value.Value
						select p.Key, StringComparer.Ordinal), recyclingSafe);
				}
				catch (Exception ex)
				{
					Fail("Cooking setup failed: " + ex);
				}
				if (failed)
				{
					yield break;
				}
				if (network.IsServer() || serverFingerprint.Value == content.Fingerprint)
				{
					break;
				}
				yield return (object)new WaitForSeconds(0.5f);
				if ((Object)(object)currentScene != (Object)(object)scene || !Object.op_Implicit((Object)(object)network) || (Object)(object)ZNet.instance != (Object)(object)network)
				{
					yield break;
				}
			}
			while (Time.realtimeSinceStartup < contentDeadline);
			try
			{
				foreach (string item in content.Report)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)item);
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Resolved " + content.RecipeCount + " alternative recipes; fingerprint " + content.Fingerprint));
				content.ApplyExistingStations();
				if (network.IsServer())
				{
					serverFingerprint.AssignLocalValueAndNotify(content.Fingerprint);
					ready = true;
				}
				else if (serverFingerprint.Value != content.Fingerprint)
				{
					Fail("Cooking recipes differ from the server. Match optional food mods, versions and native recipe settings, then restart. Local " + content.Fingerprint + "; server " + serverFingerprint.Value);
				}
			}
			catch (Exception ex2)
			{
				Fail("Cooking setup failed: " + ex2);
			}
			if (!ready && !failed && !((Object)(object)currentScene != (Object)(object)scene) && Object.op_Implicit((Object)(object)network) && !((Object)(object)ZNet.instance != (Object)(object)network))
			{
				IEnumerator handshake = ClientHandshake.Run(CurrentSession, delegate
				{
					ZNetPeer serverPeer = network.GetServerPeer();
					return (serverPeer?.m_rpc == null) ? null : serverPeer;
				}, delegate(ZNetPeer peer)
				{
					awaitingAck = peer;
					peer.m_rpc.Invoke("vercadi.CookingCompatibility.Hello", new object[1] { content.Fingerprint });
				}, () => ready, () => Time.realtimeSinceStartup, () => (object)new WaitForSecondsRealtime(0.1f), Fail);
				while (handshake.MoveNext())
				{
					yield return handshake.Current;
				}
			}
			bool CurrentSession()
			{
				//IL_0044: Unknown result type (might be due to invalid IL or missing references)
				//IL_004a: Invalid comparison between Unknown and I4
				//IL_004c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0052: Invalid comparison between Unknown and I4
				if (!failed && (Object)(object)currentScene == (Object)(object)scene && Object.op_Implicit((Object)(object)network) && (Object)(object)ZNet.instance == (Object)(object)network)
				{
					if ((int)ZNet.GetConnectionStatus() != 1)
					{
						return (int)ZNet.GetConnectionStatus() == 2;
					}
					return true;
				}
				return false;
			}
		}

		private static void PeerConnected(ZNet __instance, ZNetPeer peer)
		{
			if (!Object.op_Implicit((Object)(object)instance))
			{
				return;
			}
			Plugin plugin = instance;
			if (__instance.IsServer())
			{
				peer.m_rpc.Register<string>("vercadi.CookingCompatibility.Hello", (Action<ZRpc, string>)delegate(ZRpc _, string value)
				{
					if (Rules.ValidDigest(value) && !plugin.handshakes.ContainsKey(peer))
					{
						plugin.handshakes[peer] = value;
					}
				});
				((MonoBehaviour)plugin).StartCoroutine(plugin.CheckPeer(__instance, peer));
				return;
			}
			peer.m_rpc.Register<string>("vercadi.CookingCompatibility.Ack", (Action<ZRpc, string>)delegate(ZRpc _, string value)
			{
				if (Object.op_Implicit((Object)(object)__instance) && (Object)(object)ZNet.instance == (Object)(object)__instance && plugin.awaitingAck == peer && __instance.GetServerPeer() == peer && !plugin.failed && plugin.content != null && Rules.ValidDigest(value) && value == plugin.content.Fingerprint && value == plugin.serverFingerprint.Value)
				{
					plugin.ready = true;
					((BaseUnityPlugin)plugin).Logger.LogInfo((object)"Cooking recipe handshake accepted by server.");
				}
			});
			peer.m_rpc.Register<string>("vercadi.CookingCompatibility.Error", (Action<ZRpc, string>)delegate(ZRpc _, string reason)
			{
				if (Object.op_Implicit((Object)(object)__instance) && (Object)(object)ZNet.instance == (Object)(object)__instance && __instance.GetPeers().Contains(peer))
				{
					plugin.Fail((reason != null && reason.Length <= 512) ? reason : "Cooking compatibility mismatch.");
				}
			});
		}

		private IEnumerator CheckPeer(ZNet network, ZNetPeer peer)
		{
			float deadline = Time.realtimeSinceStartup + 180f;
			while (Object.op_Implicit((Object)(object)network) && network.GetPeers().Contains(peer) && Time.realtimeSinceStartup < deadline)
			{
				if (ready && content != null && peer.IsReady() && handshakes.TryGetValue(peer, out var value))
				{
					if (value == content.Fingerprint)
					{
						accepted.Add(peer);
						peer.m_rpc.Invoke("vercadi.CookingCompatibility.Ack", new object[1] { value });
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Accepted matching cooking recipes from peer " + peer.m_uid));
					}
					else
					{
						Reject(network, peer, "Cooking recipe mismatch. Install the same supported food mods and native recipe settings as the server.");
					}
					handshakes.Remove(peer);
					yield break;
				}
				yield return (object)new WaitForSeconds(0.25f);
			}
			handshakes.Remove(peer);
			if (Object.op_Implicit((Object)(object)network) && network.GetPeers().Contains(peer) && !accepted.Contains(peer))
			{
				Reject(network, peer, "Cooking recipe handshake timed out. Check Cooking Compatibility and its dependencies, then restart.");
			}
		}

		private void Reject(ZNet network, ZNetPeer peer, string reason)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)reason);
			peer.m_rpc.Invoke("vercadi.CookingCompatibility.Error", new object[1] { reason });
			peer.m_rpc.Invoke("Error", new object[1] { 3 });
			network.Disconnect(peer);
		}

		private void Fail(string reason)
		{
			if (failed)
			{
				return;
			}
			failed = true;
			ready = false;
			((BaseUnityPlugin)this).Logger.LogError((object)reason);
			content?.Dispose();
			content = null;
			if (Object.op_Implicit((Object)(object)ZNet.instance) && !ZNet.instance.IsServer())
			{
				ZNet.SetExternalError((ConnectionStatus)3);
				ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
				if (serverPeer != null)
				{
					ZNet.instance.Disconnect(serverPeer);
				}
			}
		}

		private void ClearScene()
		{
			if (setup != null)
			{
				((MonoBehaviour)this).StopCoroutine(setup);
			}
			setup = null;
			ready = false;
			failed = false;
			awaitingAck = null;
			content?.Dispose();
			content = null;
			currentScene = null;
			handshakes.Clear();
			accepted.Clear();
			if (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer())
			{
				serverFingerprint.AssignLocalValueIfChanged("");
			}
		}

		private void OnDestroy()
		{
			//IL_001c: 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_003a: Unknown result type (might be due to invalid IL or missing references)
			ClearScene();
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			new Harmony("vercadi.CookingCompatibility.Recycler").UnpatchSelf();
			new Harmony("vercadi.CookingCompatibility.Drying").UnpatchSelf();
			new Harmony("vercadi.CookingCompatibility.CopperShader").UnpatchSelf();
			CopperShaderGuard.Clear();
			if ((Object)(object)instance == (Object)(object)this)
			{
				instance = null;
			}
		}
	}
	internal static class RecyclerAdapter
	{
		private static FieldInfo item;

		private static PropertyInfo recyclingImpediments;

		private static PropertyInfo displayImpediments;

		internal static void Install(Assembly assembly)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_0237: Expected O, but got Unknown
			//IL_024b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Unknown result type (might be due to invalid IL or missing references)
			//IL_0264: Expected O, but got Unknown
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0286: Expected O, but got Unknown
			Harmony val = new Harmony("vercadi.CookingCompatibility.Recycler");
			try
			{
				Type type = assembly.GetType("Recycle_N_Reclaim.GamePatches.Recycling.RecyclingAnalysisContext", throwOnError: true);
				Type type2 = assembly.GetType("Recycle_N_Reclaim.GamePatches.Recycling.Reclaimer", throwOnError: true);
				Type type3 = assembly.GetType("Recycle_N_Reclaim.GamePatches.Utils", throwOnError: true);
				item = type.GetField("Item");
				recyclingImpediments = type.GetProperty("RecyclingImpediments");
				displayImpediments = type.GetProperty("DisplayImpediments");
				if (item?.FieldType != typeof(ItemData) || recyclingImpediments?.PropertyType != typeof(List<string>) || displayImpediments?.PropertyType != typeof(List<string>))
				{
					throw new NotSupportedException("Recycler context contract changed.");
				}
				MethodInfo method = type2.GetMethod("TryAnalyzeOneItem", new Type[4]
				{
					type,
					typeof(Inventory),
					typeof(Player),
					typeof(bool)
				});
				MethodInfo method2 = type2.GetMethod("DoInventoryChanges", new Type[4]
				{
					type,
					typeof(Inventory),
					typeof(Player),
					typeof(bool)
				});
				MethodInfo method3 = type3.GetMethod("InventoryRecycleItem", new Type[5]
				{
					typeof(ItemData),
					typeof(int),
					typeof(Inventory),
					typeof(InventoryGui),
					typeof(GameObject)
				});
				if (method?.ReturnType != typeof(bool) || method2?.ReturnType != typeof(void) || method3?.ReturnType != typeof(void))
				{
					throw new NotSupportedException("Recycler entry point changed.");
				}
				val.Patch((MethodBase)method, new HarmonyMethod(typeof(RecyclerAdapter), "Analyze", (Type[])null)
				{
					priority = 800
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				val.Patch((MethodBase)method2, new HarmonyMethod(typeof(RecyclerAdapter), "Commit", (Type[])null)
				{
					priority = 800
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				val.Patch((MethodBase)method3, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(RecyclerAdapter), "NoFoodRefund", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch
			{
				val.UnpatchSelf();
				throw;
			}
		}

		private static bool Protected(object context)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			if (context != null)
			{
				return Plugin.Protected((ItemData)item.GetValue(context));
			}
			return false;
		}

		private static bool Analyze(object __0, ref bool __result)
		{
			if (!Protected(__0))
			{
				return true;
			}
			PropertyInfo[] array = new PropertyInfo[2] { recyclingImpediments, displayImpediments };
			for (int i = 0; i < array.Length; i++)
			{
				IList list = (IList)array[i].GetValue(__0, null);
				if (!list.Contains("Cooking Compatibility: alternative-recipe food cannot be reclaimed. Delete it from inventory without a refund if unwanted."))
				{
					list.Add("Cooking Compatibility: alternative-recipe food cannot be reclaimed. Delete it from inventory without a refund if unwanted.");
				}
			}
			__result = false;
			return false;
		}

		private static bool Commit(object __0)
		{
			return !Protected(__0);
		}

		private static Recipe RefundRecipe(ObjectDB db, ItemData data)
		{
			if (!Plugin.Protected(data))
			{
				return db.GetRecipe(data);
			}
			return null;
		}

		internal static IEnumerable<CodeInstruction> NoFoodRefund(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = instructions.ToList();
			MethodInfo target = typeof(ObjectDB).GetMethod("GetRecipe", new Type[1] { typeof(ItemData) });
			CodeInstruction[] array = list.Where((CodeInstruction c) => (c.opcode == OpCodes.Call || c.opcode == OpCodes.Callvirt) && object.Equals(c.operand, target)).ToArray();
			if (array.Length != 3)
			{
				throw new NotSupportedException("Recycler refund IL changed.");
			}
			CodeInstruction[] array2 = array;
			foreach (CodeInstruction obj in array2)
			{
				obj.opcode = OpCodes.Call;
				obj.operand = typeof(RecyclerAdapter).GetMethod("RefundRecipe", BindingFlags.Static | BindingFlags.NonPublic);
			}
			return list;
		}
	}
	internal sealed class RecipeRule
	{
		internal readonly string Id;

		internal readonly string Module;

		internal readonly string OtherMod;

		internal readonly string Output;

		internal readonly string From;

		internal readonly string To;

		internal readonly string Station;

		internal readonly string Finished;

		internal RecipeRule(string id, string module, string otherMod, string output, string from, string to, string station, string finished = null)
		{
			Id = id;
			Module = module;
			OtherMod = otherMod;
			Output = output;
			From = from;
			To = to;
			Station = station;
			Finished = finished;
		}
	}
	internal static class Rules
	{
		internal const string Cooking = "blacks7ar.CookingAdditions";

		internal const string Hares = "blacks7ar.OdinsHares";

		internal const string Bone = "com.rockerkitten.boneappetit";

		internal const string Ocean = "soloredis.rtdocean";

		internal const string Recycler = "Azumatt.Recycle_N_Reclaim";

		internal static readonly Dictionary<string, string> Hashes = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			["blacks7ar.CookingAdditions"] = "C5C62179CAFC3292869A0E3DF3BB50A5B06D8BE5879EB47BB039053252BEFDCF",
			["blacks7ar.OdinsHares"] = "3C5721B3FAFF63B406FC9C976B7F589B774566D7B2690F3827468ABC0DF30CE1",
			["com.rockerkitten.boneappetit"] = "A6063F311B2C6D05D70AAEBE509A0E2AE27708425C51BF8D867F57FA7BB4E809",
			["soloredis.rtdocean"] = "C7F08C7BABCD7BBDB72A83FBF0974C3AB7043CAD91F9BDBBE10792ED39D7E866",
			["Azumatt.Recycle_N_Reclaim"] = "362E0A417C28CF4AC1AFE6DB8A6FE35EA8DAF7FF6DC5B71690B06B177C9D21FC"
		};

		internal static readonly string[] Modules = new string[5] { "Hare grilling", "Pork curing", "Hare curing", "Ocean fish curing", "Tomato meals" };

		internal static readonly RecipeRule[] Recipes = CreateRules();

		internal static bool DefaultEnabled(string module)
		{
			return module != "Hare curing";
		}

		private static RecipeRule[] CreateRules()
		{
			List<RecipeRule> list = new List<RecipeRule>
			{
				new RecipeRule("pork", "Pork curing", "com.rockerkitten.boneappetit", "BCA_Undryed_RawMeat", "RawMeat", "rk_pork", "piece_preptable", "BCA_Salted_BoarMeat"),
				new RecipeRule("hare", "Hare curing", "blacks7ar.OdinsHares", "BCA_Undryed_HareMeat", "HareMeat", "BOH_RawHare_Meat", "piece_preptable", "BCA_Salted_HareMeat")
			};
			string[] array = new string[3] { "Cod", "Tuna", "Marlin" };
			foreach (string text in array)
			{
				list.Add(new RecipeRule(text.ToLowerInvariant(), "Ocean fish curing", "soloredis.rtdocean", "BCA_Undryed_Fish", "FishRaw", "Meat_" + text + "_RtD", "piece_preptable", "BCA_Salted_Fish"));
			}
			array = new string[5] { "BCA_FishSoup", "BCA_SpicyMeatSupreme", "BCA_SpicySerpentMeat", "BCA_SweetNSpicyFish", "BCA_TomatoSoup" };
			foreach (string text2 in array)
			{
				list.Add(new RecipeRule("tomato_" + text2, "Tomato meals", "soloredis.rtdocean", text2, "BCA_Tomato", "Vegetable_Tomato_RtD", "BCA_CookingPot"));
			}
			array = new string[5] { "Item_Soup_Tomato_RtD", "Item_Stew_Tomato_RtD", "Item_Sushi_Cod_RtD", "Item_Soup_Corn_RtD", "Item_Soup_Pumpkin_RtD" };
			foreach (string text3 in array)
			{
				list.Add(new RecipeRule("tomato_" + text3, "Tomato meals", "soloredis.rtdocean", text3, "Vegetable_Tomato_RtD", "BCA_Tomato", "Piece_Prep_Table_RtD"));
			}
			return list.ToArray();
		}

		internal static string Unavailable(RecipeRule rule, ISet<string> supported, ISet<string> enabled, bool recyclingSafe)
		{
			if (!enabled.Contains(rule.Module))
			{
				return "disabled";
			}
			if (!supported.Contains("blacks7ar.CookingAdditions") || !supported.Contains(rule.OtherMod))
			{
				return "missing or unsupported optional dependency";
			}
			if (!recyclingSafe)
			{
				return "unsupported recycler: alternative recipes withheld";
			}
			return null;
		}

		internal static bool ValidCosts(string[] items, int[] amounts, string from, bool curing)
		{
			if (items == null || amounts == null || items.Length != amounts.Length || items.Length < 1 || items.Length > 32)
			{
				return false;
			}
			if (items.Any(string.IsNullOrEmpty) || amounts.Any((int n) => n <= 0 || n > 10000) || items.Distinct<string>(StringComparer.Ordinal).Count() != items.Length)
			{
				return false;
			}
			if (items.Count((string i) => i == from) == 1)
			{
				if (curing)
				{
					if (items.Length == 2)
					{
						return items.Contains("BCA_Salt");
					}
					return false;
				}
				return true;
			}
			return false;
		}

		internal static string Digest(IEnumerable<string> rows)
		{
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(string.Join("\n", rows.OrderBy<string, string>((string s) => s, StringComparer.Ordinal))))).Replace("-", "");
		}

		internal static bool ValidDigest(string text)
		{
			if (text != null && text.Length == 64)
			{
				return text.All((char c) => (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'));
			}
			return false;
		}

		internal static string Prefab(string name)
		{
			if (name == null || !name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				return name;
			}
			return name.Substring(0, name.Length - 7);
		}
	}
}
namespace Vercadi.RunicEnchantingCompatibility
{
	internal sealed class BackpackAccess
	{
		internal sealed class Endpoint
		{
			private readonly BackpackAccess access;

			private readonly Player player;

			private readonly object bag;

			internal readonly Inventory Inventory;

			internal readonly ItemData Parent;

			internal readonly string Key;

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

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

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

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

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

		internal const string Hash = "9F0B0FD4D1FE3B1406ECD39BEAA166C2E36F26CC012EA63812732F62AFA5BDA0";

		internal const string PreviousHash = "5773E28A4E8A7F27B839F1FBC4AB2272C5349E7E38D671F683950E30D47D4541";

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

		private readonly MethodInfo canCraft;

		private readonly MethodInfo equipped;

		private readonly MethodInfo getInventory;

		private readonly MethodInfo save;

		private readonly MethodInfo itemData;

		private readonly MethodInfo getComponent;

		private readonly FieldInfo open;

		private readonly FieldInfo loading;

		private readonly FieldInfo emptying;

		private readonly PropertyInfo alive;

		private readonly PropertyInfo item;

		private readonly PropertyInfo key;

		private readonly PropertyInfo value;

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

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

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

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

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

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

		internal readonly string Resource;

		internal readonly int Amount;

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

		internal static List<Cost> Missing(IEnumerable<Cost> costs, Func<string, int> carried)
		{
			return (from c in costs
				select new Cost(c.Name, c.Resource, Math.Max(0, c.Amount - Math.Max(0, carried(c.Name)))) into c
				where c.Amount > 0
				select c).ToList();
		}
	}
	internal sealed class PlayerSlotPolicy
	{
		internal const string Id = "shudnal.ExtraSlots";

		internal const string Hash = "389A9DFC6D36BB8897D7B0991AA1E02B37DD55A95D944DF0EB7637954B79322B";

		private readonly MethodInfo slot;

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

		internal bool Ordinary(int x, int y)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			return slot.Invoke(null, new object[1] { (object)new Vector2i(x, y) }) == null;
		}
	}
	internal sealed class RunicBridge
	{
		private sealed class Source
		{
			internal Container Chest;

			internal Inventory Inventory;

			internal List<ItemData> Items;

			internal BackpackAccess.Endpoint Bag;
		}

		private sealed class Line
		{
			internal Source Source;

			internal ItemData Item;

			internal int Quantity;
		}

		private sealed class TransferEndpoint
		{
			internal Inventory Live;

			internal Inventory Shadow;

			internal object Before;

			internal Func<bool> Authority;

			internal Source Source;

			internal string Name;
		}

		private sealed class NoCapacityException : Exception
		{
		}

		private readonly MethodInfo query;

		private readonly MethodInfo eligible;

		private readonly MethodInfo readPreview;

		private readonly MethodInfo previewCount;

		private readonly MethodInfo resourceId;

		private readonly MethodInfo begin;

		private readonly MethodInfo acquire;

		private readonly MethodInfo move;

		private readonly MethodInfo restore;

		private readonly MethodInfo save;

		private readonly MethodInfo protect;

		private readonly MethodInfo stateAt;

		private readonly MethodInfo isBlocked;

		private readonly MethodInfo block;

		private readonly MethodInfo captureAuthority;

		private readonly MethodInfo sameItem;

		private readonly ConstructorInfo snapshot;

		private BackpackAccess backpack;

		private readonly MethodInfo shadow;

		private readonly PropertyInfo range;

		private readonly PropertyInfo candidates;

		private readonly PropertyInfo returned;

		private readonly PropertyInfo initialized;

		private readonly PropertyInfo enabled;

		private readonly PropertyInfo crafting;

		private readonly PropertyInfo wardAccess;

		private readonly PropertyInfo storageEnabled;

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

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

		private List<TransferEndpoint> recoveryEndpoints;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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