Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CookingCompatibility v0.1.9
plugins/CookingCompatibility/CookingCompatibility.dll
Decompiled 13 hours agousing 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(); } } } }