using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Timers; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.RepresentationModel; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("FineDining")] [assembly: AssemblyDescription("Integrated food spoilage, diet, cooking, and station guidance for Valheim.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("FineDining")] [assembly: AssemblyCopyright("Copyright © sighsorry 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("FDE6E067-900F-4380-9530-A1EF8D16E7B8")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 FineDining { internal static class AzuExtendedPlayerInventoryCompatibility { private readonly struct DirectMergeSite { internal int TargetLoadIndex { get; } internal int AmountLoadIndex { get; } internal int SourceLoadIndex { get; } internal int SourceLoadCount { get; } internal int InsertIndex { get; } internal DirectMergeSite(int targetLoadIndex, int amountLoadIndex, int sourceLoadIndex, int sourceLoadCount, int insertIndex) { TargetLoadIndex = targetLoadIndex; AmountLoadIndex = amountLoadIndex; SourceLoadIndex = sourceLoadIndex; SourceLoadCount = sourceLoadCount; InsertIndex = insertIndex; } } internal const string PluginGuid = "Azumatt.AzuExtendedPlayerInventory"; private const string RecoveryPatchTypeName = "AzuEPI.Game.Patches.InventoryPatches+Load_TrackAndFixHiddenItems_Patch"; private static readonly FieldInfo StackField = AccessTools.Field(typeof(ItemData), "m_stack"); private static readonly MethodInfo ComposeMethod = AccessTools.Method(typeof(AzuExtendedPlayerInventoryCompatibility), "ComposeDirectRecoveryMergeExpirySafe", (Type[])null, (Type[])null); private static int _matchedMergeSites; private static bool _runtimeFailureLogged; internal static void TryInstall(Harmony harmony) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown if (!Chainloader.PluginInfos.TryGetValue("Azumatt.AzuExtendedPlayerInventory", out var value)) { return; } MethodInfo methodInfo = null; bool flag = false; try { methodInfo = ResolveTargetMethod(value); if (methodInfo == null) { FineDiningPlugin.Log.LogWarning((object)"AzuExtendedPlayerInventory is installed, but its hidden-slot recovery method no longer has the expected signature. Direct recovery merges will not compose spoilage timers."); return; } Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo); if (patchInfo == null || !patchInfo.Transpilers.Any((Patch patch) => string.Equals(patch.owner, harmony.Id, StringComparison.Ordinal) && object.Equals(patch.PatchMethod, AccessTools.Method(typeof(AzuExtendedPlayerInventoryCompatibility), "Transpiler", (Type[])null, (Type[])null)))) { _matchedMergeSites = 0; HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(AzuExtendedPlayerInventoryCompatibility), "Transpiler", (Type[])null, (Type[])null)); flag = true; harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null); if (_matchedMergeSites != 1) { harmony.Unpatch((MethodBase)methodInfo, (HarmonyPatchType)3, harmony.Id); FineDiningPlugin.Log.LogWarning((object)"AzuExtendedPlayerInventory is installed, but its direct recovery merge no longer matches the verified pattern. The compatibility patch was left disabled so inventory loading remains unchanged."); } else { FineDiningPlugin.Log.LogInfo((object)("Enabled AzuExtendedPlayerInventory " + value.Metadata.Version?.ToString() + " hidden-slot merge timer compatibility.")); } } } catch (Exception ex) { if (flag && methodInfo != null) { try { harmony.Unpatch((MethodBase)methodInfo, (HarmonyPatchType)3, harmony.Id); } catch { } } FineDiningPlugin.Log.LogWarning((object)("Could not enable AzuExtendedPlayerInventory merge timer compatibility. " + ex)); } } internal static void Shutdown() { _matchedMergeSites = 0; _runtimeFailureLogged = false; } private static MethodInfo? ResolveTargetMethod(PluginInfo pluginInfo) { MethodInfo methodInfo = ((((object)pluginInfo.Instance)?.GetType().Assembly)?.GetType("AzuEPI.Game.Patches.InventoryPatches+Load_TrackAndFixHiddenItems_Patch", throwOnError: false))?.GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic); if (methodInfo == null || methodInfo.ReturnType != typeof(void)) { return null; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length != 1 || !(parameters[0].ParameterType == typeof(Inventory))) { return null; } return methodInfo; } private static IEnumerable Transpiler(IEnumerable instructions) { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown List list = instructions.ToList(); List list2 = new List(); for (int i = 5; i < list.Count; i++) { if (TryMatchDirectMerge(list, i, out var site)) { list2.Add(site); } } _matchedMergeSites = list2.Count; if (list2.Count != 1 || ComposeMethod == null) { if (ComposeMethod == null) { _matchedMergeSites = 0; } return list; } DirectMergeSite directMergeSite = list2[0]; List list3 = new List { CloneWithoutFlowMetadata(list[directMergeSite.TargetLoadIndex]) }; for (int j = 0; j < directMergeSite.SourceLoadCount; j++) { list3.Add(CloneWithoutFlowMetadata(list[directMergeSite.SourceLoadIndex + j])); } list3.Add(CloneWithoutFlowMetadata(list[directMergeSite.AmountLoadIndex])); list3.Add(new CodeInstruction(OpCodes.Call, (object)ComposeMethod)); list.InsertRange(directMergeSite.InsertIndex, list3); return list; } private static bool TryMatchDirectMerge(IReadOnlyList code, int targetStoreIndex, out DirectMergeSite site) { site = default(DirectMergeSite); int num = targetStoreIndex - 5; if (!IsLocalLoad(code[num]) || code[num + 1].opcode != OpCodes.Dup || !IsFieldLoad(code[num + 2], StackField) || !IsLocalLoad(code[num + 3]) || code[num + 4].opcode != OpCodes.Add || !IsFieldStore(code[targetStoreIndex], StackField)) { return false; } int num2 = targetStoreIndex + 1; int sourceLoadCount; int num3; if (num2 + 5 < code.Count && IsLocalLoad(code[num2]) && code[num2 + 1].opcode == OpCodes.Dup) { sourceLoadCount = 1; num3 = num2 + 1; } else { if (num2 + 6 >= code.Count || !IsLocalLoad(code[num2]) || !IsItemDataFieldLoad(code[num2 + 1]) || !(code[num2 + 2].opcode == OpCodes.Dup)) { return false; } sourceLoadCount = 2; num3 = num2 + 2; } int index = num3 + 1; int index2 = num3 + 2; int index3 = num3 + 3; int num4 = num3 + 4; if (!IsFieldLoad(code[index], StackField) || !IsLocalLoad(code[index2]) || !AreSameLocalLoads(code[num + 3], code[index2]) || code[index3].opcode != OpCodes.Sub || !IsFieldStore(code[num4], StackField)) { return false; } site = new DirectMergeSite(num, num + 3, num2, sourceLoadCount, num4 + 1); return true; } private static void ComposeDirectRecoveryMergeExpirySafe(ItemData? target, ItemData? source, int movedAmount) { try { DecayRuntime.ComposeDirectRecoveryMergeExpiry(target, source, movedAmount); } catch (Exception ex) { if (!_runtimeFailureLogged) { _runtimeFailureLogged = true; FineDiningPlugin.Log.LogWarning((object)("AzuExtendedPlayerInventory merged a recovered stack, but FineDining could not compose its expiry timer. The original inventory merge will continue unchanged. " + ex)); } } } private static CodeInstruction CloneWithoutFlowMetadata(CodeInstruction instruction) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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_001d: Expected O, but got Unknown CodeInstruction val = new CodeInstruction(instruction); val.labels.Clear(); val.blocks.Clear(); return val; } private static bool IsItemDataFieldLoad(CodeInstruction instruction) { if (instruction.opcode == OpCodes.Ldfld && instruction.operand is FieldInfo fieldInfo) { return fieldInfo.FieldType == typeof(ItemData); } return false; } private static bool IsFieldLoad(CodeInstruction instruction, FieldInfo field) { if (instruction.opcode == OpCodes.Ldfld) { return object.Equals(instruction.operand, field); } return false; } private static bool IsFieldStore(CodeInstruction instruction, FieldInfo field) { if (instruction.opcode == OpCodes.Stfld) { return object.Equals(instruction.operand, field); } return false; } private static bool IsLocalLoad(CodeInstruction instruction) { if (!(instruction.opcode == OpCodes.Ldloc) && !(instruction.opcode == OpCodes.Ldloc_S) && !(instruction.opcode == OpCodes.Ldloc_0) && !(instruction.opcode == OpCodes.Ldloc_1) && !(instruction.opcode == OpCodes.Ldloc_2)) { return instruction.opcode == OpCodes.Ldloc_3; } return true; } private static bool AreSameLocalLoads(CodeInstruction first, CodeInstruction second) { if (first.opcode != second.opcode) { return false; } if (!(first.opcode == OpCodes.Ldloc_0) && !(first.opcode == OpCodes.Ldloc_1) && !(first.opcode == OpCodes.Ldloc_2) && !(first.opcode == OpCodes.Ldloc_3)) { return object.Equals(first.operand, second.operand); } return true; } } internal static class ConfigPresentation { internal readonly struct SectionDefinition { internal string Name { get; } internal int CategoryOrder { get; } internal SectionDefinition(string name, int categoryOrder) { Name = name; CategoryOrder = categoryOrder; } } internal static readonly SectionDefinition General = new SectionDefinition("1 - General", 500); internal static readonly SectionDefinition ClientSection = new SectionDefinition("2 - Client", 400); internal static readonly SectionDefinition Diet = new SectionDefinition("3 - Diet", 300); internal static readonly SectionDefinition ChefChoice = new SectionDefinition("4 - Chef Choice", 200); internal static readonly SectionDefinition Spoilage = new SectionDefinition("5 - Spoilage", 100); internal static ConfigDescription Synced(string description, SectionDefinition section, int order, AcceptableValueBase? acceptableValues = null) { return Create(description, "[Synced with Server]", section, order, acceptableValues); } internal static ConfigDescription Client(string description, SectionDefinition section, int order, AcceptableValueBase? acceptableValues = null) { return Create(description, "[Client Only]", section, order, acceptableValues); } private static ConfigDescription Create(string description, string scope, SectionDefinition section, int order, AcceptableValueBase? acceptableValues) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown return new ConfigDescription(description + " " + scope, acceptableValues, new object[1] { new ConfigurationManagerAttributes { CategoryOrder = section.CategoryOrder, Order = order } }); } } internal sealed class ConfigurationManagerAttributes { public int? CategoryOrder { get; set; } public int? Order { get; set; } } internal enum FoodStatAxis { None, Health, Stamina, Eitr } internal static class FoodIdentity { internal static string GetCanonicalPrefabName(ItemData? item) { if (item == null) { return string.Empty; } if ((Object)(object)item.m_dropPrefab != (Object)null) { return NormalizePrefabName(((Object)item.m_dropPrefab).name); } ObjectDB instance = ObjectDB.instance; GameObject val = default(GameObject); if ((Object)(object)instance != (Object)null && item.m_shared != null && instance.TryGetItemPrefab(item.m_shared, ref val) && (Object)(object)val != (Object)null) { return NormalizePrefabName(((Object)val).name); } return string.Empty; } internal static string GetCanonicalPrefabName(Food? food) { if (food == null) { return string.Empty; } string text = NormalizePrefabName(food.m_name); if (text.Length <= 0) { return GetCanonicalPrefabName(food.m_item); } return text; } internal static bool IsDirectlyEdible(ItemData? item) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 SharedData val = item?.m_shared; if (val != null && (int)val.m_itemType == 2) { return HasChefFoodStats(val.m_food, val.m_foodStamina, val.m_foodEitr); } return false; } internal static bool IsDirectlyEdible(Food? food) { return IsDirectlyEdible(food?.m_item); } internal static bool TryGetFoodStatAxis(float health, float stamina, float eitr, out FoodStatAxis axis) { axis = FoodStatAxis.None; if (!HasChefFoodStats(health, stamina, eitr)) { return false; } if (eitr > 0f) { axis = FoodStatAxis.Eitr; return true; } axis = ((!(health <= stamina)) ? FoodStatAxis.Health : FoodStatAxis.Stamina); return true; } internal static bool TryGetFoodStatAxis(ItemData? item, out FoodStatAxis axis) { SharedData val = item?.m_shared; if (val == null || !IsDirectlyEdible(item)) { axis = FoodStatAxis.None; return false; } return TryGetFoodStatAxis(val.m_food, val.m_foodStamina, val.m_foodEitr, out axis); } internal static bool HasChefFoodStats(float health, float stamina, float eitr) { if (!(health > 0f) && !(stamina > 0f)) { return eitr > 0f; } return true; } internal static string NormalizePrefabName(string? name) { if (string.IsNullOrWhiteSpace(name)) { return string.Empty; } return Utils.GetPrefabName(name.Trim()).Trim(); } } [HarmonyPatch(typeof(ItemDrop), "SlowUpdate")] internal static class ItemDropSlowUpdateSpoilageSafetyPatch { private static void Postfix(ItemDrop __instance) { DecayRuntime.RefreshOwnedPlacedDrop(__instance); } } [HarmonyPatch(typeof(ItemDrop), "Load")] internal static class ItemDropLoadSpoilagePatch { private static void Postfix(ItemDrop __instance) { DecayRuntime.RegisterGroundDrop(__instance); } } [HarmonyPatch(typeof(ItemDrop), "LoadFromExternalZDO")] internal static class ItemDropExternalLoadSpoilagePatch { private static void Postfix(ItemDrop __instance) { DecayRuntime.RegisterGroundDrop(__instance); } } [HarmonyPatch(typeof(ItemDrop), "Save")] internal static class ItemDropSavedSpoilagePatch { private static void Postfix(ItemDrop __instance) { DecayRuntime.RegisterGroundDrop(__instance); } } [HarmonyPatch(typeof(ItemDrop), "OnDestroy")] internal static class ItemDropDestroyedSpoilagePatch { private static void Prefix(ItemDrop __instance) { DecayRuntime.UnregisterGroundDrop(__instance); } } [HarmonyPatch(typeof(Container), "Awake")] internal static class ContainerAwakeSpoilagePatch { private static void Prefix(Container __instance) { IceboxSubsystem.ApplyConfiguredStorageSize(__instance); } private static void Postfix(Container __instance) { IceboxSubsystem.ApplyConfiguredStorageSize(__instance); IceboxSubsystem.ApplyStoredRecipe(__instance); DecayRuntime.ContainerLoaded(__instance); } } [HarmonyPatch(typeof(Container), "Load")] internal static class ContainerLoadSpoilagePatch { private static void Prefix(Container __instance, out bool __state) { __state = IceboxSubsystem.PrepareStorageLoad(__instance); DecayRuntime.RegisterContainer(__instance); } private static void Postfix(Container __instance, bool __result, bool __state) { if (__state) { IceboxSubsystem.ApplyConfiguredStorageSize(__instance); } if (__result) { DecayRuntime.ContainerLoaded(__instance); } } } [HarmonyPatch(typeof(Inventory), "Changed")] internal static class InventoryChangedSpoilagePatch { private static void Postfix(Inventory __instance) { DecayRuntime.MarkDirty(__instance); IceboxSubsystem.HandleInventoryChanged(__instance); } } [HarmonyPatch(typeof(Player), "Save", new Type[] { typeof(ZPackage) })] internal static class PlayerSaveSpoilageClockPatch { private static void Prefix(Player __instance, out List> __state) { __state = DecayRuntime.BeginPlayerSaveClockSnapshot(__instance); } private static Exception? Finalizer(List> __state, Exception? __exception) { DecayRuntime.EndPlayerSaveClockSnapshot(__state); return __exception; } } [HarmonyPatch(typeof(ItemDrop), "AutoStackItems")] internal static class ItemDropAutoStackSpoilagePatch { private static readonly FieldInfo ItemDataField = AccessTools.Field(typeof(ItemDrop), "m_itemData"); private static readonly FieldInfo StackField = AccessTools.Field(typeof(ItemData), "m_stack"); private static readonly MethodInfo ComposeMethod = AccessTools.Method(typeof(DecayRuntime), "ComposeGroundStackExpiry", (Type[])null, (Type[])null); private static IEnumerable Transpiler(IEnumerable instructions) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Expected O, but got Unknown //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Expected O, but got Unknown List list = new List(instructions); List list2 = new List(); for (int i = 8; i < list.Count; i++) { if (IsGroundStackIncrement(list, i)) { list2.Add(i - 8); } } if (list2.Count != 1 || ComposeMethod == null) { FineDiningPlugin.Log.LogWarning((object)("Could not safely patch ItemDrop.AutoStackItems expiry merge; expected one stack increment but found " + list2.Count + ". Ground-stack timers may not compose on this game version.")); return list; } int num = list2[0]; CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(list[num].labels); list[num].labels.Clear(); val.blocks.AddRange(list[num].blocks); list[num].blocks.Clear(); CodeInstruction val2 = new CodeInstruction(list[num + 4]); val2.labels.Clear(); val2.blocks.Clear(); list.InsertRange(num, (IEnumerable)(object)new CodeInstruction[3] { val, val2, new CodeInstruction(OpCodes.Call, (object)ComposeMethod) }); return list; } private static bool IsGroundStackIncrement(IReadOnlyList code, int storeIndex) { int num = storeIndex - 8; if (code[storeIndex].opcode == OpCodes.Stfld && object.Equals(code[storeIndex].operand, StackField) && code[num].opcode == OpCodes.Ldarg_0 && IsFieldLoad(code[num + 1], ItemDataField) && code[num + 2].opcode == OpCodes.Dup && IsFieldLoad(code[num + 3], StackField) && IsLocalLoad(code[num + 4]) && IsFieldLoad(code[num + 5], ItemDataField) && IsFieldLoad(code[num + 6], StackField)) { return code[num + 7].opcode == OpCodes.Add; } return false; } private static bool IsFieldLoad(CodeInstruction instruction, FieldInfo field) { if (instruction.opcode == OpCodes.Ldfld) { return object.Equals(instruction.operand, field); } return false; } private static bool IsLocalLoad(CodeInstruction instruction) { if (!(instruction.opcode == OpCodes.Ldloc) && !(instruction.opcode == OpCodes.Ldloc_S) && !(instruction.opcode == OpCodes.Ldloc_0) && !(instruction.opcode == OpCodes.Ldloc_1) && !(instruction.opcode == OpCodes.Ldloc_2)) { return instruction.opcode == OpCodes.Ldloc_3; } return true; } } internal static class SpoilageContentLifecycle { internal static void Refresh() { GeneratedPrefabRegistry.RefreshConfiguredContent(); FoodClassifier.Invalidate(); DietModule.InvalidateChefTierCatalog(); DecayRuntime.InvalidateAll(); } } [HarmonyPatch(typeof(ObjectDB), "UpdateRegisters")] internal static class ObjectDbUpdateRegistersSpoilagePatch { [HarmonyPriority(0)] private static void Postfix(ObjectDB __instance) { SpoilageContentLifecycle.Refresh(); } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] internal static class ZNetDestroySpoilagePatch { private static void Postfix() { DecayRuntime.Reset(); FoodClassifier.Invalidate(); } } internal sealed class InventoryAddMergeState { internal readonly Dictionary PreviousStacks = new Dictionary(); internal readonly Dictionary PreviousLifetimes = new Dictionary(); internal bool SourceHadValidClock; internal bool SourceHadExpiryKey; internal string? OriginalSourceExpiryValue; internal bool SourceHadLifetimeKey; internal string? OriginalSourceLifetimeValue; internal long SourceClockValue; internal AssignedLifetimeSnapshot SourceLifetime; } internal static class InventoryAddMergeTracker { private static bool _loggedTemporarySourceRestoreFailure; internal static InventoryAddMergeState? Prefix(Inventory inventory, ItemData? source) { if (source == null || DecayRuntime.IsContainerLoading(inventory) || !DecayRuntime.IsAuthoritativeInventory(inventory)) { return null; } InventoryAddMergeState inventoryAddMergeState = new InventoryAddMergeState(); CaptureOriginalSourceMetadata(source, inventoryAddMergeState); DecayRuntime.PrepareItemForAdd(inventory, source); PieceRecoverySpoilageTracker.ApplyToInventoryItem(inventory, source); inventoryAddMergeState.SourceLifetime = FreshnessRuntime.CaptureAssignedLifetime(source); if (!DecayRuntime.TryGetExpiryTicks(source, out inventoryAddMergeState.SourceClockValue)) { return null; } foreach (ItemData item in inventory.m_inventory) { if (item != null && CanPotentiallyStack(item, source)) { inventoryAddMergeState.PreviousStacks[item] = item.m_stack; inventoryAddMergeState.PreviousLifetimes[item] = FreshnessRuntime.CaptureAssignedLifetime(item); } } return inventoryAddMergeState; } internal static void Postfix(Inventory inventory, ItemData? source, InventoryAddMergeState? state) { if (state == null || source == null) { return; } bool flag = false; try { if (state.SourceClockValue != 0L) { foreach (KeyValuePair previousStack in state.PreviousStacks) { ItemData key = previousStack.Key; if (key != source && key.m_stack > previousStack.Value) { flag |= DecayRuntime.ComposeInventoryStackMetadata(inventory, key, state.SourceClockValue, state.PreviousLifetimes[key], state.SourceLifetime); } } } } finally { RestoreTemporarySourceMetadataSafe(inventory, source, state); } if (flag) { inventory.Changed(); } } internal static void RestoreTemporarySourceMetadataSafe(Inventory inventory, ItemData? source, InventoryAddMergeState? state) { if (source == null || state == null) { return; } try { RestoreTemporarySourceMetadata(inventory, source, state); } catch (Exception ex) { if (!_loggedTemporarySourceRestoreFailure) { _loggedTemporarySourceRestoreFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not restore a temporarily activated source stack after Inventory.AddItem: " + ex)); } } } private static void CaptureOriginalSourceMetadata(ItemData source, InventoryAddMergeState state) { state.SourceHadValidClock = DecayRuntime.TryGetExpiryTicks(source, out var _); if (source.m_customData != null) { state.SourceHadExpiryKey = source.m_customData.TryGetValue("sighsorry.FineDining.ExpiryWorldTicks", out state.OriginalSourceExpiryValue); state.SourceHadLifetimeKey = source.m_customData.TryGetValue("sighsorry.FineDining.AssignedLifetimeTicks", out state.OriginalSourceLifetimeValue); } } private static void RestoreTemporarySourceMetadata(Inventory inventory, ItemData source, InventoryAddMergeState state) { if (!state.SourceHadValidClock && !inventory.m_inventory.Contains(source)) { if (source.m_customData == null) { source.m_customData = new Dictionary(); } RestoreKey(source.m_customData, "sighsorry.FineDining.ExpiryWorldTicks", state.SourceHadExpiryKey, state.OriginalSourceExpiryValue); RestoreKey(source.m_customData, "sighsorry.FineDining.AssignedLifetimeTicks", state.SourceHadLifetimeKey, state.OriginalSourceLifetimeValue); } } private static void RestoreKey(Dictionary customData, string key, bool hadKey, string? originalValue) { if (hadKey) { customData[key] = originalValue ?? string.Empty; } else { customData.Remove(key); } } private static bool CanPotentiallyStack(ItemData existing, ItemData source) { if (existing.m_shared == null || source.m_shared == null) { return false; } if (existing.m_shared.m_name == source.m_shared.m_name && existing.m_worldLevel == source.m_worldLevel) { if (existing.m_shared.m_maxQuality > 1) { return existing.m_quality == source.m_quality; } return true; } return false; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData) })] internal static class InventoryAddItemSpoilagePatch { [HarmonyPrefix] [HarmonyPriority(800)] [HarmonyBefore(new string[] { "sighsorry.InventorySlots" })] private static void Prefix(Inventory __instance, ItemData item, out InventoryAddMergeState? __state) { __state = InventoryAddMergeTracker.Prefix(__instance, item); } [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "sighsorry.InventorySlots" })] private static void Postfix(Inventory __instance, ItemData item, InventoryAddMergeState? __state) { InventoryAddMergeTracker.Postfix(__instance, item, __state); } private static Exception? Finalizer(Inventory __instance, ItemData item, InventoryAddMergeState? __state, Exception? __exception) { InventoryAddMergeTracker.RestoreTemporarySourceMetadataSafe(__instance, item, __state); return __exception; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData), typeof(Vector2i) })] internal static class InventoryAddItemAtPositionSpoilagePatch { [HarmonyPrefix] [HarmonyPriority(800)] [HarmonyBefore(new string[] { "sighsorry.InventorySlots" })] private static void Prefix(Inventory __instance, ItemData item, out InventoryAddMergeState? __state) { __state = InventoryAddMergeTracker.Prefix(__instance, item); } [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "sighsorry.InventorySlots" })] private static void Postfix(Inventory __instance, ItemData item, InventoryAddMergeState? __state) { InventoryAddMergeTracker.Postfix(__instance, item, __state); } private static Exception? Finalizer(Inventory __instance, ItemData item, InventoryAddMergeState? __state, Exception? __exception) { InventoryAddMergeTracker.RestoreTemporarySourceMetadataSafe(__instance, item, __state); return __exception; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData), typeof(int), typeof(int), typeof(int) })] internal static class InventoryAddItemAmountAtPositionSpoilagePatch { [HarmonyPrefix] [HarmonyPriority(800)] [HarmonyBefore(new string[] { "sighsorry.InventorySlots" })] private static void Prefix(Inventory __instance, ItemData item, out InventoryAddMergeState? __state) { __state = InventoryAddMergeTracker.Prefix(__instance, item); } [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "sighsorry.InventorySlots" })] private static void Postfix(Inventory __instance, ItemData item, InventoryAddMergeState? __state) { InventoryAddMergeTracker.Postfix(__instance, item, __state); } private static Exception? Finalizer(Inventory __instance, ItemData item, InventoryAddMergeState? __state, Exception? __exception) { InventoryAddMergeTracker.RestoreTemporarySourceMetadataSafe(__instance, item, __state); return __exception; } } internal static class DecayRuntime { private sealed class InventoryState { internal bool Dirty = true; internal bool Reconciling; internal bool SuppressDirty; internal bool PendingNotification; internal bool EnvironmentKnown; internal bool PausedByCold; internal bool BiomeSampleKnown; internal bool SampledBiomePaused; internal Vector3 SampledBiomePosition; internal float NextVisiblePreparationAt; internal long NextExpiryTicks = long.MaxValue; } private sealed class GroundEnvironmentSample { internal bool Paused; internal Vector3 Position; } private enum PreservationState { Unknown, Running, Paused } private enum ReplacementResolution { NotReady, Invalid, Ready } internal const string ExpiryDataKey = "sighsorry.FineDining.ExpiryWorldTicks"; internal const string PlacedAnchorDataKey = "sighsorry.FineDining.PlacedWorldTicks"; private static readonly Dictionary InventoryStates = new Dictionary(); private static readonly Dictionary> ContainersByInventory = new Dictionary>(); private static readonly Dictionary> GroundDropsByInstanceId = new Dictionary>(); private static readonly Dictionary GroundEnvironmentSamples = new Dictionary(); private static readonly List>> GroundDropSnapshot = new List>>(); private static readonly HashSet LoggedReplacementWarnings = new HashSet(StringComparer.OrdinalIgnoreCase); private static float _nextTickAt; private static bool _loggedFirstGroundLoadFailure; internal static void Tick() { if (Time.unscaledTime < _nextTickAt) { return; } _nextTickAt = Time.unscaledTime + 1f; if (!TryGetWorldTicks(out var ticks)) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { Inventory inventory = ((Humanoid)localPlayer).GetInventory(); if (inventory != null) { ProcessIfDue(inventory, ticks); } } PruneAndProcessContainers(ticks); PruneAndProcessGroundDrops(ticks); } internal static void RegisterContainer(Container? container) { if (!((Object)(object)container == (Object)null) && container.m_inventory != null && (!ContainersByInventory.TryGetValue(container.m_inventory, out WeakReference value) || !value.TryGetTarget(out var target) || target != container)) { ContainersByInventory[container.m_inventory] = new WeakReference(container); GetState(container.m_inventory).Dirty = true; } } internal static void ContainerLoaded(Container? container) { RegisterContainer(container); if (!((Object)(object)container == (Object)null) && IsOwnedContainer(container) && TryGetWorldTicks(out var ticks)) { GetState(container.m_inventory).Dirty = true; ProcessIfDue(container.m_inventory, ticks); } } internal static void MarkDirty(Inventory? inventory) { if (inventory != null && InventoryStates.TryGetValue(inventory, out InventoryState value) && !value.SuppressDirty) { value.Dirty = true; value.NextVisiblePreparationAt = 0f; } } internal static void InvalidateAll() { foreach (InventoryState value in InventoryStates.Values) { value.Dirty = true; value.EnvironmentKnown = false; value.BiomeSampleKnown = false; value.NextExpiryTicks = long.MinValue; value.NextVisiblePreparationAt = 0f; } GroundEnvironmentSamples.Clear(); if (ItemDrop.s_instances == null) { return; } ItemDrop[] array = ItemDrop.s_instances.ToArray(); foreach (ItemDrop val in array) { if ((Object)(object)val != (Object)null && IsPlacedGroundDrop(val)) { RegisterGroundDrop(val); } } } internal static void Reset() { InventoryStates.Clear(); ContainersByInventory.Clear(); GroundDropsByInstanceId.Clear(); GroundEnvironmentSamples.Clear(); GroundDropSnapshot.Clear(); LoggedReplacementWarnings.Clear(); _nextTickAt = 0f; _loggedFirstGroundLoadFailure = false; } internal static bool TryPrepareVisibleInventoryTimers(Inventory? inventory, out long nowTicks) { nowTicks = 0L; if (inventory == null || !TryGetWorldTicks(out nowTicks)) { return false; } if (IsAuthoritativeInventory(inventory)) { InventoryState state = GetState(inventory); if (Time.unscaledTime >= state.NextVisiblePreparationAt) { state.NextVisiblePreparationAt = Time.unscaledTime + 1f; if (IsPolicyReadyForInventory(inventory)) { ProcessIfDue(inventory, nowTicks); } } } return true; } internal static bool TryGetExpiryTicks(ItemData? item, out long clockValue) { clockValue = 0L; if (item?.m_customData != null && item.m_customData.TryGetValue("sighsorry.FineDining.ExpiryWorldTicks", out var value)) { return SpoilageClock.TryParseClockValue(value, out clockValue); } return false; } internal static bool TryGetSpoilageClock(ItemData? item, long nowTicks, out long remainingTicks, out bool paused) { remainingTicks = 0L; paused = false; if (TryGetExpiryTicks(item, out var clockValue)) { return SpoilageClock.TryDecodeClockValue(clockValue, nowTicks, out remainingTicks, out paused); } return false; } internal static string? ComposeStackClockValues(string? destinationValue, string? sourceValue) { if (!SpoilageClock.TryParseClockValue(sourceValue, out var clockValue)) { return destinationValue; } if (!SpoilageClock.TryParseClockValue(destinationValue, out var clockValue2)) { if (destinationValue != null) { return destinationValue; } return clockValue.ToString(CultureInfo.InvariantCulture); } long ticks; bool flag = TryGetWorldTicks(out ticks); if (!flag && clockValue2 < 0 != clockValue < 0) { return clockValue2.ToString(CultureInfo.InvariantCulture); } long nowTicks = (flag ? ticks : 0); return SpoilageClock.ComposeClockValues(clockValue2, clockValue, nowTicks, clockValue2 < 0).ToString(CultureInfo.InvariantCulture); } internal static bool CanMergeStackClockValues(string? destinationValue, string? sourceValue) { bool flag = destinationValue != null; bool flag2 = sourceValue != null; long clockValue = 0L; long clockValue2 = 0L; if (flag && !SpoilageClock.TryParseClockValue(destinationValue, out clockValue)) { return false; } if (flag2 && !SpoilageClock.TryParseClockValue(sourceValue, out clockValue2)) { return false; } if (!flag || !flag2 || clockValue < 0 == clockValue2 < 0) { return true; } long ticks; return TryGetWorldTicks(out ticks); } internal static bool PrepareItemForAdd(Inventory inventory, ItemData? item) { if (item == null || !IsAuthoritativeInventory(inventory)) { return false; } bool isPlayerInventory = IsLocalPlayerInventory(inventory); long clockValue; bool hasClock = TryGetExpiryTicks(item, out clockValue); if (!ShouldProcessInventorySpoilageClock(isPlayerInventory, hasClock) || !TryGetWorldTicks(out var ticks)) { return false; } ResolvedSpoilageRule rule = SpoilagePolicy.Resolve(item); bool paused; if (rule.State == SpoilageRuleState.Enabled) { return EnsureItemState(item, rule, ticks, ResolveInventoryPausedState(inventory), transitionExisting: false, out clockValue, out paused); } return false; } internal static bool ShouldProcessInventorySpoilageClock(bool isPlayerInventory, bool hasClock) { return isPlayerInventory || hasClock; } internal static bool PrepareInheritedItemForAdd(Inventory inventory, ItemData? item, long inheritedRemainingTicks) { if (item == null || !IsAuthoritativeInventory(inventory)) { return false; } bool flag = PrepareItemForAdd(inventory, item); if (inheritedRemainingTicks < 0 || SpoilagePolicy.Resolve(item).State != SpoilageRuleState.Enabled) { return flag; } bool destinationPaused = ResolveInventoryPausedState(inventory); return ApplyEarlierRemaining(item, inheritedRemainingTicks, destinationPaused) || flag; } internal static bool ApplyEarlierInventoryClock(Inventory inventory, ItemData? target, long sourceClockValue) { if (target == null || !SpoilageClock.IsValidClockValue(sourceClockValue) || !TryGetWorldTicks(out var ticks)) { return false; } return ApplyEarlierClock(target, sourceClockValue, ticks, ResolveInventoryPausedState(inventory)); } internal static bool ComposeInventoryStackMetadata(Inventory inventory, ItemData target, long sourceClockValue, AssignedLifetimeSnapshot targetLifetime, AssignedLifetimeSnapshot sourceLifetime) { if (inventory == null || target == null || !SpoilageClock.IsValidClockValue(sourceClockValue)) { return false; } return ApplyEarlierInventoryClock(inventory, target, sourceClockValue) | FreshnessRuntime.ComposeAssignedLifetime(target, targetLifetime, sourceLifetime); } internal static bool ComposeDirectRecoveryMergeExpiry(ItemData? target, ItemData? source, int movedAmount) { if (movedAmount <= 0 || target == null || source == null || target == source || target.m_customData == null || !TryGetExpiryTicks(source, out var clockValue)) { return false; } AssignedLifetimeSnapshot destination = FreshnessRuntime.CaptureAssignedLifetime(target); AssignedLifetimeSnapshot source2 = FreshnessRuntime.CaptureAssignedLifetime(source); long clockValue2; bool flag = TryGetExpiryTicks(target, out clockValue2); if (!TryGetWorldTicks(out var ticks)) { if (flag && clockValue2 < 0 != clockValue < 0) { return false; } ticks = 0L; } bool destinationPaused = (flag ? (clockValue2 < 0) : (clockValue < 0)); return ApplyEarlierClock(target, clockValue, ticks, destinationPaused) | FreshnessRuntime.ComposeAssignedLifetime(target, destination, source2); } internal static void ComposeGroundStackExpiry(ItemDrop? destination, ItemDrop? source) { if (!((Object)(object)destination == (Object)null) && !((Object)(object)source == (Object)null) && !((Object)(object)destination.m_nview == (Object)null) && !((Object)(object)source.m_nview == (Object)null) && destination.m_nview.IsValid() && source.m_nview.IsValid() && destination.m_nview.IsOwner() && source.m_nview.IsOwner() && TryGetExpiryTicks(source.m_itemData, out var clockValue) && TryGetWorldTicks(out var ticks)) { AssignedLifetimeSnapshot destination2 = FreshnessRuntime.CaptureAssignedLifetime(destination.m_itemData); AssignedLifetimeSnapshot source2 = FreshnessRuntime.CaptureAssignedLifetime(source.m_itemData); bool destinationPaused = ResolveWorldDropPausedState(destination); ApplyEarlierClock(destination.m_itemData, clockValue, ticks, destinationPaused); FreshnessRuntime.ComposeAssignedLifetime(destination.m_itemData, destination2, source2); RegisterGroundDrop(destination); } } internal static void RegisterGroundDrop(ItemDrop? drop) { if ((Object)(object)drop == (Object)null) { return; } int instanceID = ((Object)drop).GetInstanceID(); if (!ReconcileCreatorlessPlacedDrop(drop)) { WeakReference value; ItemDrop target; if (!HasValidGroundView(drop) || (!TryGetExpiryTicks(drop.m_itemData, out var _) && !IsPlacedGroundDrop(drop))) { GroundDropsByInstanceId.Remove(instanceID); GroundEnvironmentSamples.Remove(instanceID); } else if (!GroundDropsByInstanceId.TryGetValue(instanceID, out value) || !value.TryGetTarget(out target) || target != drop) { GroundDropsByInstanceId[instanceID] = new WeakReference(drop); } } } internal static void RefreshOwnedPlacedDrop(ItemDrop? drop) { if ((Object)(object)drop == (Object)null || !IsOwnedGroundDrop(drop) || !IsPlacedGroundDrop(drop)) { return; } try { drop.Load(); RegisterGroundDrop(drop); } catch (Exception ex) { if (!_loggedFirstGroundLoadFailure) { _loggedFirstGroundLoadFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not refresh an owned placed food item; FineDining will retry: " + ex)); } } } internal static void InitializePlacedDrop(ItemDrop? drop, long inheritedRemainingTicks, long placementTicks = 0L) { if ((Object)(object)drop == (Object)null || !HasValidGroundView(drop) || !IsPlacedGroundDrop(drop) || ReconcileCreatorlessPlacedDrop(drop)) { return; } if (IsOwnedGroundDrop(drop)) { long clockValue; bool hasPersistedExpiry = TryGetExpiryTicks(drop.m_itemData, out clockValue); bool hasPendingAnchor = drop.m_itemData?.m_customData.ContainsKey("sighsorry.FineDining.PlacedWorldTicks") ?? false; if (!ShouldInitializePlacedDeadline(placementTicks, hasPersistedExpiry, hasPendingAnchor)) { RegisterGroundDrop(drop); } else { InitializeOwnedWorldDrop(drop, inheritedRemainingTicks, placementTicks, keepPendingWhenNotReady: true); } } else { RegisterGroundDrop(drop); } } internal static bool ShouldInitializePlacedDeadline(long placementTicks, bool hasPersistedExpiry, bool hasPendingAnchor) { return placementTicks > 0 || !hasPersistedExpiry || hasPendingAnchor; } internal static void InitializeRecoveredDrop(ItemDrop? drop, long inheritedRemainingTicks) { if (!((Object)(object)drop == (Object)null) && inheritedRemainingTicks >= 0 && IsOwnedGroundDrop(drop)) { InitializeOwnedWorldDrop(drop, inheritedRemainingTicks, 0L, keepPendingWhenNotReady: false); } } internal static long CalculateEffectiveRemainingTicks(long nowTicks, long anchorTicks, long lifetimeTicks, long inheritedRemainingTicks, bool paused) { long num = Math.Max(10000000L, lifetimeTicks); long num2 = ((!paused && anchorTicks > 0 && nowTicks > anchorTicks) ? (nowTicks - anchorTicks) : 0); long num3 = ((num2 >= num) ? 0 : (num - num2)); if (inheritedRemainingTicks < 0) { return num3; } return Math.Min(inheritedRemainingTicks, num3); } internal static void UnregisterGroundDrop(ItemDrop? drop) { if ((Object)(object)drop != (Object)null) { int instanceID = ((Object)drop).GetInstanceID(); GroundDropsByInstanceId.Remove(instanceID); GroundEnvironmentSamples.Remove(instanceID); } } internal static bool IsAuthoritativeInventory(Inventory? inventory) { if (inventory == null) { return false; } if (IsLocalPlayerInventory(inventory)) { GetState(inventory); return true; } if (!ContainersByInventory.TryGetValue(inventory, out WeakReference value) || !value.TryGetTarget(out var target) || (Object)(object)target == (Object)null) { return false; } return IsOwnedContainer(target); } private static bool IsLocalPlayerInventory(Inventory? inventory) { Player localPlayer = Player.m_localPlayer; if (inventory != null && (Object)(object)localPlayer != (Object)null) { return ((Humanoid)localPlayer).GetInventory() == inventory; } return false; } internal static bool IsContainerLoading(Inventory? inventory) { if (TryGetContainer(inventory, out Container container)) { return container.m_loading; } return false; } internal static bool TryGetContainer(Inventory? inventory, out Container? container) { container = null; if (inventory == null || !ContainersByInventory.TryGetValue(inventory, out WeakReference value) || !value.TryGetTarget(out var target) || (Object)(object)target == (Object)null) { return false; } container = target; return true; } private static void ProcessIfDue(Inventory inventory, long nowTicks) { InventoryState state = GetState(inventory); PreservationState preservationState = ResolveInventoryPreservationState(inventory); if (preservationState != PreservationState.Unknown) { bool flag = preservationState == PreservationState.Paused; if (!state.EnvironmentKnown || state.PausedByCold != flag) { state.EnvironmentKnown = true; state.PausedByCold = flag; state.Dirty = true; } } if (state.Dirty || nowTicks >= state.NextExpiryTicks) { Reconcile(inventory, state, nowTicks); } } private static bool IsPolicyReadyForInventory(Inventory inventory) { foreach (ItemData item in inventory.m_inventory) { if (item != null && SpoilagePolicy.Resolve(item).State == SpoilageRuleState.NotReady) { return false; } } return true; } private static void PruneAndProcessContainers(long nowTicks) { List list = null; foreach (KeyValuePair> item in ContainersByInventory) { if (!item.Value.TryGetTarget(out var target) || (Object)(object)target == (Object)null || target.m_inventory == null) { if (list == null) { list = new List(); } list.Add(item.Key); } else if (IsOwnedContainer(target)) { ProcessIfDue(item.Key, nowTicks); } } if (list == null) { return; } foreach (Inventory item2 in list) { ContainersByInventory.Remove(item2); InventoryStates.Remove(item2); } } private static void PruneAndProcessGroundDrops(long nowTicks) { GroundDropSnapshot.Clear(); foreach (KeyValuePair> item in GroundDropsByInstanceId) { GroundDropSnapshot.Add(item); } List list = null; List list2 = null; foreach (KeyValuePair> item2 in GroundDropSnapshot) { if (!item2.Value.TryGetTarget(out var target) || (Object)(object)target == (Object)null || !HasValidGroundView(target)) { if (list == null) { list = new List(); } list.Add(item2.Key); continue; } try { target.Load(); } catch (Exception ex) { if (!_loggedFirstGroundLoadFailure) { _loggedFirstGroundLoadFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not refresh a tracked world item; FineDining will retry: " + ex)); } continue; } bool flag = IsPlacedGroundDrop(target); if (flag && ReconcileCreatorlessPlacedDrop(target)) { if (list == null) { list = new List(); } list.Add(item2.Key); continue; } if (flag && IsOwnedGroundDrop(target)) { ItemData itemData = target.m_itemData; if (itemData != null && itemData.m_customData.ContainsKey("sighsorry.FineDining.PlacedWorldTicks")) { long inheritedRemainingTicks = -1L; if (TryGetSpoilageClock(target.m_itemData, nowTicks, out var remainingTicks, out var _)) { inheritedRemainingTicks = remainingTicks; } InitializeOwnedWorldDrop(target, inheritedRemainingTicks, 0L, keepPendingWhenNotReady: true); } } if (!TryGetExpiryTicks(target.m_itemData, out var clockValue)) { if (!flag) { if (list == null) { list = new List(); } list.Add(item2.Key); continue; } if (!IsOwnedGroundDrop(target)) { if (list == null) { list = new List(); } list.Add(item2.Key); continue; } InitializeOwnedWorldDrop(target, -1L, 0L, keepPendingWhenNotReady: true); if (!TryGetExpiryTicks(target.m_itemData, out clockValue)) { continue; } } if (!IsOwnedGroundDrop(target)) { continue; } ResolvedSpoilageRule rule = SpoilagePolicy.Resolve(target.m_itemData); if (rule.State == SpoilageRuleState.NotReady) { continue; } bool expired; if (rule.State != SpoilageRuleState.Enabled) { ClearOwnedGroundExpiry(target); if (list == null) { list = new List(); } list.Add(item2.Key); } else if (TryEvaluateGroundExpiry(target, rule, nowTicks, out expired) && expired) { if (list == null) { list = new List(); } list.Add(item2.Key); if (list2 == null) { list2 = new List(); } list2.Add(target); } } GroundDropSnapshot.Clear(); if (list != null) { foreach (int item3 in list) { GroundDropsByInstanceId.Remove(item3); GroundEnvironmentSamples.Remove(item3); } } if (list2 == null) { return; } foreach (ItemDrop item4 in list2) { try { if (!IsOwnedGroundDrop(item4)) { RegisterGroundDrop(item4); continue; } ResolvedSpoilageRule rule2 = SpoilagePolicy.Resolve(item4.m_itemData); bool expired2; if (rule2.State == SpoilageRuleState.NotReady) { RegisterGroundDrop(item4); } else if (rule2.State != SpoilageRuleState.Enabled) { ClearOwnedGroundExpiry(item4); } else if (!TryEvaluateGroundExpiry(item4, rule2, nowTicks, out expired2) || !expired2) { RegisterGroundDrop(item4); } else { ExpireGroundStack(item4, rule2); } } catch (Exception ex2) { FineDiningPlugin.Log.LogError((object)("Failed to expire a tracked world item: " + ex2)); RegisterGroundDrop(item4); } } } private static bool TryEvaluateGroundExpiry(ItemDrop drop, ResolvedSpoilageRule rule, long nowTicks, out bool expired) { expired = false; if (ReconcileCreatorlessPlacedDrop(drop)) { return false; } ItemData itemData = drop.m_itemData; if (itemData == null) { return false; } if (EnsureItemState(itemData, rule, nowTicks, ResolveWorldDropPausedState(drop, rule.Group), transitionExisting: true, out var remainingTicks, out var paused)) { drop.Save(); } expired = !paused && remainingTicks <= 0; return true; } private static void ClearOwnedGroundExpiry(ItemDrop drop) { if (IsOwnedGroundDrop(drop)) { bool flag = false; if (drop.m_itemData?.m_customData != null) { flag |= drop.m_itemData.m_customData.Remove("sighsorry.FineDining.ExpiryWorldTicks"); flag |= drop.m_itemData.m_customData.Remove("sighsorry.FineDining.PlacedWorldTicks"); flag |= FreshnessRuntime.ClearTrackedMetadata(drop.m_itemData); } if (flag) { drop.Save(); } UnregisterGroundDrop(drop); } } private static void InitializeOwnedWorldDrop(ItemDrop drop, long inheritedRemainingTicks, long anchorTicks, bool keepPendingWhenNotReady) { if (!IsOwnedGroundDrop(drop) || ReconcileCreatorlessPlacedDrop(drop)) { return; } bool flag = false; long ticks = 0L; if (keepPendingWhenNotReady && !TryGetPositiveCustomTicks(drop.m_itemData, "sighsorry.FineDining.PlacedWorldTicks", out ticks)) { ticks = anchorTicks; if (ticks <= 0) { TryGetWorldTicks(out ticks); } if (ticks > 0) { drop.m_itemData.m_customData["sighsorry.FineDining.PlacedWorldTicks"] = ticks.ToString(CultureInfo.InvariantCulture); flag = true; } } ResolvedSpoilageRule resolvedSpoilageRule = SpoilagePolicy.Resolve(drop.m_itemData); if (resolvedSpoilageRule.State == SpoilageRuleState.NotReady) { if (flag | (inheritedRemainingTicks >= 0 && ApplyEarlierRemaining(drop.m_itemData, inheritedRemainingTicks, ResolveWorldDropPausedState(drop)))) { drop.Save(); } if (keepPendingWhenNotReady || inheritedRemainingTicks >= 0) { RegisterGroundDrop(drop); } return; } if (resolvedSpoilageRule.State != SpoilageRuleState.Enabled) { ClearOwnedGroundExpiry(drop); return; } if (!TryGetWorldTicks(out var ticks2)) { RegisterGroundDrop(drop); return; } bool flag2 = ResolveWorldDropPausedState(drop, resolvedSpoilageRule.Group); long num = CalculateEffectiveRemainingTicks(ticks2, ticks, resolvedSpoilageRule.LifetimeTicks, inheritedRemainingTicks, flag2); long clockValue = SpoilageClock.EncodeClockValue(ticks2, num, flag2 && num > 0); flag |= SetClockValue(drop.m_itemData, clockValue); flag |= FreshnessRuntime.EnsureTrackedMetadata(drop.m_itemData, resolvedSpoilageRule.LifetimeTicks); if (keepPendingWhenNotReady) { flag |= drop.m_itemData.m_customData.Remove("sighsorry.FineDining.PlacedWorldTicks"); } if (flag) { drop.Save(); } RegisterGroundDrop(drop); } private static bool HasValidGroundView(ItemDrop drop) { try { return (Object)(object)drop.m_nview != (Object)null && drop.m_nview.IsValid(); } catch { return false; } } private static bool IsOwnedGroundDrop(ItemDrop drop) { try { return HasValidGroundView(drop) && drop.m_nview.IsOwner(); } catch { return false; } } private static bool IsPlacedGroundDrop(ItemDrop drop) { try { if (!HasValidGroundView(drop)) { return false; } return drop.m_nview.GetZDO().GetBool(ZDOVars.s_piece, false) || drop.IsPiece(); } catch { return false; } } internal static bool IsCreatorlessPlacedDrop(ItemDrop? drop) { try { return (Object)(object)drop != (Object)null && IsPlacedGroundDrop(drop) && drop.m_nview.GetZDO().GetLong(ZDOVars.s_creator, 0L) == 0; } catch { return false; } } private static bool ReconcileCreatorlessPlacedDrop(ItemDrop? drop) { if (!IsCreatorlessPlacedDrop(drop)) { return false; } if (IsOwnedGroundDrop(drop)) { ClearOwnedGroundExpiry(drop); } else { UnregisterGroundDrop(drop); } return true; } private static bool TryGetPositiveCustomTicks(ItemData? item, string key, out long ticks) { ticks = 0L; if (item?.m_customData != null && item.m_customData.TryGetValue(key, out var value) && long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out ticks) && ticks > 0) { return string.Equals(value, ticks.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal); } return false; } private static PreservationState ResolveInventoryPreservationState(Inventory inventory) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) InventoryState state = GetState(inventory); Player localPlayer = Player.m_localPlayer; if (IsLocalPlayerInventory(inventory)) { return SampleInventoryBiome(state, ((Component)localPlayer).transform.position); } if (ContainersByInventory.TryGetValue(inventory, out WeakReference value) && value.TryGetTarget(out var target) && (Object)(object)target != (Object)null) { if (GeneratedPrefabRegistry.IsIcebox(target)) { return PreservationState.Paused; } return SampleInventoryBiome(state, ((Component)target).transform.position); } return PreservationState.Unknown; } private static bool ResolveInventoryPausedState(Inventory inventory) { PreservationState preservationState = ResolveInventoryPreservationState(inventory); if (preservationState != PreservationState.Unknown) { return preservationState == PreservationState.Paused; } InventoryState state = GetState(inventory); if (state.EnvironmentKnown) { return state.PausedByCold; } return false; } private static bool ResolveWorldDropPausedState(ItemDrop drop, SpoilageGroup? knownGroup = null) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) if (IsFishPreservedInWater(drop, knownGroup)) { return true; } int instanceID = ((Object)drop).GetInstanceID(); Vector3 position = ((Component)drop).transform.position; if (GroundEnvironmentSamples.TryGetValue(instanceID, out GroundEnvironmentSample value) && PositionsMatch(value.Position, position)) { return value.Paused; } if (TryIsNoSpoilBiome(position, out var preserved, out var cacheable)) { if (cacheable) { GroundEnvironmentSamples[instanceID] = new GroundEnvironmentSample { Position = position, Paused = preserved }; } return preserved; } if (TryGetExpiryTicks(drop.m_itemData, out var clockValue)) { return clockValue < 0; } return false; } private static bool IsFishPreservedInWater(ItemDrop? drop, SpoilageGroup? knownGroup) { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)drop == (Object)null) { return false; } try { SpoilageGroup spoilageGroup; if (knownGroup.HasValue) { spoilageGroup = knownGroup.Value; } else { ResolvedSpoilageRule resolvedSpoilageRule = SpoilagePolicy.Resolve(drop.m_itemData); if (resolvedSpoilageRule.State != SpoilageRuleState.Enabled) { return false; } spoilageGroup = resolvedSpoilageRule.Group; } if (spoilageGroup != SpoilageGroup.Fish) { return false; } Fish val = ((Component)drop).GetComponent() ?? ((Component)drop).GetComponentInParent() ?? ((Component)drop).GetComponentInChildren(true); if ((Object)(object)val != (Object)null) { return !val.IsOutOfWater(); } Floating val2 = drop.m_floating ?? ((Component)drop).GetComponent(); if ((Object)(object)val2 == (Object)null || val2.m_waterLevel <= -10000f) { return false; } return (((Object)(object)val2.m_body != (Object)null) ? val2.m_body.worldCenterOfMass.y : ((Component)drop).transform.position.y) - val2.m_waterLevel - val2.m_waterLevelOffset <= 0.05f; } catch { return false; } } private static PreservationState SampleInventoryBiome(InventoryState state, Vector3 position) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (state.BiomeSampleKnown && PositionsMatch(state.SampledBiomePosition, position)) { if (!state.SampledBiomePaused) { return PreservationState.Running; } return PreservationState.Paused; } if (!TryIsNoSpoilBiome(position, out var preserved, out var cacheable)) { return PreservationState.Unknown; } if (cacheable) { state.BiomeSampleKnown = true; state.SampledBiomePosition = position; state.SampledBiomePaused = preserved; } if (!preserved) { return PreservationState.Running; } return PreservationState.Paused; } private static bool PositionsMatch(Vector3 left, Vector3 right) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) Vector3 val = left - right; return ((Vector3)(ref val)).sqrMagnitude <= 0.0001f; } private static bool TryIsNoSpoilBiome(Vector3 position, out bool preserved, out bool cacheable) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) preserved = false; cacheable = false; try { Biome val = Heightmap.FindBiome(position); cacheable = (int)val > 0; if ((int)val == 0 && WorldGenerator.instance != null) { val = WorldGenerator.instance.GetBiome(position.x, position.z, 0.02f, false); } if ((int)val == 0) { return false; } preserved = PreservationConfig.IsNoSpoilBiome(val); return true; } catch { cacheable = false; return false; } } internal static List> BeginPlayerSaveClockSnapshot(Player? player) { List> list = new List>(); Inventory val = ((player != null) ? ((Humanoid)player).GetInventory() : null); if (val == null || !TryGetWorldTicks(out var ticks)) { return list; } foreach (ItemData item in val.m_inventory) { if (TryGetSpoilageClock(item, ticks, out var remainingTicks, out var paused) && paused && item.m_customData.TryGetValue("sighsorry.FineDining.ExpiryWorldTicks", out var value)) { list.Add(new KeyValuePair(item, value)); item.m_customData["sighsorry.FineDining.ExpiryWorldTicks"] = SpoilageClock.EncodeClockValue(ticks, remainingTicks, paused: false).ToString(CultureInfo.InvariantCulture); } } return list; } internal static void EndPlayerSaveClockSnapshot(List>? pausedClocks) { if (pausedClocks == null) { return; } foreach (KeyValuePair pausedClock in pausedClocks) { if (pausedClock.Key?.m_customData != null) { pausedClock.Key.m_customData["sighsorry.FineDining.ExpiryWorldTicks"] = pausedClock.Value; } } } private static bool IsOwnedContainer(Container container) { try { return (Object)(object)container.m_nview != (Object)null && container.m_nview.IsValid() && container.m_nview.IsOwner(); } catch { return false; } } private static InventoryState GetState(Inventory inventory) { if (!InventoryStates.TryGetValue(inventory, out InventoryState value)) { value = new InventoryState(); InventoryStates.Add(inventory, value); } return value; } private static void Reconcile(Inventory inventory, InventoryState state, long nowTicks) { if (state.Reconciling) { return; } state.Reconciling = true; bool flag = state.PendingNotification; long num = long.MaxValue; bool shouldPause = state.EnvironmentKnown && state.PausedByCold; bool isPlayerInventory = IsLocalPlayerInventory(inventory); try { List inventory2 = inventory.m_inventory; for (int num2 = inventory2.Count - 1; num2 >= 0; num2--) { ItemData val = inventory2[num2]; if (val != null) { ResolvedSpoilageRule rule = SpoilagePolicy.Resolve(val); if (rule.State != SpoilageRuleState.NotReady) { if (rule.State != SpoilageRuleState.Enabled) { if (val.m_customData.Remove("sighsorry.FineDining.ExpiryWorldTicks")) { flag = true; } flag |= FreshnessRuntime.ClearTrackedMetadata(val); } else { long clockValue; bool hasClock = TryGetExpiryTicks(val, out clockValue); if (ShouldProcessInventorySpoilageClock(isPlayerInventory, hasClock)) { if (EnsureItemState(val, rule, nowTicks, shouldPause, state.EnvironmentKnown, out var remainingTicks, out var paused)) { flag = true; } if (!paused) { if (remainingTicks <= 0) { bool flag2 = ExpireItem(inventory, val, rule); flag = flag || flag2; if (!flag2) { num = Math.Min(num, nowTicks); } } else { long val2 = SpoilageClock.AddTicksSaturating(nowTicks, remainingTicks); num = Math.Min(num, val2); } } } } } } } if (flag) { state.PendingNotification = true; state.SuppressDirty = true; inventory.Changed(); state.PendingNotification = false; } state.Dirty = false; state.NextExpiryTicks = num; } catch (Exception ex) { state.Dirty = true; FineDiningPlugin.Log.LogError((object)("Failed to reconcile inventory spoilage: " + ex)); } finally { state.SuppressDirty = false; state.Reconciling = false; } } private static bool EnsureItemState(ItemData item, ResolvedSpoilageRule rule, long nowTicks, bool shouldPause, bool transitionExisting, out long remainingTicks, out bool paused) { remainingTicks = 0L; paused = false; if (rule.State != SpoilageRuleState.Enabled) { return false; } bool result = FreshnessRuntime.EnsureTrackedMetadata(item, rule.LifetimeTicks); if (TryGetSpoilageClock(item, nowTicks, out remainingTicks, out paused)) { if (!transitionExisting || paused == shouldPause) { return result; } if (!paused && remainingTicks <= 0) { return result; } paused = shouldPause; long num = SpoilageClock.EncodeClockValue(nowTicks, remainingTicks, paused); item.m_customData["sighsorry.FineDining.ExpiryWorldTicks"] = num.ToString(CultureInfo.InvariantCulture); return true; } long num2 = Math.Max(10000000L, rule.LifetimeTicks); remainingTicks = num2; paused = shouldPause; long num3 = SpoilageClock.EncodeClockValue(nowTicks, remainingTicks, paused); item.m_customData["sighsorry.FineDining.ExpiryWorldTicks"] = num3.ToString(CultureInfo.InvariantCulture); return true; } private static ReplacementResolution ResolveReplacement(ItemData sourceItem, ResolvedSpoilageRule rule, out string configuredPrefab, out ItemDrop replacementDrop) { configuredPrefab = FoodIdentity.NormalizePrefabName(rule.ReplacementPrefab); replacementDrop = null; ObjectDB instance = ObjectDB.instance; if (!IsObjectDatabaseReady(instance)) { return ReplacementResolution.NotReady; } bool flag = GeneratedPrefabRegistry.IsGeneratedReplacementPrefabName(configuredPrefab); if (flag && !GeneratedPrefabRegistry.EnsureGeneratedReplacementAvailable(configuredPrefab)) { return ReplacementResolution.NotReady; } GameObject val = ResolveItemPrefab(instance, configuredPrefab); if ((Object)(object)val == (Object)null && flag) { return ReplacementResolution.NotReady; } ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(sourceItem); if (val2?.m_itemData?.m_shared == null || string.Equals(canonicalPrefabName, configuredPrefab, StringComparison.OrdinalIgnoreCase)) { WarnReplacementOnce(configuredPrefab, "Spoiled prefab '" + configuredPrefab + "' is missing, invalid, or the same as the expired item. Removing expired stacks instead."); return ReplacementResolution.Invalid; } replacementDrop = val2; return ReplacementResolution.Ready; } private static bool ExpireItem(Inventory inventory, ItemData item, ResolvedSpoilageRule rule) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) string configuredPrefab; ItemDrop replacementDrop; switch (ResolveReplacement(item, rule, out configuredPrefab, out replacementDrop)) { case ReplacementResolution.NotReady: return false; case ReplacementResolution.Invalid: return inventory.m_inventory.Remove(item); default: { GameObject gameObject = ((Component)replacementDrop).gameObject; ItemData itemData = replacementDrop.m_itemData; int num = Math.Max(0, item.m_stack); Vector2i gridPos = item.m_gridPos; int worldLevel = item.m_worldLevel; if (!inventory.m_inventory.Remove(item)) { return false; } if (num <= 0) { return true; } int num2 = Math.Max(1, itemData.m_shared.m_maxStackSize); int num3 = CalculateReplacementAmount(num); ItemData val = CreateReplacement(itemData, gameObject, worldLevel); int amount = num3; num3 -= InsertReplacementStack(inventory, val, amount, gridPos); if (num3 > 0) { foreach (ItemData item2 in inventory.m_inventory) { if (num3 <= 0) { break; } if (CanStackReplacement(item2, val)) { int num4 = Math.Min(num3, Math.Max(0, num2 - item2.m_stack)); item2.m_stack += num4; num3 -= num4; } } } while (num3 > 0) { ItemData val2 = CreateReplacement(itemData, gameObject, worldLevel); Vector2i val3 = inventory.FindEmptySlot(inventory.TopFirst(val2)); if (val3.x < 0) { break; } int amount2 = Math.Min(num2, num3); int num5 = InsertReplacementStack(inventory, val2, amount2, val3); if (num5 <= 0) { break; } num3 -= num5; } if (num3 > 0) { WarnReplacementOnce("overflow:" + configuredPrefab, "Inventory had no room for " + num3 + " additional '" + configuredPrefab + "' items; overflow was discarded safely."); } return true; } } } private static void ExpireGroundStack(ItemDrop drop, ResolvedSpoilageRule rule) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) if (!IsOwnedGroundDrop(drop)) { RegisterGroundDrop(drop); } else { if (ReconcileCreatorlessPlacedDrop(drop)) { return; } ZDOID uid = drop.m_nview.GetZDO().m_uid; if (!TryGetExpiryTicks(drop.m_itemData, out var clockValue)) { UnregisterGroundDrop(drop); return; } if (drop.m_autoDestroy && drop.GetTimeSinceSpawned() >= 3600.0) { drop.TimedDestruction(); if (!IsOwnedGroundDrop(drop)) { return; } } ItemData itemData = drop.m_itemData; int num = Math.Max(0, itemData?.m_stack ?? 0); if (itemData == null || num <= 0) { drop.m_nview.Destroy(); return; } string configuredPrefab; ItemDrop replacementDrop; ReplacementResolution replacementResolution = ResolveReplacement(itemData, rule, out configuredPrefab, out replacementDrop); if (replacementResolution == ReplacementResolution.NotReady) { RegisterGroundDrop(drop); return; } string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(itemData); if (replacementResolution == ReplacementResolution.Invalid) { drop.m_nview.Destroy(); return; } GameObject gameObject = ((Component)replacementDrop).gameObject; ItemData itemData2 = replacementDrop.m_itemData; Vector3 position = ((Component)drop).transform.position; Quaternion rotation = ((Component)drop).transform.rotation; long num2 = (ShouldInheritGroundSpawnTime(drop.m_autoDestroy, IsPlacedGroundDrop(drop)) ? GetGroundSpawnTimeTicks(drop) : 0); if (num2 > 0 && !replacementDrop.m_autoDestroy) { WarnReplacementOnce("cleanup:" + configuredPrefab, "Spoiled prefab '" + configuredPrefab + "' does not use vanilla ItemDrop auto-destruction; the original ground cleanup age cannot be enforced for that replacement."); } int worldLevel = itemData.m_worldLevel; int num3 = CalculateReplacementAmount(num); ItemData val = CreateReplacement(itemData2, gameObject, worldLevel); ItemDrop val2 = null; try { val2 = ItemDrop.DropItem(val, num3, position, rotation); if (!IsOwnedGroundDrop(val2)) { throw new InvalidOperationException("A newly spawned spoilage replacement was not locally owned."); } val2.OnPlayerDrop(); InheritGroundSpawnTime(val2, num2); } catch (Exception ex) { DestroySpawnedGroundReplacement(val2); FineDiningPlugin.Log.LogError((object)("Could not create a ground spoilage replacement; the original stack was kept: " + ex)); RegisterGroundDrop(drop); return; } if (!GroundSourceStillMatches(drop, uid, clockValue, canonicalPrefabName, num)) { DestroySpawnedGroundReplacement(val2); RegisterGroundDrop(drop); } else { drop.m_nview.Destroy(); } } } private static bool GroundSourceStillMatches(ItemDrop drop, ZDOID sourceZdoId, long sourceExpiryTicks, string sourcePrefab, int sourceAmount) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (!IsOwnedGroundDrop(drop) || IsCreatorlessPlacedDrop(drop) || drop.m_nview.GetZDO().m_uid != sourceZdoId || drop.m_itemData == null || drop.m_itemData.m_stack != sourceAmount || !TryGetExpiryTicks(drop.m_itemData, out var clockValue) || clockValue != sourceExpiryTicks) { return false; } return string.Equals(FoodIdentity.GetCanonicalPrefabName(drop.m_itemData), sourcePrefab, StringComparison.OrdinalIgnoreCase); } internal static bool ShouldInheritGroundSpawnTime(bool autoDestroy, bool isPiece) { if (autoDestroy) { return !isPiece; } return false; } private static long GetGroundSpawnTimeTicks(ItemDrop drop) { try { return HasValidGroundView(drop) ? drop.m_nview.GetZDO().GetLong(ZDOVars.s_spawnTime, 0L) : 0; } catch { return 0L; } } private static void InheritGroundSpawnTime(ItemDrop replacement, long sourceSpawnTimeTicks) { if (sourceSpawnTimeTicks > 0 && IsOwnedGroundDrop(replacement)) { replacement.m_nview.GetZDO().Set(ZDOVars.s_spawnTime, sourceSpawnTimeTicks); } } private static void DestroySpawnedGroundReplacement(ItemDrop? replacement) { if (!((Object)(object)replacement == (Object)null)) { if (IsOwnedGroundDrop(replacement)) { replacement.m_nview.Destroy(); } else if (HasValidGroundView(replacement)) { WarnReplacementOnce("ground-rollback-owner", "A spoilage replacement changed owner during rollback and could not be removed locally."); } } } internal static int CalculateReplacementAmount(int sourceAmount) { return Math.Max(0, sourceAmount); } private static int InsertReplacementStack(Inventory inventory, ItemData item, int amount, Vector2i requestedPosition) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (amount <= 0) { return 0; } item.m_stack = amount; int stack = item.m_stack; inventory.AddItem(item, amount, requestedPosition.x, requestedPosition.y); return Math.Max(0, stack - item.m_stack); } private static ItemData CreateReplacement(ItemData template, GameObject prefab, int sourceWorldLevel) { ItemData obj = template.Clone(); obj.m_dropPrefab = prefab; obj.m_worldLevel = sourceWorldLevel; obj.m_equipped = false; obj.m_customData.Remove("sighsorry.FineDining.ExpiryWorldTicks"); FreshnessRuntime.ClearTrackedMetadata(obj); return obj; } private static GameObject? ResolveItemPrefab(ObjectDB? objectDb, string prefabName) { if ((Object)(object)objectDb == (Object)null || string.IsNullOrWhiteSpace(prefabName)) { return null; } GameObject itemPrefab = objectDb.GetItemPrefab(prefabName); if ((Object)(object)itemPrefab != (Object)null && string.Equals(FoodIdentity.NormalizePrefabName(((Object)itemPrefab).name), prefabName, StringComparison.OrdinalIgnoreCase)) { return itemPrefab; } return objectDb.m_items?.FirstOrDefault((Func)((GameObject prefab) => (Object)(object)prefab != (Object)null && string.Equals(FoodIdentity.NormalizePrefabName(((Object)prefab).name), prefabName, StringComparison.OrdinalIgnoreCase))); } private static bool IsObjectDatabaseReady(ObjectDB? objectDb) { if ((Object)(object)objectDb != (Object)null && objectDb.m_items != null) { return objectDb.m_items.Count > 0; } return false; } private static bool CanStackReplacement(ItemData candidate, ItemData template) { if (candidate.m_shared.m_name == template.m_shared.m_name && candidate.m_quality == template.m_quality && candidate.m_worldLevel == template.m_worldLevel && candidate.m_stack < candidate.m_shared.m_maxStackSize) { return CustomDataEqual(candidate.m_customData, template.m_customData); } return false; } private static bool CustomDataEqual(Dictionary left, Dictionary right) { int num = left.Count - CountStackMetadataKeys(left); int num2 = right.Count - CountStackMetadataKeys(right); string value; if (num == num2) { return left.All>((KeyValuePair pair) => IsStackMetadataKey(pair.Key) || (right.TryGetValue(pair.Key, out value) && value == pair.Value)); } return false; } private static int CountStackMetadataKeys(Dictionary values) { return (values.ContainsKey("sighsorry.FineDining.ExpiryWorldTicks") ? 1 : 0) + (values.ContainsKey("sighsorry.FineDining.AssignedLifetimeTicks") ? 1 : 0); } private static bool IsStackMetadataKey(string key) { if (!(key == "sighsorry.FineDining.ExpiryWorldTicks")) { return key == "sighsorry.FineDining.AssignedLifetimeTicks"; } return true; } private static bool ApplyEarlierClock(ItemData target, long sourceClockValue, long nowTicks, bool destinationPaused) { if (!SpoilageClock.IsValidClockValue(sourceClockValue)) { return false; } long clockValue2; long clockValue = (TryGetExpiryTicks(target, out clockValue2) ? SpoilageClock.ComposeClockValues(clockValue2, sourceClockValue, nowTicks, destinationPaused) : ReencodeClockValue(sourceClockValue, nowTicks, destinationPaused)); return SetClockValue(target, clockValue); } private static bool ApplyEarlierRemaining(ItemData target, long sourceRemainingTicks, bool destinationPaused) { if (sourceRemainingTicks < 0) { return false; } long ticks; long nowTicks = (TryGetWorldTicks(out ticks) ? ticks : 0); long sourceClockValue = SpoilageClock.EncodeClockValue(nowTicks, sourceRemainingTicks, destinationPaused); return ApplyEarlierClock(target, sourceClockValue, nowTicks, destinationPaused); } private static long ReencodeClockValue(long clockValue, long nowTicks, bool paused) { if (!SpoilageClock.TryDecodeClockValue(clockValue, nowTicks, out var remainingTicks, out var _)) { return clockValue; } return SpoilageClock.EncodeClockValue(nowTicks, remainingTicks, paused && remainingTicks > 0); } private static bool SetClockValue(ItemData target, long clockValue) { if (!SpoilageClock.IsValidClockValue(clockValue)) { return false; } string text = clockValue.ToString(CultureInfo.InvariantCulture); if (target.m_customData.TryGetValue("sighsorry.FineDining.ExpiryWorldTicks", out var value) && value == text) { return false; } target.m_customData["sighsorry.FineDining.ExpiryWorldTicks"] = text; return true; } private static void WarnReplacementOnce(string key, string message) { if (LoggedReplacementWarnings.Add(key ?? "")) { FineDiningPlugin.Log.LogWarning((object)message); } } internal static bool TryGetWorldTicks(out long ticks) { ticks = 0L; ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return false; } try { ticks = instance.GetTime().Ticks; return ticks > 0; } catch { return false; } } } internal static class ChefChoiceMath { internal const float MinimumAllowedMultiplier = 1f; internal const float MaximumTierSelectionStrength = 20f; internal static float ClampCookingFactor(float cookingFactor) { return ClampUnit(cookingFactor); } internal static float ClampNormalizedTier(float normalizedTier) { return ClampUnit(normalizedTier); } internal static float ClampPercentage(float percentage) { if (float.IsNaN(percentage) || percentage <= 0f) { return 0f; } if (!float.IsPositiveInfinity(percentage) && !(percentage >= 100f)) { return percentage; } return 100f; } internal static float BlendCategoryProbability(float baseProbability, float historyProbability, float percentage) { float num = ClampPercentage(percentage) / 100f; if (num <= 0f) { return baseProbability; } if (num >= 1f) { return historyProbability; } return baseProbability + (historyProbability - baseProbability) * num; } internal static float GetFoodSelectionWeight(float cookingFactor, float normalizedTier, bool tierResolved, float highTierSelectionStrength) { if (!tierResolved) { return 1f; } float num = ClampCookingFactor(cookingFactor); float num2 = ClampNormalizedTier(normalizedTier); return (float)Math.Exp(ClampTierSelectionStrength(highTierSelectionStrength) * num * num2); } internal static float GetInterpolatedMultiplierMode(float minimum, float maximum, float cookingLevelOneHundredMode, float cookingFactor) { minimum = ClampMultiplierMinimum(minimum); maximum = ClampMultiplierMaximum(maximum, minimum); float num = ClampMultiplierMode(cookingLevelOneHundredMode, minimum, maximum); return minimum + (num - minimum) * ClampCookingFactor(cookingFactor); } internal static float GetTriangularQuantile(float uniformSample, float normalizedMode) { double num = ClampUnit(uniformSample); double num2 = ClampUnit(normalizedMode); return ClampUnit((float)((num < num2) ? Math.Sqrt(num * num2) : (1.0 - Math.Sqrt((1.0 - num) * (1.0 - num2))))); } internal static float GetChefMultiplier(float minimum, float maximum, float uniformSample, float cookingFactor, float cookingLevelOneHundredMode) { minimum = ClampMultiplierMinimum(minimum); maximum = ClampMultiplierMaximum(maximum, minimum); if (minimum >= maximum) { return minimum; } double num = ((double)GetInterpolatedMultiplierMode(minimum, maximum, cookingLevelOneHundredMode, cookingFactor) - (double)minimum) / (double)(maximum - minimum); double num2 = GetTriangularQuantile(uniformSample, (float)num); return (float)((1.0 - num2) * (double)minimum + num2 * (double)maximum); } internal static int ChooseWeightedIndex(IReadOnlyList? weights, float uniformSample) { if (weights == null || weights.Count == 0) { return -1; } double num = 0.0; int num2 = -1; for (int i = 0; i < weights.Count; i++) { float num3 = weights[i]; if (!float.IsNaN(num3) && !float.IsInfinity(num3) && !(num3 <= 0f)) { num += (double)num3; num2 = i; } } if (num2 < 0 || num <= 0.0 || double.IsInfinity(num)) { return -1; } double num4 = (double)ClampUnit(uniformSample) * num; double num5 = 0.0; for (int j = 0; j < weights.Count; j++) { float num6 = weights[j]; if (!float.IsNaN(num6) && !float.IsInfinity(num6) && !(num6 <= 0f)) { num5 += (double)num6; if (num4 < num5) { return j; } } } return num2; } private static float ClampUnit(float value) { if (float.IsNaN(value) || value <= 0f) { return 0f; } if (!(value >= 1f)) { return value; } return 1f; } internal static float ClampMultiplierMinimum(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return Math.Max(1f, value); } return 1f; } internal static float ClampMultiplierMaximum(float value, float minimum) { minimum = ClampMultiplierMinimum(minimum); if (!float.IsNaN(value) && !float.IsInfinity(value)) { return Math.Max(minimum, value); } return minimum; } internal static float ClampMultiplierMode(float value, float minimum, float maximum) { minimum = ClampMultiplierMinimum(minimum); maximum = ClampMultiplierMaximum(maximum, minimum); if (float.IsNaN(value) || value <= minimum) { return minimum; } if (!float.IsPositiveInfinity(value) && !(value >= maximum)) { return value; } return maximum; } private static float ClampTierSelectionStrength(float value) { if (float.IsNaN(value) || value <= 0f) { return 0f; } if (!float.IsPositiveInfinity(value) && !(value >= 20f)) { return value; } return 20f; } } internal static class ChefCollectionService { private sealed class ChefCandidate { internal string Key { get; } internal int AxisIndex { get; } private ChefFoodTierInfo? TierInfo { get; } internal ChefCandidate(string key, ChefFoodTierInfo? tierInfo, FoodStatAxis axis) { Key = key; TierInfo = tierInfo; AxisIndex = GetFoodStatAxisIndex(axis); } internal float GetSelectionWeight(float cookingFactor) { return ChefChoiceMath.GetFoodSelectionWeight(cookingFactor, TierInfo?.NormalizedTier ?? 0f, TierInfo?.IsResolved ?? false, DietConfig.GetChefHighTierSelectionStrength()); } } private readonly struct RecentFoodComposition { private readonly int _health; private readonly int _stamina; private readonly int _eitr; private readonly int _total; internal RecentFoodComposition(int health, int stamina, int eitr) { _health = Math.Max(0, health); _stamina = Math.Max(0, stamina); _eitr = Math.Max(0, eitr); _total = _health + _stamina + _eitr; } internal float GetShare(int axisIndex) { if (_total <= 0) { return 0f; } return (float)(axisIndex switch { 0 => _health, 1 => _stamina, 2 => _eitr, _ => 0, }) / (float)_total; } } private static readonly object RandomLock = new object(); private static readonly Random RandomSource = new Random(); private const int FoodStatAxisCount = 3; internal static bool EnsureChefCollection(Player? player, PlayerFoodStateData state) { return EnsureChefCollection(player, state, null); } private static bool EnsureChefCollection(Player? player, PlayerFoodStateData state, string? excludedRefillKey) { if ((Object)(object)player == (Object)null || (Object)(object)ObjectDB.instance == (Object)null) { return false; } ChefFoodTierCatalog.Tick(); if (!ChefFoodTierCatalog.IsReady) { return false; } Dictionary foodAxesByKey = new Dictionary(StringComparer.Ordinal); List knownFoodCandidates = GetKnownFoodCandidates(player, foodAxesByKey); RecentFoodComposition recentFoodComposition = GetRecentFoodComposition(state, foodAxesByKey); HashSet eligibleSet = new HashSet(StringComparer.Ordinal); foreach (ChefCandidate item in knownFoodCandidates) { eligibleSet.Add(item.Key); } bool flag = false; int count = state.Chef.Count; state.Chef.RemoveAll((ChefEntryData entry) => entry == null || string.IsNullOrWhiteSpace(entry.Key) || !eligibleSet.Contains(entry.Key)); flag |= state.Chef.Count != count; HashSet hashSet = new HashSet(StringComparer.Ordinal); for (int num = state.Chef.Count - 1; num >= 0; num--) { if (!hashSet.Add(state.Chef[num].Key)) { state.Chef.RemoveAt(num); flag = true; } } int num2 = Mathf.Min(DietConfig.GetChefCollectionSize(), knownFoodCandidates.Count); float cookingFactor = ChefChoiceMath.ClampCookingFactor(((Character)player).GetSkillFactor((SkillType)105)); ChefCandidate selected; while (state.Chef.Count < num2 && (TrySelectWeightedCandidate(knownFoodCandidates, hashSet, excludedRefillKey, cookingFactor, recentFoodComposition, out selected) || (!string.IsNullOrWhiteSpace(excludedRefillKey) && TrySelectWeightedCandidate(knownFoodCandidates, hashSet, null, cookingFactor, recentFoodComposition, out selected)))) { state.Chef.Add(new ChefEntryData { Key = selected.Key, Multiplier = RollChefMultiplier(cookingFactor) }); hashSet.Add(selected.Key); flag = true; } while (state.Chef.Count > num2) { state.Chef.RemoveAt(state.Chef.Count - 1); flag = true; } return flag; } internal static bool TryConsumeChefEntry(Player player, PlayerFoodStateData state, string key, out float multiplier) { EnsureChefCollection(player, state); for (int i = 0; i < state.Chef.Count; i++) { ChefEntryData chefEntryData = state.Chef[i]; if (!(chefEntryData.Key != key)) { multiplier = chefEntryData.Multiplier; state.Chef.RemoveAt(i); return true; } } multiplier = 1f; return false; } internal static bool RefillAfterConsumption(Player player, PlayerFoodStateData state, string consumedKey) { return EnsureChefCollection(player, state, consumedKey); } internal static ChefEntryData? GetEntry(PlayerFoodStateData state, string key) { if (SpoilagePolicy.IsChefChoiceBlacklisted(key)) { return null; } foreach (ChefEntryData item in state.Chef) { if (item.Key == key) { return item; } } return null; } internal static void RerollAll(Player? player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)ObjectDB.instance == (Object)null)) { PlayerFoodStateData state = FoodStateStore.GetState(player); state.Chef.Clear(); EnsureChefCollection(player, state); FoodStateStore.SaveState(player, state); } } internal static void RotateOldest(Player? player, int count) { if ((Object)(object)player == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || count <= 0) { return; } PlayerFoodStateData state = FoodStateStore.GetState(player); bool flag = EnsureChefCollection(player, state); for (int i = 0; i < count; i++) { if (state.Chef.Count <= 0) { break; } string key = state.Chef[0].Key; state.Chef.RemoveAt(0); flag = true; flag |= EnsureChefCollection(player, state, key); } if (flag) { FoodStateStore.SaveState(player, state); } } private static bool TrySelectWeightedCandidate(IReadOnlyList eligible, ISet currentKeys, string? excludedKey, float cookingFactor, RecentFoodComposition recentFoodComposition, out ChefCandidate selected) { List list = new List(); List list2 = new List(); float[] array = new float[3]; foreach (ChefCandidate item in eligible) { if (!currentKeys.Contains(item.Key) && !item.Key.Equals(excludedKey, StringComparison.Ordinal)) { list.Add(item); float selectionWeight = item.GetSelectionWeight(cookingFactor); list2.Add(selectionWeight); array[item.AxisIndex] += selectionWeight; } } if (list.Count == 0) { selected = null; return false; } float num = 0f; float num2 = 0f; for (int i = 0; i < 3; i++) { if (!(array[i] <= 0f)) { num += array[i]; num2 += recentFoodComposition.GetShare(i); } } List list3 = new List(list.Count); float percentage = ((num2 > 0f) ? DietConfig.GetChefRecentFoodPreferencePercent() : 0f); for (int j = 0; j < list.Count; j++) { ChefCandidate chefCandidate = list[j]; float num3 = array[chefCandidate.AxisIndex]; float num4 = ((num > 0f) ? (num3 / num) : 0f); float historyProbability = ((num2 > 0f) ? (recentFoodComposition.GetShare(chefCandidate.AxisIndex) / num2) : num4); float num5 = ChefChoiceMath.BlendCategoryProbability(num4, historyProbability, percentage); list3.Add((num3 > 0f) ? (num5 * list2[j] / num3) : 0f); } float uniformSample; lock (RandomLock) { uniformSample = (float)RandomSource.NextDouble(); } int num6 = ChefChoiceMath.ChooseWeightedIndex(list3, uniformSample); if (num6 < 0) { selected = null; return false; } selected = list[num6]; return true; } private static List GetKnownFoodCandidates(Player player, IDictionary foodAxesByKey) { List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (ChefFoodTierInfo item in ChefFoodTierCatalog.GetSnapshot()) { string prefabName = item.PrefabName; if (!string.IsNullOrWhiteSpace(prefabName) && item.Axis != FoodStatAxis.None) { foodAxesByKey[prefabName] = item.Axis; if ((player.IsRecipeKnown(item.ItemNameToken) || player.IsKnownMaterial(item.ItemNameToken)) && !SpoilagePolicy.IsChefChoiceBlacklisted(prefabName) && hashSet.Add(prefabName)) { list.Add(new ChefCandidate(prefabName, item, item.Axis)); } } } list.Sort((ChefCandidate left, ChefCandidate right) => StringComparer.Ordinal.Compare(left.Key, right.Key)); return list; } private static RecentFoodComposition GetRecentFoodComposition(PlayerFoodStateData state, IReadOnlyDictionary foodAxesByKey) { int num = 0; int num2 = 0; int num3 = 0; foreach (HistoryEntryData item in state.Recent) { if (item != null && foodAxesByKey.TryGetValue(item.Key, out var value)) { switch (value) { case FoodStatAxis.Health: num++; break; case FoodStatAxis.Stamina: num2++; break; case FoodStatAxis.Eitr: num3++; break; } } } return new RecentFoodComposition(num, num2, num3); } private static float RollChefMultiplier(float cookingFactor) { float uniformSample; lock (RandomLock) { uniformSample = (float)RandomSource.NextDouble(); } return ChefChoiceMath.GetChefMultiplier(DietConfig.GetChefMultiplierMin(), DietConfig.GetChefMultiplierMax(), uniformSample, cookingFactor, DietConfig.GetChefMultiplierModeAtMaxCooking()); } private static int GetFoodStatAxisIndex(FoodStatAxis axis) { return axis switch { FoodStatAxis.Health => 0, FoodStatAxis.Stamina => 1, FoodStatAxis.Eitr => 2, _ => throw new ArgumentOutOfRangeException("axis", axis, "Chef food must have a classified food-stat axis."), }; } } internal sealed class ChefFoodTierInfo { internal string PrefabName { get; } internal int Tier { get; } internal int TierCount { get; } internal string TierName { get; } internal string FallbackReason { get; } internal IReadOnlyList Sources { get; } internal string ItemNameToken { get; private set; } = ""; internal FoodStatAxis Axis { get; private set; } internal bool IsResolved => Tier >= 0; internal bool IsAllTier => !IsResolved; internal float NormalizedTier { get { if (IsResolved && TierCount > 1) { return Mathf.Clamp01((float)Tier / (float)(TierCount - 1)); } return 0f; } } internal ChefFoodTierInfo(string prefabName, int tier, int tierCount, string tierName, string fallbackReason, IEnumerable? sources) { PrefabName = ChefFoodTierCatalog.NormalizePrefabIdentity(prefabName); Tier = tier; TierCount = Math.Max(1, tierCount); TierName = tierName ?? ""; FallbackReason = fallbackReason ?? ""; Sources = (from source in (sources ?? Enumerable.Empty()).Select(ChefFoodTierCatalog.NormalizePrefabIdentity) where source.Length > 0 select source).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy((string source) => source, StringComparer.OrdinalIgnoreCase).ThenBy((string source) => source, StringComparer.Ordinal) .ToArray(); } internal ChefFoodTierInfo WithFoodIdentity(string? itemNameToken, FoodStatAxis axis) { ItemNameToken = itemNameToken ?? ""; Axis = axis; return this; } } internal static class ChefFoodTierCatalog { private sealed class FoodNode { internal string PrefabName { get; } internal ItemDrop? Item { get; set; } internal int DirectTier { get; set; } = -1; internal int Tier { get; set; } = -1; internal HashSet Dependencies { get; } = new HashSet(StringComparer.OrdinalIgnoreCase); internal FoodNode(string prefabName) { PrefabName = prefabName; } } internal const int AllTier = -1; internal const string NoRecipeReason = "no_recipe_or_conversion"; internal const string UnmappedSourcesReason = "unmapped_sources"; internal const string CyclicProductionReason = "cyclic_production_path"; private const float RefreshIntervalSeconds = 1f; private static Dictionary _byPrefab = new Dictionary(StringComparer.OrdinalIgnoreCase); private static bool _dirty = true; private static float _nextRefreshAt; private static bool _failureLogged; private static int _builtResourceMapVersion = -1; internal static bool IsReady { get; private set; } internal static int Version { get; private set; } internal static string NormalizePrefabIdentity(string? value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } string text = value.Trim(); if (!text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { return text; } return text.Substring(0, text.Length - "(Clone)".Length).TrimEnd(Array.Empty()); } internal static void Tick() { if (!ChefResourceMapPolicy.TryGetSnapshot(out ChefResourceMapSnapshot snapshot)) { IsReady = false; return; } int version = ChefResourceMapPolicy.Version; if (_builtResourceMapVersion != version) { _dirty = true; _nextRefreshAt = 0f; IsReady = false; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextRefreshAt) { return; } _nextRefreshAt = realtimeSinceStartup + 1f; if (!_dirty) { return; } ObjectDB instance = ObjectDB.instance; ZNetScene instance2 = ZNetScene.instance; if (instance?.m_items == null || instance.m_recipes == null || instance2?.m_namedPrefabs == null || instance2.m_prefabs == null || instance2.m_nonNetViewPrefabs == null) { return; } try { Dictionary dictionary = BuildCatalog(instance, instance2, snapshot); if (!ChefResourceMapPolicy.TryGetSnapshot(out ChefResourceMapSnapshot snapshot2) || snapshot != snapshot2 || version != ChefResourceMapPolicy.Version) { _dirty = true; _nextRefreshAt = 0f; IsReady = false; return; } bool num = !CatalogEquals(_byPrefab, dictionary); _byPrefab = dictionary; _dirty = false; IsReady = true; _builtResourceMapVersion = version; _failureLogged = false; if (num) { Version++; ChefTierReferenceGenerator.Invalidate(); } } catch (Exception ex) { if (!_failureLogged) { _failureLogged = true; FineDiningPlugin.Log.LogWarning((object)("Could not build the Chef food tier catalog; FineDining will retry: " + ex.GetBaseException().Message)); } } } internal static void Invalidate() { _dirty = true; _nextRefreshAt = 0f; IsReady = false; } internal static void Reset() { _byPrefab = new Dictionary(StringComparer.OrdinalIgnoreCase); _dirty = true; _nextRefreshAt = 0f; _failureLogged = false; _builtResourceMapVersion = -1; IsReady = false; Version = 0; } internal static IReadOnlyList GetSnapshot() { if (IsReady) { return _byPrefab.Values.OrderBy((ChefFoodTierInfo info) => info.PrefabName, StringComparer.OrdinalIgnoreCase).ThenBy((ChefFoodTierInfo info) => info.PrefabName, StringComparer.Ordinal).ToArray(); } return Array.Empty(); } private static Dictionary BuildCatalog(ObjectDB objectDb, ZNetScene scene, ChefResourceMapSnapshot resourceMap) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (GameObject item in objectDb.m_items) { RegisterItem(dictionary, ((Object)(object)item != (Object)null) ? item.GetComponent() : null, resourceMap); } foreach (Recipe recipe in objectDb.m_recipes) { if ((Object)(object)recipe?.m_item == (Object)null) { continue; } FoodNode foodNode = RegisterItem(dictionary, recipe.m_item, resourceMap); if (foodNode == null || recipe.m_resources == null) { continue; } Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (!((Object)(object)val?.m_resItem == (Object)null) && val.m_amount > 0) { FoodNode foodNode2 = RegisterItem(dictionary, val.m_resItem, resourceMap); if (foodNode2 != null) { foodNode.Dependencies.Add(foodNode2.PrefabName); } } } } AddFeastProductionPaths(dictionary, objectDb, resourceMap); foreach (GameObject item2 in CollectScenePrefabs(scene)) { if ((Object)(object)item2 == (Object)null) { continue; } CookingStation[] array; try { array = item2.GetComponentsInChildren(true); } catch { array = Array.Empty(); } CookingStation[] array2 = array; foreach (CookingStation val2 in array2) { if (val2?.m_conversion == null) { continue; } foreach (ItemConversion item3 in val2.m_conversion) { RegisterConversion(dictionary, item3?.m_from, item3?.m_to, resourceMap); } } Fermenter[] array3; try { array3 = item2.GetComponentsInChildren(true); } catch { array3 = Array.Empty(); } Fermenter[] array4 = array3; foreach (Fermenter val3 in array4) { if (val3?.m_conversion == null) { continue; } foreach (ItemConversion item4 in val3.m_conversion) { RegisterConversion(dictionary, item4?.m_from, item4?.m_to, resourceMap); } } } foreach (FoodNode value2 in dictionary.Values) { value2.Tier = value2.DirectTier; } for (int j = 0; j < dictionary.Count; j++) { bool flag = false; foreach (FoodNode value3 in dictionary.Values) { int num = value3.Tier; foreach (string dependency in value3.Dependencies) { if (dictionary.TryGetValue(dependency, out var value)) { num = Math.Max(num, value.Tier); } } if (num > value3.Tier) { value3.Tier = num; flag = true; } } if (!flag) { break; } } Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (FoodNode value4 in dictionary.Values) { if (value4.Item?.m_itemData != null && FoodIdentity.TryGetFoodStatAxis(value4.Item.m_itemData, out var axis)) { string name = value4.Item.m_itemData.m_shared.m_name; if (value4.Tier >= 0) { dictionary2[value4.PrefabName] = new ChefFoodTierInfo(value4.PrefabName, value4.Tier, resourceMap.TierCount, resourceMap.GetTierName(value4.Tier), "", Array.Empty()).WithFoodIdentity(name, axis); continue; } ResolveFallback(value4, dictionary, out string reason, out IReadOnlyList sources); dictionary2[value4.PrefabName] = new ChefFoodTierInfo(value4.PrefabName, -1, resourceMap.TierCount, "AllTier", reason, sources).WithFoodIdentity(name, axis); } } return dictionary2; } private static void RegisterConversion(Dictionary nodes, ItemDrop? from, ItemDrop? to, ChefResourceMapSnapshot resourceMap) { FoodNode foodNode = RegisterItem(nodes, from, resourceMap); FoodNode foodNode2 = RegisterItem(nodes, to, resourceMap); if (foodNode != null) { foodNode2?.Dependencies.Add(foodNode.PrefabName); } } private static void AddFeastProductionPaths(Dictionary nodes, ObjectDB objectDb, ChefResourceMapSnapshot resourceMap) { //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Invalid comparison between Unknown and I4 //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Invalid comparison between Unknown and I4 //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Invalid comparison between Unknown and I4 //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Invalid comparison between Unknown and I4 HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); List<(ItemDrop, ItemDrop)> list = new List<(ItemDrop, ItemDrop)>(); foreach (GameObject item in objectDb.m_items) { ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent() : null); if (val?.m_itemData?.m_shared == null) { continue; } ItemDrop val2 = ((Component)val).GetComponent()?.m_foodItem; SharedData val3 = val2?.m_itemData?.m_shared; if (!((Object)(object)val2 == (Object)null) && val3 != null && !GetItemPrefabName(val).Equals(GetItemPrefabName(val2), StringComparison.OrdinalIgnoreCase)) { bool num = (Object)(object)((Component)val2).GetComponent() != (Object)null || (int)val3.m_itemType == 2 || LooksLikeFeastRoutingFood(val3); bool flag = (int)val.m_itemData.m_shared.m_itemType == 1; bool flag2 = (int)val.m_itemData.m_shared.m_itemType != 2 && !LooksLikeFeastRoutingFood(val.m_itemData.m_shared); if (num && (flag || flag2)) { list.Add((val, val2)); hashSet.Add(GetItemPrefabName(val2)); } } } foreach (var (val4, to) in list) { RegisterConversion(nodes, val4, to, resourceMap); } foreach (GameObject item2 in objectDb.m_items) { ItemDrop val5 = (((Object)(object)item2 != (Object)null) ? item2.GetComponent() : null); SharedData val6 = val5?.m_itemData?.m_shared; ItemDrop val7 = val6?.m_appendToolTip; if (!((Object)(object)val5 == (Object)null) && val7?.m_itemData?.m_shared != null && (int)val6.m_itemType == 1) { string itemPrefabName = GetItemPrefabName(val7); if (hashSet.Contains(itemPrefabName) || (Object)(object)((Component)val7).GetComponent() != (Object)null) { RegisterConversion(nodes, val5, val7, resourceMap); } } } } private static bool LooksLikeFeastRoutingFood(SharedData shared) { if (!(shared.m_food > 0f) && !(shared.m_foodStamina > 0f) && !(shared.m_foodEitr > 0f)) { return shared.m_isDrink; } return true; } private static IReadOnlyList CollectScenePrefabs(ZNetScene scene) { List list = new List(); HashSet seen = new HashSet(); AddScenePrefabs(scene.m_namedPrefabs.Values, list, seen); AddScenePrefabs(scene.m_prefabs, list, seen); AddScenePrefabs(scene.m_nonNetViewPrefabs, list, seen); return list; } private static void AddScenePrefabs(IEnumerable source, ICollection target, ISet seen) { foreach (GameObject item in source) { if ((Object)(object)item != (Object)null && seen.Add(((Object)item).GetInstanceID())) { target.Add(item); } } } private static FoodNode? RegisterItem(Dictionary nodes, ItemDrop? item, ChefResourceMapSnapshot resourceMap) { if (item?.m_itemData?.m_shared == null) { return null; } string itemPrefabName = GetItemPrefabName(item); if (itemPrefabName.Length == 0) { return null; } if (!nodes.TryGetValue(itemPrefabName, out FoodNode value)) { value = new FoodNode(itemPrefabName); nodes.Add(itemPrefabName, value); } FoodNode foodNode = value; if (foodNode.Item == null) { ItemDrop val = (foodNode.Item = item); } value.DirectTier = Math.Max(value.DirectTier, GetDirectTier(item, resourceMap)); return value; } private static string GetItemPrefabName(ItemDrop item) { string text = FoodIdentity.GetCanonicalPrefabName(item.m_itemData); if (text.Length == 0 && (Object)(object)((Component)item).gameObject != (Object)null) { text = FoodIdentity.NormalizePrefabName(((Object)((Component)item).gameObject).name); } return text; } private static int GetDirectTier(ItemDrop item, ChefResourceMapSnapshot resourceMap) { int num = -1; foreach (string localeIndependentToken in GetLocaleIndependentTokens(item)) { if (resourceMap.TryGetResourceTier(localeIndependentToken, out var tier)) { num = Math.Max(num, tier); } } return num; } private static IEnumerable GetLocaleIndependentTokens(ItemDrop item) { string itemPrefabName = GetItemPrefabName(item); string text = (((Object)(object)((Component)item).gameObject != (Object)null) ? FoodIdentity.NormalizePrefabName(((Object)((Component)item).gameObject).name) : ""); string text2 = item.m_itemData?.m_shared?.m_name ?? ""; string[] array = new string[3] { itemPrefabName, text, text2 }; for (int i = 0; i < array.Length; i++) { string text3 = ChefResourceMapPolicy.NormalizeResourceToken(array[i]); if (text3.Length > 0) { yield return text3; } } } private static void ResolveFallback(FoodNode root, IReadOnlyDictionary nodes, out string reason, out IReadOnlyList sources) { if (root.Dependencies.Count == 0) { reason = "no_recipe_or_conversion"; sources = Array.Empty(); return; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); HashSet hashSet2 = new HashSet(StringComparer.OrdinalIgnoreCase); CollectUnresolvedSources(root.PrefabName, root.PrefabName, nodes, new HashSet(StringComparer.OrdinalIgnoreCase), hashSet, hashSet2); if (hashSet.Count > 0) { reason = "unmapped_sources"; sources = hashSet.OrderBy((string source) => source, StringComparer.OrdinalIgnoreCase).ThenBy((string source) => source, StringComparer.Ordinal).ToArray(); return; } reason = ((hashSet2.Count > 0) ? "cyclic_production_path" : "unmapped_sources"); sources = hashSet2.Where((string source) => !source.Equals(root.PrefabName, StringComparison.OrdinalIgnoreCase)).OrderBy((string source) => source, StringComparer.OrdinalIgnoreCase).ThenBy((string source) => source, StringComparer.Ordinal) .ToArray(); } private static void CollectUnresolvedSources(string current, string root, IReadOnlyDictionary nodes, HashSet path, HashSet leaves, HashSet cycleMembers) { if (!path.Add(current)) { cycleMembers.Add(current); return; } if (!nodes.TryGetValue(current, out FoodNode value) || value.Dependencies.Count == 0) { if (!current.Equals(root, StringComparison.OrdinalIgnoreCase)) { leaves.Add(current); } path.Remove(current); return; } foreach (string dependency in value.Dependencies) { if (path.Contains(dependency)) { cycleMembers.Add(dependency); cycleMembers.Add(current); } else { CollectUnresolvedSources(dependency, root, nodes, path, leaves, cycleMembers); } } path.Remove(current); } private static bool CatalogEquals(IReadOnlyDictionary left, IReadOnlyDictionary right) { if (left.Count != right.Count) { return false; } foreach (KeyValuePair item in left) { if (!right.TryGetValue(item.Key, out ChefFoodTierInfo value) || !item.Value.PrefabName.Equals(value.PrefabName, StringComparison.Ordinal) || item.Value.Tier != value.Tier || item.Value.TierCount != value.TierCount || !item.Value.TierName.Equals(value.TierName, StringComparison.Ordinal) || !item.Value.FallbackReason.Equals(value.FallbackReason, StringComparison.Ordinal) || !item.Value.ItemNameToken.Equals(value.ItemNameToken, StringComparison.Ordinal) || item.Value.Axis != value.Axis || !item.Value.Sources.SequenceEqual(value.Sources, StringComparer.Ordinal)) { return false; } } return true; } } internal sealed class ChefResourceMapSnapshot { private readonly string[] _tierNames; private readonly Dictionary _resourceTiers; internal int TierCount => _tierNames.Length; internal int ResourceCount => _resourceTiers.Count; internal IReadOnlyList TierNames => _tierNames; internal ChefResourceMapSnapshot(IEnumerable tierNames, IDictionary resourceTiers) { _tierNames = (tierNames ?? Enumerable.Empty()).ToArray(); _resourceTiers = new Dictionary(resourceTiers ?? new Dictionary(), StringComparer.OrdinalIgnoreCase); } internal bool TryGetResourceTier(string? token, out int tier) { return _resourceTiers.TryGetValue(ChefResourceMapPolicy.NormalizeResourceToken(token), out tier); } internal string GetTierName(int tier) { if (tier < 0 || tier >= _tierNames.Length) { return "AllTier"; } return _tierNames[tier]; } internal bool ContentEquals(ChefResourceMapSnapshot? other) { if (other == null || !_tierNames.SequenceEqual(other._tierNames, StringComparer.Ordinal) || _resourceTiers.Count != other._resourceTiers.Count) { return false; } foreach (KeyValuePair resourceTier in _resourceTiers) { if (!other._resourceTiers.TryGetValue(resourceTier.Key, out var value) || value != resourceTier.Value) { return false; } } return true; } } internal static class ChefResourceMapPolicy { private enum AuthorityMode { Unknown, LocalFiles, SyncedOnly } internal const string ResourceMapFileName = "ResourceMap.yml"; internal const string DefaultResourceMapResourceName = "FineDining.Resources.Defaults.ResourceMap.yml"; internal const string SyncedYamlIdentifier = "finedining_resource_map_yaml"; private const double ReloadDebounceMilliseconds = 350.0; private static readonly Lazy DefaultResourceMap = new Lazy(LoadDefaultResourceMapYaml); private static ConfigSync? _configSync; private static ConfigSync? _registeredConfigSync; private static CustomSyncedValue? _syncedYaml; private static FileSystemWatcher? _watcher; private static System.Timers.Timer? _reloadTimer; private static AuthorityMode _authorityMode; private static ChefResourceMapSnapshot? _snapshot; private static bool _isReady; internal static string ConfigDirectoryPath => SpoilagePolicy.ConfigDirectoryPath; internal static string ResourceMapFilePath => Path.Combine(ConfigDirectoryPath, "ResourceMap.yml"); internal static string DefaultResourceMapYaml => DefaultResourceMap.Value; internal static bool IsReady { get { if (_isReady) { return _snapshot != null; } return false; } } internal static int Version { get; private set; } internal static void Initialize(ConfigSync sync) { if (sync == null) { throw new ArgumentNullException("sync"); } Shutdown(); _configSync = sync; if (_syncedYaml == null) { _syncedYaml = new CustomSyncedValue(sync, "finedining_resource_map_yaml", string.Empty); _registeredConfigSync = sync; } else if (_registeredConfigSync != sync) { throw new InvalidOperationException("ResourceMap.yml cannot be rebound to a different ConfigSync instance."); } _syncedYaml.ValueChanged += OnSyncedYamlChanged; _configSync.SourceOfTruthChanged += OnSourceOfTruthChanged; RefreshAuthority(force: true); } internal static void Shutdown() { DisposeWatcher(); if (_syncedYaml != null) { _syncedYaml.ValueChanged -= OnSyncedYamlChanged; } if (_configSync != null) { _configSync.SourceOfTruthChanged -= OnSourceOfTruthChanged; _configSync = null; } _authorityMode = AuthorityMode.Unknown; _snapshot = null; _isReady = false; Version = 0; } internal static void RefreshAuthority(bool force = false) { if (_configSync == null) { return; } AuthorityMode authorityMode = (UsesLocalAuthorityFiles() ? AuthorityMode.LocalFiles : AuthorityMode.SyncedOnly); bool flag = authorityMode != _authorityMode; if (!force && !flag) { return; } if (flag) { _snapshot = null; _isReady = false; DietModule.InvalidateChefTierCatalog(); } _authorityMode = authorityMode; switch (authorityMode) { case AuthorityMode.LocalFiles: try { SetupWatcher(); } catch (Exception ex) { DisposeWatcher(); FineDiningPlugin.Log.LogWarning((object)("Could not watch " + ResourceMapFilePath + "; startup loading remains available. " + ex.GetBaseException().Message)); } ReloadFromDiskAndSync(); break; case AuthorityMode.SyncedOnly: DisposeWatcher(); break; } } internal static bool TryGetSnapshot(out ChefResourceMapSnapshot snapshot) { if (IsReady) { snapshot = _snapshot; return true; } snapshot = null; return false; } internal static bool TryParseResourceMapYaml(string yaml, out ChefResourceMapSnapshot? snapshot, out string error) { snapshot = null; error = string.Empty; try { snapshot = ParseResourceMapYaml(yaml); return true; } catch (Exception ex) { error = ex.GetBaseException().Message; return false; } } internal static ChefResourceMapSnapshot ParseResourceMapYaml(string yaml) { if (string.IsNullOrWhiteSpace(yaml)) { throw new InvalidDataException("ResourceMap.yml cannot be empty."); } YamlStream yamlStream = new YamlStream(); using (StringReader input = new StringReader(yaml)) { yamlStream.Load(input); } if (yamlStream.Documents.Count != 1) { throw new InvalidDataException("ResourceMap.yml must contain exactly one YAML document."); } YamlMappingNode obj = (yamlStream.Documents[0].RootNode as YamlMappingNode) ?? throw new InvalidDataException("ResourceMap.yml root must map tier names to resource lists."); if (obj.Children.Count == 0) { throw new InvalidDataException("ResourceMap.yml must contain at least one tier."); } List list = new List(obj.Children.Count); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair child in obj.Children) { if (!(child.Key is YamlScalarNode yamlScalarNode) || string.IsNullOrWhiteSpace(yamlScalarNode.Value)) { throw new InvalidDataException("ResourceMap.yml contains an empty or structured tier name."); } string text = yamlScalarNode.Value.Trim(); if (!hashSet.Add(text)) { throw new InvalidDataException("ResourceMap.yml tier '" + text + "' is duplicated with different casing."); } YamlSequenceNode obj2 = (child.Value as YamlSequenceNode) ?? throw new InvalidDataException("ResourceMap.yml tier '" + text + "' must contain a YAML sequence."); int count = list.Count; list.Add(text); foreach (YamlNode child2 in obj2.Children) { if (!(child2 is YamlScalarNode yamlScalarNode2) || string.IsNullOrWhiteSpace(yamlScalarNode2.Value)) { throw new InvalidDataException("ResourceMap.yml tier '" + text + "' contains an empty or structured resource."); } string text2 = NormalizeResourceToken(yamlScalarNode2.Value); if (text2.Length == 0) { throw new InvalidDataException("ResourceMap.yml tier '" + text + "' contains a resource with no usable token."); } if (!dictionary.ContainsKey(text2)) { dictionary.Add(text2, count); } } } return new ChefResourceMapSnapshot(list, dictionary); } internal static string NormalizeResourceToken(string? value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } string text = value.Trim(); if (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - "(Clone)".Length).TrimEnd(Array.Empty()); } if (text.StartsWith("$item_", StringComparison.OrdinalIgnoreCase)) { text = text.Substring("$item_".Length); } else if (text.StartsWith("$", StringComparison.Ordinal)) { text = text.Substring(1); } return new string(text.Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray()); } private static bool UsesLocalAuthorityFiles() { ConfigSync? configSync = _configSync; if (configSync == null || !configSync.IsSourceOfTruth) { return false; } if (ZNet.HasServerHost()) { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } return true; } private static void OnSourceOfTruthChanged(bool _) { RefreshAuthority(force: true); } private static void SetupWatcher() { EnsureLocalResourceMapExists(); if (_watcher == null) { _reloadTimer = new System.Timers.Timer(350.0) { AutoReset = false, SynchronizingObject = ThreadingHelper.SynchronizingObject }; _reloadTimer.Elapsed += OnReloadTimerElapsed; _watcher = new FileSystemWatcher(ConfigDirectoryPath, "*.yml") { IncludeSubdirectories = false, SynchronizingObject = ThreadingHelper.SynchronizingObject, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime) }; _watcher.Changed += OnResourceMapFileChanged; _watcher.Created += OnResourceMapFileChanged; _watcher.Deleted += OnResourceMapFileChanged; _watcher.Renamed += OnResourceMapFileChanged; _watcher.EnableRaisingEvents = true; } } private static void DisposeWatcher() { if (_watcher != null) { _watcher.EnableRaisingEvents = false; _watcher.Changed -= OnResourceMapFileChanged; _watcher.Created -= OnResourceMapFileChanged; _watcher.Deleted -= OnResourceMapFileChanged; _watcher.Renamed -= OnResourceMapFileChanged; _watcher.Dispose(); _watcher = null; } if (_reloadTimer != null) { _reloadTimer.Stop(); _reloadTimer.Elapsed -= OnReloadTimerElapsed; _reloadTimer.Dispose(); _reloadTimer = null; } } private static void OnResourceMapFileChanged(object sender, FileSystemEventArgs args) { if (_authorityMode == AuthorityMode.LocalFiles && _reloadTimer != null && IsResourceMapFileChange(args)) { _reloadTimer.Stop(); _reloadTimer.Start(); } } private static bool IsResourceMapFileChange(FileSystemEventArgs args) { if (IsResourceMapFilePath(args.FullPath)) { return true; } if (args is RenamedEventArgs e) { return IsResourceMapFilePath(e.OldFullPath); } return false; } private static bool IsResourceMapFilePath(string path) { return Path.GetFileName(path).Equals("ResourceMap.yml", StringComparison.OrdinalIgnoreCase); } private static void OnReloadTimerElapsed(object sender, ElapsedEventArgs args) { if (_authorityMode == AuthorityMode.LocalFiles) { ReloadFromDiskAndSync(); } } private static void ReloadFromDiskAndSync() { if (_authorityMode != AuthorityMode.LocalFiles) { return; } try { EnsureLocalResourceMapExists(); if (!ApplyYamlText(File.ReadAllText(ResourceMapFilePath), publish: true, ResourceMapFilePath) && !IsReady) { ApplyBuiltInFallback(); } } catch (Exception ex) { FineDiningPlugin.Log.LogError((object)("Could not reload " + ResourceMapFilePath + "; keeping the last-known-good resource map. " + ex.GetBaseException().Message)); if (!IsReady) { ApplyBuiltInFallback(); } } } private static void ApplyBuiltInFallback() { try { ApplyYamlText(DefaultResourceMapYaml, publish: true, "embedded FineDining ResourceMap.yml"); } catch (Exception ex) { FineDiningPlugin.Log.LogError((object)("Could not load the embedded FineDining ResourceMap.yml: " + ex.GetBaseException().Message)); } } private static void ApplyCurrentSyncedYaml() { string text = _syncedYaml?.Value ?? string.Empty; if (!string.IsNullOrWhiteSpace(text)) { ApplyYamlText(text, publish: false, "server-synced ResourceMap.yml"); } } private static void OnSyncedYamlChanged() { if (_authorityMode == AuthorityMode.SyncedOnly) { ApplyCurrentSyncedYaml(); } } private static bool ApplyYamlText(string yaml, bool publish, string source) { if (!TryParseResourceMapYaml(yaml, out ChefResourceMapSnapshot snapshot, out string error)) { FineDiningPlugin.Log.LogError((object)("Could not parse " + source + "; keeping the last-known-good resource map. " + error)); return false; } string text = NormalizeYamlText(yaml); CommitSnapshot(snapshot); if (publish && _syncedYaml != null && !string.Equals(_syncedYaml.Value ?? string.Empty, text, StringComparison.Ordinal)) { _syncedYaml.AssignLocalValue(text); } return true; } private static void CommitSnapshot(ChefResourceMapSnapshot snapshot) { bool num = _snapshot == null || !_snapshot.ContentEquals(snapshot); _snapshot = snapshot; _isReady = true; if (num) { Version++; DietModule.InvalidateChefTierCatalog(); FineDiningPlugin.Log.LogInfo((object)($"Applied ResourceMap.yml with {snapshot.TierCount} tier(s) and " + $"{snapshot.ResourceCount} unique resource token(s).")); } } private static void EnsureLocalResourceMapExists() { EnsureDefaultFileExists(ResourceMapFilePath); } internal static void EnsureDefaultFileExists(string path) { if (string.IsNullOrWhiteSpace(path)) { throw new ArgumentException("A ResourceMap.yml path is required.", "path"); } string directoryName = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } if (File.Exists(path)) { return; } try { using FileStream stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.Read); using StreamWriter streamWriter = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); streamWriter.Write(DefaultResourceMapYaml); } catch (IOException) when (File.Exists(path)) { } } private static string LoadDefaultResourceMapYaml() { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("FineDining.Resources.Defaults.ResourceMap.yml"); if (stream == null) { throw new InvalidOperationException("Embedded resource 'FineDining.Resources.Defaults.ResourceMap.yml' was not found."); } using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true); return NormalizeYamlText(streamReader.ReadToEnd()); } private static string NormalizeYamlText(string yaml) { return (yaml ?? string.Empty).Replace("\r\n", "\n").Replace('\r', '\n').TrimEnd(new char[1] { '\n' }) + "\n"; } } internal static class ChefTierReferenceGenerator { internal sealed class ReferenceEntry { internal ChefFoodTierInfo Info { get; } internal string OwnerName { get; } internal ReferenceEntry(ChefFoodTierInfo info, string ownerName) { Info = info; OwnerName = FoodPrefabOwnerResolver.NormalizeOwnerName(ownerName); } } private readonly struct ReferenceGeneration { internal bool Changed { get; } internal bool HasUnknownOwner { get; } internal int EntryCount { get; } internal int UnassignedCount { get; } internal ReferenceGeneration(bool changed, bool hasUnknownOwner, int entryCount, int unassignedCount) { Changed = changed; HasUnknownOwner = hasUnknownOwner; EntryCount = entryCount; UnassignedCount = unassignedCount; } } internal const string ReferenceFileName = "FoodTier.reference.yml"; private const float ReadyRetrySeconds = 1f; private const float FailureRetrySeconds = 5f; private const float ExistenceCheckSeconds = 5f; private const int OwnerResolutionRetryCount = 3; private static bool _dirty = true; private static bool _failureLogged; private static float _nextAttemptAt; private static float _nextExistenceCheckAt; private static int _ownerResolutionRetriesRemaining = 3; private static readonly FoodStatAxis[] OrderedFoodAxes = new FoodStatAxis[3] { FoodStatAxis.Health, FoodStatAxis.Stamina, FoodStatAxis.Eitr }; private static string ReferenceFilePath => Path.Combine(SpoilagePolicy.ConfigDirectoryPath, "FoodTier.reference.yml"); internal static void Tick() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!_dirty) { if (realtimeSinceStartup < _nextExistenceCheckAt) { return; } _nextExistenceCheckAt = realtimeSinceStartup + 5f; if (!SpoilagePolicy.IsRuntimeReferenceAuthority || File.Exists(ReferenceFilePath)) { return; } _dirty = true; _ownerResolutionRetriesRemaining = 3; } if (realtimeSinceStartup < _nextAttemptAt) { return; } _nextAttemptAt = realtimeSinceStartup + 1f; if (!SpoilagePolicy.IsRuntimeReferenceAuthority || !ChefFoodTierCatalog.IsReady || !_dirty) { return; } if (!TryGenerateCurrentReference(out ReferenceGeneration result, out string error)) { _nextAttemptAt = Time.realtimeSinceStartup + 5f; if (!_failureLogged) { _failureLogged = true; FineDiningPlugin.Log.LogWarning((object)("Could not generate " + ReferenceFilePath + "; FineDining will retry: " + error)); } return; } if (result.HasUnknownOwner && _ownerResolutionRetriesRemaining > 0) { _ownerResolutionRetriesRemaining--; _dirty = true; _nextAttemptAt = realtimeSinceStartup + 5f; } else { _dirty = false; _ownerResolutionRetriesRemaining = 0; _nextExistenceCheckAt = realtimeSinceStartup + 5f; } _failureLogged = false; if (result.Changed) { FineDiningPlugin.Log.LogInfo((object)("Updated generated Chef tier reference with " + result.EntryCount + " prefab(s), including " + result.UnassignedCount + " unassigned: " + ReferenceFilePath)); } } internal static void Invalidate() { ResetGenerationState(resetFailureLog: false); } internal static void Reset() { ResetGenerationState(resetFailureLog: true); } private static bool TryGenerateCurrentReference(out ReferenceGeneration result, out string error) { result = default(ReferenceGeneration); if (!ChefFoodTierCatalog.IsReady) { error = "The synchronized Chef tier catalog is not ready yet. Wait until world loading finishes."; return false; } try { IReadOnlyList snapshot = ChefFoodTierCatalog.GetSnapshot(); FoodPrefabOwnerSnapshot owners = FoodPrefabOwnerResolver.GetSnapshot(snapshot.Select((ChefFoodTierInfo entry) => entry.PrefabName)); List list = snapshot.Select((ChefFoodTierInfo entry) => new ReferenceEntry(entry, owners.GetOwnerName(entry.PrefabName))).ToList(); bool changed = SpoilageReferenceGenerator.WriteTextIfChanged(ReferenceFilePath, BuildReferenceContent(list)); result = new ReferenceGeneration(changed, list.Any((ReferenceEntry entry) => entry.OwnerName.Equals("Unknown / Untracked", StringComparison.OrdinalIgnoreCase)), list.Count, list.Count((ReferenceEntry entry) => entry.Info.IsAllTier)); error = string.Empty; return true; } catch (Exception ex) { error = ex.GetBaseException().Message; return false; } } internal static string BuildReferenceContent(IEnumerable sourceEntries) { List source = (from @group in (sourceEntries ?? Enumerable.Empty()).Where((ReferenceEntry entry) => entry != null && entry.Info.PrefabName.Length > 0).GroupBy((ReferenceEntry entry) => entry.Info.PrefabName, StringComparer.OrdinalIgnoreCase) select @group.OrderBy((ReferenceEntry entry) => entry.OwnerName, StringComparer.OrdinalIgnoreCase).ThenBy((ReferenceEntry entry) => entry.OwnerName, StringComparer.Ordinal).First()).ToList(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("# Generated by FineDining ").Append("1.0.0").AppendLine(". This file is overwritten automatically."); stringBuilder.AppendLine("# It lists every Chef-eligible food by ResourceMap tier and food-stat category."); stringBuilder.AppendLine("# Unassigned foods remain eligible but use the neutral AllTier selection weight."); stringBuilder.AppendLine("# Positive Eitr is eitrFood; otherwise Health <= Stamina is staminaFood, else healthFood."); stringBuilder.AppendLine("# ResourceMap tiers with no Chef-eligible foods are omitted."); stringBuilder.AppendLine("# This file is diagnostic only; it is not loaded as configuration."); List entries = source.Where((ReferenceEntry entry) => entry.Info.IsAllTier).ToList(); AppendUnassigned(stringBuilder, entries); HashSet usedNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "unassigned" }; foreach (IGrouping item in from entry in source where entry.Info.IsResolved group entry by entry.Info.Tier into @group orderby @group.Key select @group) { AppendTier(stringBuilder, item, GetUniqueTierOutputName(item, usedNames)); } return Canonicalize(stringBuilder.ToString()); } private static void AppendUnassigned(StringBuilder builder, IReadOnlyCollection entries) { builder.AppendLine(); if (entries.Count == 0) { builder.AppendLine("unassigned: []"); return; } builder.AppendLine("unassigned:"); AppendCounts(builder, " ", entries); foreach (IGrouping item in OrderOwnerGroups(entries)) { builder.AppendLine(); builder.Append(" # ----- ").Append(FoodPrefabOwnerResolver.NormalizeOwnerName(item.Key)).AppendLine(" -----"); foreach (ReferenceEntry item2 in item.OrderBy((ReferenceEntry item) => GetAxisSortOrder(item.Info.Axis)).ThenBy((ReferenceEntry item) => item.Info.PrefabName, StringComparer.OrdinalIgnoreCase).ThenBy((ReferenceEntry item) => item.Info.PrefabName, StringComparer.Ordinal)) { builder.Append(" - prefab: ").AppendLine(FormatYamlScalar(item2.Info.PrefabName)); builder.Append(" foodType: ").AppendLine(GetFoodTypeLabel(item2.Info.Axis)); builder.Append(" reason: ").AppendLine(FormatYamlScalar(item2.Info.FallbackReason)); if (item2.Info.Sources.Count == 0) { builder.AppendLine(" sources: []"); continue; } builder.AppendLine(" sources:"); foreach (string source in item2.Info.Sources) { builder.Append(" - ").AppendLine(FormatYamlScalar(source)); } } } } private static void AppendTier(StringBuilder builder, IGrouping tierGroup, string tierName) { List list = tierGroup.ToList(); builder.AppendLine(); builder.Append(FormatYamlScalar(tierName)).AppendLine(":"); AppendCounts(builder, " ", list); FoodStatAxis[] orderedFoodAxes = OrderedFoodAxes; foreach (FoodStatAxis axis in orderedFoodAxes) { List list2 = list.Where((ReferenceEntry entry) => entry.Info.Axis == axis).ToList(); if (list2.Count == 0) { continue; } builder.AppendLine(); builder.Append(" # --- ").Append(GetFoodTypeLabel(axis)).AppendLine(" ---"); foreach (IGrouping item in OrderOwnerGroups(list2)) { builder.Append(" # ----- ").Append(FoodPrefabOwnerResolver.NormalizeOwnerName(item.Key)).AppendLine(" -----"); foreach (ReferenceEntry item2 in item.OrderBy((ReferenceEntry item) => item.Info.PrefabName, StringComparer.OrdinalIgnoreCase).ThenBy((ReferenceEntry item) => item.Info.PrefabName, StringComparer.Ordinal)) { builder.Append(" - ").AppendLine(FormatCompactFood(item2.Info.PrefabName, axis)); } } } } private static string GetUniqueTierOutputName(IGrouping tierGroup, ISet usedNames) { string text = tierGroup.Select((ReferenceEntry entry) => entry.Info.TierName).FirstOrDefault((string name) => !string.IsNullOrWhiteSpace(name)) ?? ("tier" + tierGroup.Key); string text2 = text; int num = 0; while (!usedNames.Add(text2)) { num++; text2 = text + " (tier " + tierGroup.Key + ((num > 1) ? ("-" + num) : "") + ")"; } return text2; } private static IEnumerable> OrderOwnerGroups(IEnumerable entries) { return (from @group in entries.GroupBy((ReferenceEntry entry) => entry.OwnerName, StringComparer.OrdinalIgnoreCase) orderby FoodPrefabOwnerResolver.GetOwnerSortBucket(@group.Key) select @group).ThenBy, string>((IGrouping group) => group.Key, StringComparer.OrdinalIgnoreCase).ThenBy, string>((IGrouping group) => group.Key, StringComparer.Ordinal); } private static void AppendCounts(StringBuilder builder, string indentation, IEnumerable entries) { Dictionary dictionary = OrderedFoodAxes.ToDictionary((FoodStatAxis axis) => axis, (FoodStatAxis _) => 0); foreach (ReferenceEntry entry in entries) { if (dictionary.ContainsKey(entry.Info.Axis)) { dictionary[entry.Info.Axis]++; } } builder.Append(indentation).Append("# counts: healthFood=").Append(dictionary[FoodStatAxis.Health]) .Append(", staminaFood=") .Append(dictionary[FoodStatAxis.Stamina]) .Append(", eitrFood=") .Append(dictionary[FoodStatAxis.Eitr]) .AppendLine(); } private static string FormatCompactFood(string prefabName, FoodStatAxis axis) { string text = prefabName + ", " + GetFoodTypeLabel(axis); if (string.IsNullOrWhiteSpace(prefabName) || !prefabName.All(delegate(char character) { bool flag = char.IsLetterOrDigit(character); if (!flag) { bool flag2 = ((character == '-' || character == '.' || character == '_') ? true : false); flag = flag2; } return flag; })) { return FormatYamlScalar(text); } return text; } private static int GetAxisSortOrder(FoodStatAxis axis) { return axis switch { FoodStatAxis.Health => 0, FoodStatAxis.Stamina => 1, FoodStatAxis.Eitr => 2, _ => 3, }; } private static string GetFoodTypeLabel(FoodStatAxis axis) { return axis switch { FoodStatAxis.Health => "healthFood", FoodStatAxis.Stamina => "staminaFood", FoodStatAxis.Eitr => "eitrFood", _ => "unknownFood", }; } private static void ResetGenerationState(bool resetFailureLog) { _dirty = true; if (resetFailureLog) { _failureLogged = false; } _nextAttemptAt = 0f; _nextExistenceCheckAt = 0f; _ownerResolutionRetriesRemaining = 3; } private static string FormatYamlScalar(string value) { string text = value ?? ""; if (text.Length > 0 && text.All(delegate(char character) { bool flag = char.IsLetterOrDigit(character); if (!flag) { bool flag2 = ((character == '-' || character == '.' || character == '_') ? true : false); flag = flag2; } return flag; })) { return text; } return "\"" + text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; } private static string Canonicalize(string value) { return (value ?? "").Replace("\r\n", "\n").Replace('\r', '\n').TrimEnd(new char[1] { '\n' }) + "\n"; } } internal enum PukeFoodRemovalOrder { OldestFirst, Random, NewestFirst } internal static class DietConfig { internal static ConfigEntry MaxFoodSlots; internal static ConfigEntry SixSlotFoodStatScale; internal static ConfigEntry NineSlotFoodStatScale; internal static ConfigEntry FullCourseMultiplier; internal static ConfigEntry RecentHistorySize; internal static ConfigEntry DiminishingThreshold; internal static ConfigEntry DiminishingFactor; internal static ConfigEntry PukeRemovalOrder; internal static ConfigEntry ChefCollectionSize; internal static ConfigEntry ChefMultiplierMin; internal static ConfigEntry ChefMultiplierMax; internal static ConfigEntry ChefHighTierSelectionStrength; internal static ConfigEntry ChefMultiplierModeAtMaxCooking; internal static ConfigEntry ChefRecentFoodPreferencePercent; internal static ConfigEntry CookingExperiencePerFoodEaten; internal static ConfigEntry CookingBonusChanceAtMaxCookingPercent; internal static ConfigEntry FermenterOutputBonusChanceAtMaxCookingPercent; internal static ConfigEntry CookingBonusExcludedOutputPrefabs; internal static void Initialize(ConfigFile config, ConfigSync configSync) { MaxFoodSlots = BindSynced(config, configSync, ConfigPresentation.Diet, "Maximum Food Slots", 9, ConfigPresentation.Synced("Maximum number of active food slots. Choose either six or nine; slots unlock from three as the player learns more directly edible Health/Stamina/Eitr foods.", ConfigPresentation.Diet, 500, (AcceptableValueBase?)(object)new AcceptableValueList(new int[2] { 6, 9 }))); SixSlotFoodStatScale = BindSynced(config, configSync, ConfigPresentation.Diet, "6-Slot Food Stat Scale", 0.45f, ConfigPresentation.Synced("Per-food stat multiplier at the six-slot maximum. Earlier unlocked tiers are automatically raised so a completely filled current diet keeps the same total base strength. The default keeps that total at 90% of three vanilla food slots before Full Course.", ConfigPresentation.Diet, 450, (AcceptableValueBase?)(object)new AcceptableValueRange(0.1f, 3f))); NineSlotFoodStatScale = BindSynced(config, configSync, ConfigPresentation.Diet, "9-Slot Food Stat Scale", 0.3f, ConfigPresentation.Synced("Per-food stat multiplier at the nine-slot maximum. Earlier unlocked tiers are automatically raised so a completely filled current diet keeps the same total base strength. The default keeps that total at 90% of three vanilla food slots before Full Course.", ConfigPresentation.Diet, 400, (AcceptableValueBase?)(object)new AcceptableValueRange(0.1f, 3f))); FullCourseMultiplier = BindSynced(config, configSync, ConfigPresentation.Diet, "Full Course Multiplier", 1.2f, ConfigPresentation.Synced("Multiplier applied to health, stamina, eitr, and health regeneration from every active food once at least six slots are unlocked and every currently unlocked slot is filled with a directly edible Health/Stamina/Eitr food. 1 disables the bonus.", ConfigPresentation.Diet, 350, (AcceptableValueBase?)(object)new AcceptableValueRange(1f, 5f))); RecentHistorySize = BindSynced(config, configSync, ConfigPresentation.Diet, "Recent Food History Size", 7, ConfigPresentation.Synced("Number of unique recent foods tracked in the HUD and history.", ConfigPresentation.Diet, 300, (AcceptableValueBase?)(object)new AcceptableValueRange(1, 12))); DiminishingThreshold = BindSynced(config, configSync, ConfigPresentation.Diet, "Diminishing Returns Start Count", 4, ConfigPresentation.Synced("The consumption count that starts diminishing returns for regular foods.", ConfigPresentation.Diet, 200, (AcceptableValueBase?)(object)new AcceptableValueRange(1, 20))); DiminishingFactor = BindSynced(config, configSync, ConfigPresentation.Diet, "Diminishing Returns Multiplier", 0.75f, ConfigPresentation.Synced("Single multiplier applied at and after the diminishing threshold.", ConfigPresentation.Diet, 100, (AcceptableValueBase?)(object)new AcceptableValueRange(0.1f, 1f))); PukeRemovalOrder = BindSynced(config, configSync, ConfigPresentation.Diet, "Puke Food Removal Order", PukeFoodRemovalOrder.NewestFirst, ConfigPresentation.Synced("Controls which active food each SE_Puke removal tick removes. Vanilla uses Random. OldestFirst and NewestFirst compare the time elapsed since each food was last eaten.", ConfigPresentation.Diet, 50)); ChefCollectionSize = BindSynced(config, configSync, ConfigPresentation.ChefChoice, "List Size", 7, ConfigPresentation.Synced("Number of active Chef's Choice foods shown in the HUD.", ConfigPresentation.ChefChoice, 600, (AcceptableValueBase?)(object)new AcceptableValueRange(1, 12))); ChefMultiplierMin = BindSynced(config, configSync, ConfigPresentation.ChefChoice, "Minimum Multiplier", 1.1f, ConfigPresentation.Synced("Minimum random multiplier for Chef's Choice foods. If it exceeds the configured maximum, the effective maximum is raised to this value.", ConfigPresentation.ChefChoice, 500, (AcceptableValueBase?)(object)new AcceptableValueRange(1f, 5f))); ChefMultiplierModeAtMaxCooking = BindSynced(config, configSync, ConfigPresentation.ChefChoice, "Most Likely Multiplier at Max Cooking Level", 1.5f, ConfigPresentation.Synced("Most likely Chef's Choice multiplier at maximum Cooking level. At level 0, Minimum Multiplier is most likely; intermediate levels move the triangular distribution's mode linearly between them. The effective value is clamped to the configured multiplier range.", ConfigPresentation.ChefChoice, 450, (AcceptableValueBase?)(object)new AcceptableValueRange(1f, 5f))); ChefMultiplierMax = BindSynced(config, configSync, ConfigPresentation.ChefChoice, "Maximum Multiplier", 1.5f, ConfigPresentation.Synced("Maximum random multiplier for Chef's Choice foods. The effective maximum cannot be lower than Minimum Multiplier.", ConfigPresentation.ChefChoice, 400, (AcceptableValueBase?)(object)new AcceptableValueRange(1f, 5f))); ChefHighTierSelectionStrength = BindSynced(config, configSync, ConfigPresentation.ChefChoice, "High-Tier Selection Strength", 5f, ConfigPresentation.Synced("Controls how strongly Cooking level favors higher ResourceMap tiers in Chef's Choice. Higher values make high-tier foods more likely at high Cooking levels. 0 disables tier weighting.", ConfigPresentation.ChefChoice, 300, (AcceptableValueBase?)(object)new AcceptableValueRange(0f, 20f))); ChefRecentFoodPreferencePercent = BindSynced(config, configSync, ConfigPresentation.ChefChoice, "Recent Food-Type Preference (%)", 70f, ConfigPresentation.Synced("Percentage used to blend the existing Chef food-type probabilities with the Health/Stamina/Eitr proportions in recent unique-food history. 0 keeps the existing distribution; 100 follows the history proportions exactly when those food types are available.", ConfigPresentation.ChefChoice, 100, (AcceptableValueBase?)(object)new AcceptableValueRange(0f, 100f))); CookingExperiencePerFoodEaten = BindSynced(config, configSync, ConfigPresentation.General, "Cooking Experience per Food Eaten", 0.15f, ConfigPresentation.Synced("Cooking skill experience granted after successfully eating a directly edible Health/Stamina/Eitr food. 0 disables this reward.", ConfigPresentation.General, 450, (AcceptableValueBase?)(object)new AcceptableValueRange(0f, 1f))); CookingBonusChanceAtMaxCookingPercent = BindSynced(config, configSync, ConfigPresentation.General, "Production Bonus Chance at Max Cooking (%)", 25f, ConfigPresentation.Synced("Independent bonus chance per base output item at Cooking level 100 for Cooking recipes and CookingStation outputs. Lower Cooking levels scale this chance linearly.", ConfigPresentation.General, 400, (AcceptableValueBase?)(object)new AcceptableValueRange(0f, 25f))); FermenterOutputBonusChanceAtMaxCookingPercent = BindSynced(config, configSync, ConfigPresentation.General, "Fermenter Output Bonus Chance at Max Cooking (%)", 20f, ConfigPresentation.Synced("Independent bonus chance per base Fermenter output item at Cooking level 100. Lower Cooking levels scale this chance linearly.", ConfigPresentation.General, 350, (AcceptableValueBase?)(object)new AcceptableValueRange(0f, 25f))); CookingBonusExcludedOutputPrefabs = BindSynced(config, configSync, ConfigPresentation.General, "Production Bonus Excluded Output Prefabs", string.Empty, ConfigPresentation.Synced("Comma-, semicolon-, or newline-separated output prefab names that receive no Cooking production bonus. '*' is a case-insensitive whole-name wildcard.", ConfigPresentation.General, 300)); } internal static void Shutdown() { MaxFoodSlots = null; SixSlotFoodStatScale = null; NineSlotFoodStatScale = null; FullCourseMultiplier = null; RecentHistorySize = null; DiminishingThreshold = null; DiminishingFactor = null; PukeRemovalOrder = null; ChefCollectionSize = null; ChefMultiplierMin = null; ChefMultiplierMax = null; ChefHighTierSelectionStrength = null; ChefMultiplierModeAtMaxCooking = null; ChefRecentFoodPreferencePercent = null; CookingExperiencePerFoodEaten = null; CookingBonusChanceAtMaxCookingPercent = null; FermenterOutputBonusChanceAtMaxCookingPercent = null; CookingBonusExcludedOutputPrefabs = null; } internal static int GetMaxFoodSlots() { return MaxFoodSlots.Value; } internal static float GetBaseSlotScale(int unlockedFoodSlots) { return CalculateBaseSlotScale(GetMaxFoodSlots(), unlockedFoodSlots, SixSlotFoodStatScale.Value, NineSlotFoodStatScale.Value); } internal static float CalculateBaseSlotScale(int maximumFoodSlots, int unlockedFoodSlots, float sixSlotScale, float nineSlotScale) { int num = ((maximumFoodSlots <= 6) ? 6 : 9); int num2 = Math.Max(3, Math.Min(num, unlockedFoodSlots)); return ((num == 6) ? sixSlotScale : nineSlotScale) * (float)num / (float)num2; } internal static float GetFullCourseMultiplier() { return FullCourseMultiplier.Value; } internal static int GetRecentHistorySize() { return RecentHistorySize.Value; } internal static int GetDiminishingThreshold() { return DiminishingThreshold.Value; } internal static float GetDiminishingFactor() { return DiminishingFactor.Value; } internal static PukeFoodRemovalOrder GetPukeFoodRemovalOrder() { return PukeRemovalOrder?.Value ?? PukeFoodRemovalOrder.NewestFirst; } internal static int GetChefCollectionSize() { return ChefCollectionSize.Value; } internal static float GetChefMultiplierMin() { return ChefChoiceMath.ClampMultiplierMinimum(ChefMultiplierMin.Value); } internal static float GetChefMultiplierMax() { return ChefChoiceMath.ClampMultiplierMaximum(ChefMultiplierMax.Value, GetChefMultiplierMin()); } internal static float GetChefHighTierSelectionStrength() { return ChefHighTierSelectionStrength.Value; } internal static float GetChefMultiplierModeAtMaxCooking() { return ChefChoiceMath.ClampMultiplierMode(ChefMultiplierModeAtMaxCooking.Value, GetChefMultiplierMin(), GetChefMultiplierMax()); } internal static float GetChefRecentFoodPreferencePercent() { return ChefRecentFoodPreferencePercent.Value; } internal static float GetCookingExperiencePerFoodEaten() { return CookingExperiencePerFoodEaten.Value; } internal static float GetCookingBonusChanceAtMaxCookingPercent() { return CookingBonusChanceAtMaxCookingPercent.Value; } internal static float GetFermenterOutputBonusChanceAtMaxCookingPercent() { return FermenterOutputBonusChanceAtMaxCookingPercent.Value; } internal static string GetCookingBonusExcludedOutputPrefabs() { return CookingBonusExcludedOutputPrefabs.Value; } private static ConfigEntry BindSynced(ConfigFile config, ConfigSync configSync, ConfigPresentation.SectionDefinition section, string key, T defaultValue, ConfigDescription description) { ConfigEntry val = config.Bind(section.Name, key, defaultValue, description); configSync.AddConfigEntry(val).SynchronizedConfig = true; return val; } } internal static class DietModule { private static bool _initialized; private static bool _dietReconcileRequested; private static bool _chefReconcileRequested; internal static void Initialize(ConfigFile config, ConfigSync configSync) { if (!_initialized) { DietConfig.Initialize(config, configSync); DietConfig.MaxFoodSlots.SettingChanged += FoodStateShapeChanged; DietConfig.SixSlotFoodStatScale.SettingChanged += FoodStateShapeChanged; DietConfig.NineSlotFoodStatScale.SettingChanged += FoodStateShapeChanged; DietConfig.RecentHistorySize.SettingChanged += FoodStateShapeChanged; DietConfig.ChefCollectionSize.SettingChanged += FoodStateShapeChanged; DietConfig.ChefMultiplierMin.SettingChanged += FoodStateShapeChanged; DietConfig.ChefMultiplierMax.SettingChanged += FoodStateShapeChanged; FineDiningLocalization.OnLocalizationComplete += HudFoodPanels.ResetAll; ItemManager.OnItemsRegistered += ChefContentRegistered; PrefabManager.OnPrefabsRegistered += ChefContentRegistered; CookingStationAutoPopSystem.Reset(); FermenterCookingBonusSystem.ResetRuntime(); _dietReconcileRequested = true; _chefReconcileRequested = true; _initialized = true; } } internal static void Tick() { if (!_initialized || (!_dietReconcileRequested && !_chefReconcileRequested)) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } bool flag = _dietReconcileRequested && ChefFoodTierCatalog.IsReady; bool flag2 = _chefReconcileRequested && ChefFoodTierCatalog.IsReady; if (flag || flag2) { PlayerFoodStateData state = FoodStateStore.GetState(localPlayer); if (flag) { _dietReconcileRequested = false; HudFoodPanels.ResetAll(); FoodSlotProgression.ReconcileConfiguration(localPlayer, state); FoodSlotProgression.TrimExcessFoods(localPlayer, state); } if (flag2) { _chefReconcileRequested = false; ChefCollectionService.EnsureChefCollection(localPlayer, state); } FoodStateStore.SaveState(localPlayer, state); if (flag) { PlayerFoodLogic.RefreshFoodStats(localPlayer); } } } internal static void Shutdown() { if (_initialized) { DietConfig.MaxFoodSlots.SettingChanged -= FoodStateShapeChanged; DietConfig.SixSlotFoodStatScale.SettingChanged -= FoodStateShapeChanged; DietConfig.NineSlotFoodStatScale.SettingChanged -= FoodStateShapeChanged; DietConfig.RecentHistorySize.SettingChanged -= FoodStateShapeChanged; DietConfig.ChefCollectionSize.SettingChanged -= FoodStateShapeChanged; DietConfig.ChefMultiplierMin.SettingChanged -= FoodStateShapeChanged; DietConfig.ChefMultiplierMax.SettingChanged -= FoodStateShapeChanged; FineDiningLocalization.OnLocalizationComplete -= HudFoodPanels.ResetAll; ItemManager.OnItemsRegistered -= ChefContentRegistered; PrefabManager.OnPrefabsRegistered -= ChefContentRegistered; } _initialized = false; _dietReconcileRequested = false; _chefReconcileRequested = false; HudFoodPanels.ResetAll(); FoodStateStore.Reset(); FoodSlotProgression.Reset(); CookingStationAutoPopSystem.Reset(); FermenterCookingBonusSystem.ResetRuntime(); DietConfig.Shutdown(); } private static void FoodStateShapeChanged(object sender, EventArgs e) { _dietReconcileRequested = true; _chefReconcileRequested = true; } internal static void InvalidateChefTierCatalog() { ChefFoodTierCatalog.Invalidate(); ChefTierReferenceGenerator.Invalidate(); FoodSlotProgression.Reset(); _dietReconcileRequested = true; RequestChefCollectionReconcile(); } internal static void RequestChefCollectionReconcile() { _chefReconcileRequested = true; } internal static void RequestDietReconcile() { _dietReconcileRequested = true; } private static void ChefContentRegistered() { InvalidateChefTierCatalog(); } } internal readonly struct FoodEffect { internal float FreshnessScale { get; } internal float AppliedScale { get; } internal float EffectiveScale { get; } internal float DiminishingScale { get; } internal float Health { get; } internal float Stamina { get; } internal float Eitr { get; } internal float Regen { get; } internal bool IsChef { get; } internal float ChefMultiplier { get; } internal bool FullCourseActive { get; } internal FoodEffect(float freshnessScale, float appliedScale, float effectiveScale, float diminishingScale, float health, float stamina, float eitr, float regen, bool isChef, float chefMultiplier, bool fullCourseActive) { FreshnessScale = freshnessScale; AppliedScale = appliedScale; EffectiveScale = effectiveScale; DiminishingScale = diminishingScale; Health = health; Stamina = stamina; Eitr = eitr; Regen = regen; IsChef = isChef; ChefMultiplier = chefMultiplier; FullCourseActive = fullCourseActive; } } internal static class FoodRules { internal const int MinimumFullCourseSlots = 6; internal static float CalculateRegularFoodScale(Player player, PlayerFoodStateData state, int historyStack) { return DietConfig.GetBaseSlotScale(FoodSlotProgression.GetCurrentSlots(player, state)) * CalculateDiminishingScale(historyStack); } internal static float CalculateDiminishingScale(int historyStack) { if (historyStack < DietConfig.GetDiminishingThreshold()) { return 1f; } return DietConfig.GetDiminishingFactor(); } internal static bool IsFullCourseActive(Player? player) { if ((Object)(object)player == (Object)null) { return false; } PlayerFoodStateData state = FoodStateStore.GetState(player); return IsFullCourseActive(player, state, CountActiveDietFoods(player.GetFoods())); } internal static bool IsFullCourseActive(Player player, PlayerFoodStateData state, int activeFoodCount) { return IsFullCourseEligible(FoodSlotProgression.GetCurrentSlots(player, state), activeFoodCount); } internal static bool IsFullCourseEligible(int unlockedFoodSlots, int activeFoodCount) { if (unlockedFoodSlots >= 6) { return activeFoodCount >= unlockedFoodSlots; } return false; } internal static float GetFullCourseScale(Player player, PlayerFoodStateData state, int activeFoodCount) { if (!IsFullCourseActive(player, state, activeFoodCount)) { return 1f; } return DietConfig.GetFullCourseMultiplier(); } internal static bool WillHaveFullCourseAfterEating(Player player, PlayerFoodStateData state, ItemData item, bool replacesExistingFood = false, bool replacesDietFood = false) { int num = (replacesExistingFood ? FoodSlotProgression.GetSlotsAfterFoodRemoval(player, state) : FoodSlotProgression.GetCurrentSlots(player, state)); if (num < 6) { return false; } List foods = player.GetFoods(); int num2 = CountActiveDietFoods(foods); string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(item); foreach (Food item2 in foods) { if (FoodIdentity.IsDirectlyEdible(item2.m_item) && FoodIdentity.GetCanonicalPrefabName(item2) == canonicalPrefabName) { return IsFullCourseEligible(num, num2); } } if ((!replacesExistingFood || !replacesDietFood) && num2 < num) { num2++; } return IsFullCourseEligible(num, num2); } internal static FoodEffect PreviewNextFoodEffect(Player player, PlayerFoodStateData state, ItemData item) { string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(item); ChefEntryData? entry = ChefCollectionService.GetEntry(state, canonicalPrefabName); bool isChef = entry != null; int nextStack = RecentHistoryService.GetNextStack(state, canonicalPrefabName, isChef); float chefMultiplier = entry?.Multiplier ?? 1f; bool replacesDietFood; bool flag = TryGetReplacementFood(player, state, item, out replacesDietFood); bool fullCourseActive = WillHaveFullCourseAfterEating(player, state, item, flag, replacesDietFood); int unlockedFoodSlots = (flag ? FoodSlotProgression.GetSlotsAfterFoodRemoval(player, state) : FoodSlotProgression.GetCurrentSlots(player, state)); return CalculateFoodEffect(item, nextStack, isChef, chefMultiplier, fullCourseActive, unlockedFoodSlots); } internal static FoodEffect CalculateFoodEffect(ItemData item, int stack, bool isChef, float chefMultiplier, bool fullCourseActive, int unlockedFoodSlots) { float num = (isChef ? 1f : CalculateDiminishingScale(stack)); float num2 = DietConfig.GetBaseSlotScale(unlockedFoodSlots) * (isChef ? chefMultiplier : num); float num3 = FreshnessRuntime.GetFoodStatMultiplier(item); if (float.IsNaN(num3) || float.IsInfinity(num3)) { num3 = 1f; } num3 = Math.Max(0f, Math.Min(1f, num3)); float num4 = num2 * num3; float num5 = num4 * (fullCourseActive ? DietConfig.GetFullCourseMultiplier() : 1f); return new FoodEffect(num3, num4, num5, num, item.m_shared.m_food * num5, item.m_shared.m_foodStamina * num5, item.m_shared.m_foodEitr * num5, item.m_shared.m_foodRegen * num5, isChef, chefMultiplier, fullCourseActive); } internal static float GetAppliedScale(Player player, PlayerFoodStateData state, Food food) { if (!FoodIdentity.IsDirectlyEdible(food?.m_item)) { return 1f; } string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(food); ActiveFoodData activeFood = GetActiveFood(state, canonicalPrefabName); if (activeFood != null) { return activeFood.AppliedScale; } HistoryEntryData entry = RecentHistoryService.GetEntry(state, canonicalPrefabName); if (entry == null) { return DietConfig.GetBaseSlotScale(FoodSlotProgression.GetCurrentSlots(player, state)); } return CalculateRegularFoodScale(player, state, entry.Stack); } internal static int CountActiveDietFoods(IReadOnlyList? foods) { if (foods == null) { return 0; } int num = 0; for (int i = 0; i < foods.Count; i++) { if (FoodIdentity.IsDirectlyEdible(foods[i]?.m_item)) { num++; } } return num; } private static bool TryGetReplacementFood(Player player, PlayerFoodStateData state, ItemData item, out bool replacesDietFood) { replacesDietFood = false; List foods = player.GetFoods(); string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(item); foreach (Food item2 in foods) { if (FoodIdentity.GetCanonicalPrefabName(item2) == canonicalPrefabName) { if (!item2.CanEatAgain()) { return false; } replacesDietFood = FoodIdentity.IsDirectlyEdible(item2.m_item); return true; } } if (foods.Count < FoodSlotProgression.GetCurrentSlots(player, state)) { return false; } Food val = null; foreach (Food item3 in foods) { if (item3.CanEatAgain() && (val == null || item3.m_time < val.m_time)) { val = item3; } } if (val == null) { return false; } replacesDietFood = FoodIdentity.IsDirectlyEdible(val.m_item); return true; } internal static void SetActiveFoodScale(PlayerFoodStateData state, string key, float scale) { for (int i = 0; i < state.Active.Count; i++) { ActiveFoodData activeFoodData = state.Active[i]; if (!(activeFoodData.Key != key)) { activeFoodData.AppliedScale = scale; if (i != state.Active.Count - 1) { state.Active.RemoveAt(i); state.Active.Add(activeFoodData); } return; } } state.Active.Add(new ActiveFoodData { Key = key, AppliedScale = scale }); } private static ActiveFoodData? GetActiveFood(PlayerFoodStateData state, string key) { foreach (ActiveFoodData item in state.Active) { if (item.Key == key) { return item; } } return null; } } internal static class FoodSlotProgression { private sealed class KnownFoodCache { internal int CatalogVersion { get; } internal int KnownRecipeCount { get; } internal int KnownMaterialCount { get; } internal HashSet Keys { get; } internal KnownFoodCache(int catalogVersion, int knownRecipeCount, int knownMaterialCount, HashSet keys) { CatalogVersion = catalogVersion; KnownRecipeCount = knownRecipeCount; KnownMaterialCount = knownMaterialCount; Keys = keys; } } internal const int MinimumFoodSlots = 3; internal const int MaximumFoodSlots = 9; private static readonly Dictionary KnownFoodCaches = new Dictionary(); internal static int CalculateUnlockedSlots(int knownFoodCount, int maximumFoodSlots) { int val = ((maximumFoodSlots <= 6) ? 6 : 9); int val2 = 3 + (Math.Max(0, knownFoodCount - 6) + 2) / 3; return Math.Min(val, Math.Min(9, val2)); } internal static float CalculateScaleRebase(float previousBaseScale, float nextBaseScale) { if (float.IsNaN(previousBaseScale) || float.IsInfinity(previousBaseScale) || previousBaseScale <= 0f || float.IsNaN(nextBaseScale) || float.IsInfinity(nextBaseScale) || nextBaseScale <= 0f) { return 1f; } return nextBaseScale / previousBaseScale; } internal static int GetCurrentSlots(Player player, PlayerFoodStateData state) { NormalizeState(player, state); return state.UnlockedFoodSlots; } internal static int GetKnownFoodCount(Player? player) { if (!TryGetKnownFoodKeys(player, out HashSet knownFoods)) { return 0; } return knownFoods.Count; } internal static bool ApplyPendingAfterFoodRemoval(Player player, PlayerFoodStateData state, bool trimExcess = true) { NormalizeState(player, state); if (!TryGetDesiredSlots(player, out var desiredSlots)) { return false; } bool result = ApplySlotCount(state, desiredSlots); if (trimExcess) { TrimExcessFoods(player, state); } return result; } internal static int GetSlotsAfterFoodRemoval(Player player, PlayerFoodStateData state) { NormalizeState(player, state); if (!TryGetDesiredSlots(player, out var desiredSlots)) { return GetCurrentSlots(player, state); } return desiredSlots; } internal static bool ReconcileConfiguration(Player player, PlayerFoodStateData state) { int unlockedFoodSlots = state.UnlockedFoodSlots; float appliedBaseSlotScale = state.AppliedBaseSlotScale; NormalizeState(player, state); bool flag = unlockedFoodSlots != state.UnlockedFoodSlots || Math.Abs(appliedBaseSlotScale - state.AppliedBaseSlotScale) > 1E-05f; if (!TryGetDesiredSlots(player, out var desiredSlots)) { return flag; } if (player.GetFoods().Count != 0) { return flag; } return ApplySlotCount(state, desiredSlots) || flag; } internal static void TrimExcessFoods(Player player, PlayerFoodStateData state, Food? protectedFood = null) { List foods = player.GetFoods(); int currentSlots = GetCurrentSlots(player, state); while (foods.Count > currentSlots) { int num = -1; for (int i = 0; i < foods.Count; i++) { if (foods[i] != protectedFood && (num < 0 || !(foods[i].m_time >= foods[num].m_time))) { num = i; } } if (num >= 0) { foods.RemoveAt(num); continue; } break; } } internal static void NormalizeState(Player player, PlayerFoodStateData state) { int maxFoodSlots = DietConfig.GetMaxFoodSlots(); int count = player.GetFoods().Count; int unlockedFoodSlots = state.UnlockedFoodSlots; unlockedFoodSlots = ((unlockedFoodSlots >= 3 && unlockedFoodSlots <= 9) ? Math.Min(maxFoodSlots, unlockedFoodSlots) : Math.Max(3, Math.Min(maxFoodSlots, count))); if (count == 0 && TryGetDesiredSlots(player, out var desiredSlots)) { unlockedFoodSlots = desiredSlots; } ApplySlotCount(state, unlockedFoodSlots); } internal static void Invalidate(Player? player) { if ((Object)(object)player != (Object)null) { KnownFoodCaches.Remove(player); } } internal static void Reset() { KnownFoodCaches.Clear(); } private static bool TryGetDesiredSlots(Player? player, out int desiredSlots) { if (!TryGetKnownFoodKeys(player, out HashSet knownFoods)) { desiredSlots = 3; return false; } desiredSlots = CalculateUnlockedSlots(knownFoods.Count, DietConfig.GetMaxFoodSlots()); return true; } private static bool ApplySlotCount(PlayerFoodStateData state, int desiredSlots) { int maxFoodSlots = DietConfig.GetMaxFoodSlots(); int num = Math.Max(3, Math.Min(maxFoodSlots, desiredSlots)); int num2 = Math.Max(3, Math.Min(9, state.UnlockedFoodSlots)); float baseSlotScale = DietConfig.GetBaseSlotScale(num); float appliedBaseSlotScale = state.AppliedBaseSlotScale; if (float.IsNaN(appliedBaseSlotScale) || float.IsInfinity(appliedBaseSlotScale) || !(appliedBaseSlotScale > 0f)) { state.UnlockedFoodSlots = num; state.AppliedBaseSlotScale = baseSlotScale; return num != num2; } bool num3 = num != num2; bool flag = baseSlotScale != appliedBaseSlotScale; if (!num3 && !flag) { state.UnlockedFoodSlots = num; state.AppliedBaseSlotScale = baseSlotScale; return false; } float num4 = CalculateScaleRebase(appliedBaseSlotScale, baseSlotScale); foreach (ActiveFoodData item in state.Active) { if (item != null && !float.IsNaN(item.AppliedScale) && !float.IsInfinity(item.AppliedScale) && !(item.AppliedScale < 0f)) { item.AppliedScale *= num4; } } state.UnlockedFoodSlots = num; state.AppliedBaseSlotScale = baseSlotScale; return true; } private static bool TryGetKnownFoodKeys(Player? player, out HashSet knownFoods) { knownFoods = null; if ((Object)(object)player == (Object)null || !ChefFoodTierCatalog.IsReady) { return false; } int count = PlayerPrivateAccess.KnownRecipes.Invoke(player).Count; int count2 = PlayerPrivateAccess.KnownMaterials.Invoke(player).Count; if (KnownFoodCaches.TryGetValue(player, out KnownFoodCache value) && value.CatalogVersion == ChefFoodTierCatalog.Version && value.KnownRecipeCount == count && value.KnownMaterialCount == count2) { knownFoods = value.Keys; return true; } HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (ChefFoodTierInfo item in ChefFoodTierCatalog.GetSnapshot()) { if (item.Axis != FoodStatAxis.None && !string.IsNullOrWhiteSpace(item.PrefabName) && !string.IsNullOrWhiteSpace(item.ItemNameToken) && (player.IsRecipeKnown(item.ItemNameToken) || player.IsKnownMaterial(item.ItemNameToken))) { hashSet.Add(item.PrefabName); } } KnownFoodCaches[player] = new KnownFoodCache(ChefFoodTierCatalog.Version, count, count2, hashSet); knownFoods = hashSet; return true; } } [Serializable] internal sealed class HistoryEntryData { public string Key = string.Empty; public int Stack = 1; } [Serializable] internal sealed class ChefEntryData { public string Key = string.Empty; public float Multiplier = 1f; } [Serializable] internal sealed class ActiveFoodData { public string Key = string.Empty; public float AppliedScale = 1f; } [Serializable] internal sealed class PlayerFoodStateData { public int UnlockedFoodSlots; public float AppliedBaseSlotScale; public List Recent = new List(); public List Chef = new List(); public List Active = new List(); } internal static class FoodStateStore { private const string CustomDataKey = "sighsorry.FineDining.DietState"; private const string StatePrefix = "v3:"; private static readonly Dictionary Cache = new Dictionary(); internal static PlayerFoodStateData GetState(Player? player) { if ((Object)(object)player == (Object)null) { return new PlayerFoodStateData(); } if (Cache.TryGetValue(player, out PlayerFoodStateData value)) { return value; } value = LoadState(player); NormalizeState(player, value); Cache[player] = value; return value; } internal static void SaveState(Player? player, PlayerFoodStateData? state = null) { if (!((Object)(object)player == (Object)null)) { if (state == null) { state = GetState(player); } NormalizeState(player, state); Cache[player] = state; player.m_customData["sighsorry.FineDining.DietState"] = SerializeState(state); } } internal static void Invalidate(Player? player) { if (player != null) { Cache.Remove(player); } } internal static void Reset() { Cache.Clear(); } private static PlayerFoodStateData LoadState(Player player) { if (!player.m_customData.TryGetValue("sighsorry.FineDining.DietState", out var value) || string.IsNullOrWhiteSpace(value)) { return new PlayerFoodStateData(); } try { return DeserializeState(value); } catch (Exception ex) { FineDiningPlugin.Log.LogWarning((object)("Failed to deserialize FineDining diet state: " + ex.Message)); return new PlayerFoodStateData(); } } private static string SerializeState(PlayerFoodStateData state) { return "v3:" + JsonUtility.ToJson((object)state); } private static PlayerFoodStateData DeserializeState(string data) { if (!data.StartsWith("v3:", StringComparison.Ordinal)) { FineDiningPlugin.Log.LogWarning((object)"Unsupported FineDining diet state format; resetting the stored diet state."); return new PlayerFoodStateData(); } string text = data.Substring("v3:".Length); if (string.IsNullOrWhiteSpace(text)) { throw new FormatException("The FineDining diet state JSON payload is empty."); } return JsonUtility.FromJson(text) ?? throw new FormatException("The FineDining diet state JSON payload is invalid."); } private static void NormalizeState(Player player, PlayerFoodStateData state) { PlayerFoodStateData playerFoodStateData = state; if (playerFoodStateData.Recent == null) { playerFoodStateData.Recent = new List(); } playerFoodStateData = state; if (playerFoodStateData.Chef == null) { playerFoodStateData.Chef = new List(); } playerFoodStateData = state; if (playerFoodStateData.Active == null) { playerFoodStateData.Active = new List(); } NormalizeRecent(state); NormalizeChef(state); FoodSlotProgression.NormalizeState(player, state); NormalizeActive(player, state); } private static void NormalizeRecent(PlayerFoodStateData state) { List list = new List(); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (HistoryEntryData item in state.Recent) { if (item != null && !string.IsNullOrWhiteSpace(item.Key)) { if (dictionary.TryGetValue(item.Key, out var value)) { value.Stack += Math.Max(1, item.Stack); continue; } HistoryEntryData historyEntryData = new HistoryEntryData { Key = item.Key, Stack = Math.Max(1, item.Stack) }; list.Add(historyEntryData); dictionary[item.Key] = historyEntryData; } } while (list.Count > DietConfig.GetRecentHistorySize()) { list.RemoveAt(0); } state.Recent = list; } private static void NormalizeChef(PlayerFoodStateData state) { List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); float chefMultiplierMin = DietConfig.GetChefMultiplierMin(); float chefMultiplierMax = DietConfig.GetChefMultiplierMax(); foreach (ChefEntryData item in state.Chef) { if (item != null && !string.IsNullOrWhiteSpace(item.Key) && hashSet.Add(item.Key)) { float num = item.Multiplier; if (float.IsNaN(num) || float.IsInfinity(num) || num <= 0f) { num = chefMultiplierMin; } list.Add(new ChefEntryData { Key = item.Key, Multiplier = Math.Max(chefMultiplierMin, Math.Min(chefMultiplierMax, num)) }); } } while (list.Count > DietConfig.GetChefCollectionSize()) { list.RemoveAt(list.Count - 1); } state.Chef = list; } private static void NormalizeActive(Player player, PlayerFoodStateData state) { HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (Food food in player.GetFoods()) { string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(food); if (!string.IsNullOrWhiteSpace(canonicalPrefabName)) { hashSet.Add(canonicalPrefabName); } } List list = new List(); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (ActiveFoodData item in state.Active) { if (item != null && !string.IsNullOrWhiteSpace(item.Key) && hashSet.Contains(item.Key)) { float num = item.AppliedScale; if (float.IsNaN(num) || float.IsInfinity(num) || num < 0f) { num = state.AppliedBaseSlotScale; } ActiveFoodData activeFoodData = new ActiveFoodData { Key = item.Key, AppliedScale = num }; if (dictionary.TryGetValue(item.Key, out var value)) { list[value] = activeFoodData; continue; } dictionary[item.Key] = list.Count; list.Add(activeFoodData); } } state.Active = list; } } internal static class HudFoodPanels { private sealed class PanelContext { public RectTransform Root; public RectTransform RecentRow; public RectTransform ChefRow; public HoverPanelContext RecentHover; public HoverPanelContext ChefHover; public FullCourseContext? FullCourse; public List RecentSlots = new List(); public List ChefSlots = new List(); public string RecentGuidance = string.Empty; public string ChefGuidance = string.Empty; public string GuidanceLanguage = string.Empty; public bool GuidanceInitialized; public bool RecentPreferenceEnabled; public bool ChefTierEnabled; public bool ChefMultiplierEnabled; public Player? ChefPlayer; public ObjectDB? ChefObjectDb; public int KnownRecipeCount = -1; public int KnownMaterialCount = -1; public int ObjectDbItemCount = -1; } private sealed class HoverPanelContext { public RectTransform Root; public TextMeshProUGUI Text; public SlotContext? HoveredSlot; public float HoverStartedAt; } private sealed class FullCourseContext { public RectTransform Root; public Image Background; public Image Icon; public TextMeshProUGUI Multiplier; public HoverPanelContext TooltipPanel; public bool Hovered; public float HoverStartedAt; public string TooltipLanguage = string.Empty; public float TooltipMultiplier = float.NaN; } private sealed class SlotContext { public RectTransform Root; public Image Icon; public TextMeshProUGUI CornerText; public TextMeshProUGUI FooterText; public string ItemKey = string.Empty; public string ItemNameToken = string.Empty; public ObjectDB? ItemObjectDb; public int ItemObjectDbCount = -1; public string HoverText = string.Empty; public string HoverGuidance = string.Empty; public bool HoverTextDirty = true; public bool HoverIsChef; public bool HoverChefExemptsDiminishing; public int HoverStack = -1; public float HoverMultiplier = float.NaN; public string HoverLanguage = string.Empty; } private const string RootName = "FineDining_DietHudRoot"; private const float FallbackFoodIconSize = 43f; private const float SlotSpacing = 1f; private const float HoverPanelGap = 6f; private const float HoverPanelHeight = 48f; private const float HoverPanelMinimumWidth = 460f; private const float HoverPanelFontSize = 15f; private const float HoverPanelMinimumFontSize = 10f; private const float HoverPanelShowDelay = 0.5f; private const string FullCourseIconResourceName = "FineDining.Resources.UI.FullCourseIcon.png"; private const float FullCourseIconSize = 52f; private const float FullCourseTooltipGap = 6f; private const float FullCourseTooltipCanvasMargin = 6f; private const float FullCourseTooltipWidth = 340f; private const float FullCourseTooltipHeight = 96f; private const float FullCourseTooltipFontSize = 14f; private const float FullCourseTooltipMinimumFontSize = 10f; private static readonly Vector3[] RectCorners = (Vector3[])(object)new Vector3[4]; private static readonly Color DefaultBackground = new Color(0f, 0f, 0f, 0.45f); private static readonly FieldRef CurrentTooltipField = AccessTools.StaticFieldRefAccess(AccessTools.DeclaredField(typeof(UITooltip), "m_current") ?? throw new MissingFieldException(typeof(UITooltip).FullName, "m_current")); private static Sprite? _fullCourseIconSprite; private static bool _fullCourseIconLoadAttempted; private static Hud? _contextOwner; private static PanelContext? _context; internal static void Update(Hud hud, Player player) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)hud == (Object)null || (Object)(object)player == (Object)null) { return; } if ((Object)(object)hud.m_gpRoot == (Object)null) { ResetAll(); return; } PanelContext orCreateContext = GetOrCreateContext(hud); Vector2 foodIconSize = GetFoodIconSize(hud, orCreateContext.Root); Vector2 slotSize = foodIconSize; LayoutRoot(orCreateContext, hud, slotSize); EnsureSlots(orCreateContext.RecentSlots, orCreateContext.RecentRow, DietConfig.GetRecentHistorySize(), hud, slotSize, foodIconSize); EnsureSlots(orCreateContext.ChefSlots, orCreateContext.ChefRow, DietConfig.GetChefCollectionSize(), hud, slotSize, foodIconSize); UpdateFullCourseIndicator(orCreateContext, hud, player); bool num = ShouldRefreshChefCollection(orCreateContext, player); PlayerFoodStateData state = FoodStateStore.GetState(player); if (num) { if (ChefCollectionService.EnsureChefCollection(player, state)) { FoodStateStore.SaveState(player, state); } RememberChefCollectionInputs(orCreateContext, player); } UpdateHoverGuidance(orCreateContext); UpdateRecentSlots(orCreateContext.RecentSlots, state, orCreateContext.RecentGuidance); UpdateChefSlots(orCreateContext.ChefSlots, state, orCreateContext.ChefGuidance); UpdateHoverPanel(orCreateContext.RecentHover, orCreateContext.RecentSlots); UpdateHoverPanel(orCreateContext.ChefHover, orCreateContext.ChefSlots); } private static void UpdateFullCourseIndicator(PanelContext context, Hud hud, Player player) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) if (!FoodRules.IsFullCourseActive(player)) { HideFullCourseIndicator(context.FullCourse); return; } if (!TryGetTopFoodBounds(context.Root, hud, out var topFoodBounds)) { HideFullCourseIndicator(context.FullCourse); return; } if ((Object)(object)context.FullCourse?.Root == (Object)null) { context.FullCourse = null; context.FullCourse = CreateFullCourseIndicator(context.Root, hud); if (context.FullCourse == null) { return; } } FullCourseContext? fullCourse = context.FullCourse; Vector2 val = Vector2.one * 52f; float num = ((Rect)(ref topFoodBounds)).center.x; float num2 = ((Rect)(ref topFoodBounds)).yMax + 1f; float num3 = num2 + val.y * 0.5f; Canvas componentInParent = ((Component)context.Root).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { Transform transform = ((Component)componentInParent.rootCanvas).transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val2 != null) { Rect rectInParent = GetRectInParent(val2, context.Root); float num4 = ((Rect)(ref rectInParent)).yMax - num2; float num5 = val.y * 0.65f; if (num4 >= num5) { float num6 = Mathf.Min(val.y, num4); val = Vector2.one * num6; num3 = num2 + val.y * 0.5f; } else { float num7 = ((Rect)(ref topFoodBounds)).xMax + 1f + val.x * 0.5f; float num8 = ((Rect)(ref topFoodBounds)).xMin - 1f - val.x * 0.5f; num = ((num7 + val.x * 0.5f <= ((Rect)(ref rectInParent)).xMax) ? num7 : num8); num3 = ((Rect)(ref topFoodBounds)).center.y; } } } fullCourse.Root.sizeDelta = val; fullCourse.Root.anchoredPosition = new Vector2(num, num3); EnsureFullCourseTooltip(fullCourse); ((Component)fullCourse.Root).gameObject.SetActive(true); UpdateFullCourseTooltipPanel(fullCourse); } private static bool TryGetTopFoodBounds(RectTransform targetRoot, Hud hud, out Rect topFoodBounds) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) topFoodBounds = default(Rect); int num = Mathf.Min(DietConfig.GetMaxFoodSlots(), hud.m_foodIcons.Length); bool flag = false; for (int i = 0; i < num; i++) { Image val = hud.m_foodIcons[i]; if (!((Object)(object)val == (Object)null) && ((Behaviour)val).isActiveAndEnabled && ((Component)val).gameObject.activeInHierarchy) { Rect rectInParent = GetRectInParent(((Graphic)val).rectTransform, targetRoot); if (!flag || ((Rect)(ref rectInParent)).yMax > ((Rect)(ref topFoodBounds)).yMax) { topFoodBounds = rectInParent; flag = true; } } } if (flag && ((Rect)(ref topFoodBounds)).width > 0f) { return ((Rect)(ref topFoodBounds)).height > 0f; } return false; } private static FullCourseContext? CreateFullCourseIndicator(RectTransform parent, Hud hud) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) if (hud.m_foodTime.Length == 0 || (Object)(object)hud.m_foodTime[0] == (Object)null || (Object)(object)hud.m_foodTime[0].font == (Object)null) { return null; } GameObject val = new GameObject("FullCourse", new Type[1] { typeof(RectTransform) }); val.SetActive(false); RectTransform component = val.GetComponent(); ((Transform)component).SetParent((Transform)(object)parent, false); component.anchorMin = new Vector2(parent.pivot.x, parent.pivot.y); component.anchorMax = component.anchorMin; component.pivot = new Vector2(0.5f, 0.5f); GameObject val2 = new GameObject("Background", new Type[2] { typeof(RectTransform), typeof(Image) }); RectTransform component2 = val2.GetComponent(); ((Transform)component2).SetParent((Transform)(object)component, false); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = Vector2.zero; component2.offsetMax = Vector2.zero; Image component3 = val2.GetComponent(); ((Graphic)component3).color = Color.clear; ((Graphic)component3).raycastTarget = false; GameObject val3 = new GameObject("Icon", new Type[2] { typeof(RectTransform), typeof(Image) }); RectTransform component4 = val3.GetComponent(); ((Transform)component4).SetParent((Transform)(object)component, false); component4.anchorMin = Vector2.zero; component4.anchorMax = Vector2.one; component4.offsetMin = new Vector2(2f, 2f); component4.offsetMax = new Vector2(-2f, -2f); Image component5 = val3.GetComponent(); component5.sprite = LoadFullCourseIconSprite(); component5.preserveAspect = true; ((Graphic)component5).raycastTarget = false; ((Behaviour)component5).enabled = (Object)(object)component5.sprite != (Object)null; TMP_Text template = hud.m_foodTime[0]; TextMeshProUGUI val4 = CreateText(component, template, "Multiplier", new Vector2(3f, 3f), new Vector2(-3f, -3f), (TextAlignmentOptions)514, 12f); ((TMP_Text)val4).fontStyle = (FontStyles)1; ((TMP_Text)val4).text = string.Empty; ((Component)val4).gameObject.SetActive(true); Outline obj = ((Component)val4).gameObject.AddComponent(); ((Shadow)obj).effectColor = new Color(0f, 0f, 0f, 0.9f); ((Shadow)obj).effectDistance = new Vector2(1f, -1f); ((Shadow)obj).useGraphicAlpha = true; Shadow obj2 = ((Component)val4).gameObject.AddComponent(); obj2.effectColor = new Color(0f, 0f, 0f, 0.9f); obj2.effectDistance = new Vector2(1f, -1f); obj2.useGraphicAlpha = true; HoverPanelContext tooltipPanel = CreateFullCourseTooltipPanel(parent, template); return new FullCourseContext { Root = component, Background = component3, Icon = component5, Multiplier = val4, TooltipPanel = tooltipPanel }; } private static void EnsureFullCourseTooltip(FullCourseContext indicator) { float fullCourseMultiplier = DietConfig.GetFullCourseMultiplier(); string text = fullCourseMultiplier.ToString("0.00", CultureInfo.InvariantCulture); string text2 = "x" + text; if (!string.Equals(((TMP_Text)indicator.Multiplier).text, text2, StringComparison.Ordinal)) { ((TMP_Text)indicator.Multiplier).text = text2; } Localization instance = Localization.instance; string selectedLanguage = instance.GetSelectedLanguage(); if (!string.Equals(indicator.TooltipLanguage, selectedLanguage, StringComparison.OrdinalIgnoreCase) || !Mathf.Approximately(indicator.TooltipMultiplier, fullCourseMultiplier)) { string text3 = instance.Localize("$finedining_diet_full_course_title"); string text4 = instance.Localize("$finedining_diet_full_course_description", new string[1] { text }); ((TMP_Text)indicator.TooltipPanel.Text).text = "" + text3 + "\n" + text4; indicator.TooltipLanguage = selectedLanguage; indicator.TooltipMultiplier = fullCourseMultiplier; } } private static HoverPanelContext CreateFullCourseTooltipPanel(RectTransform parent, TMP_Text template) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("FullCourseTooltip", new Type[2] { typeof(RectTransform), typeof(Image) }); val.SetActive(false); RectTransform component = val.GetComponent(); ((Transform)component).SetParent((Transform)(object)parent, false); component.anchorMin = new Vector2(parent.pivot.x, parent.pivot.y); component.anchorMax = component.anchorMin; component.pivot = new Vector2(0f, 0.5f); component.sizeDelta = new Vector2(340f, 96f); Image component2 = val.GetComponent(); ((Graphic)component2).color = DefaultBackground; ((Graphic)component2).raycastTarget = false; TextMeshProUGUI val2 = CreateText(component, template, "Text", new Vector2(8f, 5f), new Vector2(-8f, -5f), (TextAlignmentOptions)257, 14f); ((TMP_Text)val2).enableAutoSizing = true; ((TMP_Text)val2).fontSizeMin = 10f; ((TMP_Text)val2).fontSizeMax = 14f; ((TMP_Text)val2).maxVisibleLines = 5; ((TMP_Text)val2).overflowMode = (TextOverflowModes)1; ((TMP_Text)val2).textWrappingMode = (TextWrappingModes)1; ((TMP_Text)val2).richText = true; ((Component)val2).gameObject.SetActive(true); return new HoverPanelContext { Root = component, Text = val2 }; } private static void UpdateFullCourseTooltipPanel(FullCourseContext indicator) { bool flag = ((Behaviour)indicator.Background).isActiveAndEnabled && ((Component)indicator.Root).gameObject.activeInHierarchy && IsHovered(indicator.Background, canHover: true); if (indicator.Hovered != flag) { indicator.Hovered = flag; indicator.HoverStartedAt = Time.unscaledTime; ((Component)indicator.TooltipPanel.Root).gameObject.SetActive(false); return; } bool flag2 = flag && Time.unscaledTime - indicator.HoverStartedAt >= 0.5f; if (flag2) { LayoutFullCourseTooltipPanel(indicator); } if (((Component)indicator.TooltipPanel.Root).gameObject.activeSelf != flag2) { ((Component)indicator.TooltipPanel.Root).gameObject.SetActive(flag2); } } private static void LayoutFullCourseTooltipPanel(FullCourseContext indicator) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) RectTransform root = indicator.TooltipPanel.Root; Transform parent = ((Transform)root).parent; RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null); if (val == null) { return; } Rect rectInParent = GetRectInParent(indicator.Root, val); float num = 340f; float num2 = 96f; float num3 = ((Rect)(ref rectInParent)).xMax + 6f; float num4 = ((Rect)(ref rectInParent)).center.y; Canvas componentInParent = ((Component)indicator.Root).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { Transform transform = ((Component)componentInParent.rootCanvas).transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val2 != null) { Rect rectInParent2 = GetRectInParent(val2, val); num = Mathf.Min(num, Mathf.Max(1f, ((Rect)(ref rectInParent2)).width - 12f)); num2 = Mathf.Min(num2, Mathf.Max(1f, ((Rect)(ref rectInParent2)).height - 12f)); float num5 = ((Rect)(ref rectInParent)).xMax + 6f; float num6 = ((Rect)(ref rectInParent)).xMin - 6f - num; num3 = ((num5 + num <= ((Rect)(ref rectInParent2)).xMax - 6f) ? num5 : num6); float num7 = ((Rect)(ref rectInParent2)).xMin + 6f; float num8 = ((Rect)(ref rectInParent2)).xMax - 6f - num; num3 = Mathf.Clamp(num3, num7, Mathf.Max(num7, num8)); float num9 = ((Rect)(ref rectInParent2)).yMin + 6f + num2 * 0.5f; float num10 = ((Rect)(ref rectInParent2)).yMax - 6f - num2 * 0.5f; num4 = Mathf.Clamp(num4, num9, Mathf.Max(num9, num10)); } } root.sizeDelta = new Vector2(num, num2); root.anchoredPosition = new Vector2(num3, num4); } private static Sprite? LoadFullCourseIconSprite() { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if (_fullCourseIconLoadAttempted) { return _fullCourseIconSprite; } _fullCourseIconLoadAttempted = true; try { using Stream stream = typeof(HudFoodPanels).Assembly.GetManifestResourceStream("FineDining.Resources.UI.FullCourseIcon.png"); if (stream == null) { FineDiningPlugin.Log.LogWarning((object)"Embedded Full Course icon was not found: FineDining.Resources.UI.FullCourseIcon.png"); return null; } using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); Texture2D val = AssetUtils.LoadImage(memoryStream.ToArray()); if ((Object)(object)val == (Object)null) { FineDiningPlugin.Log.LogWarning((object)"Embedded Full Course icon could not be decoded."); return null; } ((Object)val).name = "FineDining_FullCourseIcon_Texture"; ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; _fullCourseIconSprite = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); ((Object)_fullCourseIconSprite).name = "FineDining_FullCourseIcon"; return _fullCourseIconSprite; } catch (Exception ex) { FineDiningPlugin.Log.LogWarning((object)("Embedded Full Course icon load failed: " + ex.Message)); return null; } } private static void HideFullCourseIndicator(FullCourseContext? indicator) { if (!((Object)(object)indicator?.Root == (Object)null)) { indicator.Hovered = false; indicator.HoverStartedAt = 0f; ((Component)indicator.TooltipPanel.Root).gameObject.SetActive(false); ((Component)indicator.Root).gameObject.SetActive(false); } } private static bool ShouldRefreshChefCollection(PanelContext context, Player player) { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return false; } if (!((Object)(object)context.ChefPlayer != (Object)(object)player) && !((Object)(object)context.ChefObjectDb != (Object)(object)instance) && context.KnownRecipeCount == PlayerPrivateAccess.KnownRecipes.Invoke(player).Count && context.KnownMaterialCount == PlayerPrivateAccess.KnownMaterials.Invoke(player).Count) { return context.ObjectDbItemCount != instance.m_items.Count; } return true; } private static void RememberChefCollectionInputs(PanelContext context, Player player) { ObjectDB instance = ObjectDB.instance; if (!((Object)(object)instance == (Object)null)) { context.ChefPlayer = player; context.ChefObjectDb = instance; context.KnownRecipeCount = PlayerPrivateAccess.KnownRecipes.Invoke(player).Count; context.KnownMaterialCount = PlayerPrivateAccess.KnownMaterials.Invoke(player).Count; context.ObjectDbItemCount = instance.m_items.Count; } } internal static void ResetAll() { if (_context != null) { HideFullCourseIndicator(_context.FullCourse); foreach (SlotContext recentSlot in _context.RecentSlots) { HideSlot(recentSlot); } foreach (SlotContext chefSlot in _context.ChefSlots) { HideSlot(chefSlot); } } if ((Object)(object)_context?.Root != (Object)null) { Object.Destroy((Object)(object)((Component)_context.Root).gameObject); } _contextOwner = null; _context = null; } private static Vector2 GetFoodIconSize(Hud hud, RectTransform targetRoot) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) if (hud.m_foodIcons.Length == 0 || (Object)(object)hud.m_foodIcons[0] == (Object)null) { return Vector2.one * 43f; } Rect rectInParent = GetRectInParent(((Graphic)hud.m_foodIcons[0]).rectTransform, targetRoot); if (!(((Rect)(ref rectInParent)).width > 0f) || !(((Rect)(ref rectInParent)).height > 0f)) { return Vector2.one * 43f; } return ((Rect)(ref rectInParent)).size; } private static Rect GetRectInParent(RectTransform source, RectTransform parent) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_009d: Unknown result type (might be due to invalid IL or missing references) source.GetWorldCorners(RectCorners); Vector3 val = ((Transform)parent).InverseTransformPoint(RectCorners[0]); float num = val.x; float num2 = val.x; float num3 = val.y; float num4 = val.y; for (int i = 1; i < RectCorners.Length; i++) { Vector3 val2 = ((Transform)parent).InverseTransformPoint(RectCorners[i]); num = Mathf.Min(num, val2.x); num2 = Mathf.Max(num2, val2.x); num3 = Mathf.Min(num3, val2.y); num4 = Mathf.Max(num4, val2.y); } return Rect.MinMaxRect(num, num3, num2, num4); } private static PanelContext GetOrCreateContext(Hud hud) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_contextOwner == (Object)(object)hud && (Object)(object)_context?.Root != (Object)null) { return _context; } ResetAll(); RectTransform val = (RectTransform)((Transform)hud.m_gpRoot).parent; RectTransform component = new GameObject("FineDining_DietHudRoot", new Type[1] { typeof(RectTransform) }).GetComponent(); ((Transform)component).SetParent((Transform)(object)val, false); component.pivot = new Vector2(0f, 1f); RectTransform recentRow = CreateRow(component, "RecentRow"); RectTransform chefRow = CreateRow(component, "ChefRow"); HoverPanelContext recentHover = CreateHoverPanel(component, hud.m_foodTime[0], "RecentHover"); HoverPanelContext chefHover = CreateHoverPanel(component, hud.m_foodTime[0], "ChefHover"); PanelContext obj = new PanelContext { Root = component, RecentRow = recentRow, ChefRow = chefRow, RecentHover = recentHover, ChefHover = chefHover }; _contextOwner = hud; _context = obj; return obj; } private static void LayoutRoot(PanelContext context, Hud hud, Vector2 slotSize) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: 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_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) RectTransform gpRoot = hud.m_gpRoot; RectTransform parent = (RectTransform)((Transform)context.Root).parent; context.Root.anchorMin = Vector2.zero; context.Root.anchorMax = Vector2.zero; context.Root.pivot = new Vector2(0f, 1f); int num = Mathf.Max(DietConfig.GetRecentHistorySize(), DietConfig.GetChefCollectionSize()); float num2 = (float)num * slotSize.x + (float)Mathf.Max(0, num - 1) * 1f; Rect rectInParent = GetRectInParent(gpRoot, parent); Rect val = (((Object)(object)hud.m_gpIcon != (Object)null) ? GetRectInParent(((Graphic)hud.m_gpIcon).rectTransform, parent) : rectInParent); float num3 = 0f; if ((Object)(object)hud.m_healthBarRoot != (Object)null) { Rect rectInParent2 = GetRectInParent(hud.m_healthBarRoot, parent); num3 = Mathf.Max(0f, ((Rect)(ref val)).xMin - ((Rect)(ref rectInParent2)).xMax); } else if ((Object)(object)hud.m_healthPanel != (Object)null) { Rect rectInParent3 = GetRectInParent(hud.m_healthPanel, parent); num3 = Mathf.Max(0f, ((Rect)(ref rectInParent)).xMin - ((Rect)(ref rectInParent3)).xMax); } float num4 = ((Rect)(ref rectInParent)).yMax; float num5 = 0f - slotSize.y - 1f; if (hud.m_foodIcons.Length >= 2 && (Object)(object)hud.m_foodIcons[0] != (Object)null && (Object)(object)hud.m_foodIcons[1] != (Object)null) { Rect rectInParent4 = GetRectInParent(((Graphic)hud.m_foodIcons[1]).rectTransform, parent); float yMax = ((Rect)(ref rectInParent4)).yMax; rectInParent4 = GetRectInParent(((Graphic)hud.m_foodIcons[0]).rectTransform, parent); float yMax2 = ((Rect)(ref rectInParent4)).yMax; num4 = Mathf.Max(yMax2, yMax); num5 = Mathf.Min(yMax2, yMax) - num4; } context.Root.sizeDelta = new Vector2(num2, Mathf.Abs(num5) + slotSize.y); ((Transform)context.Root).localPosition = new Vector3(((Rect)(ref val)).xMax + num3, num4, ((Transform)gpRoot).localPosition.z); context.RecentRow.anchorMin = new Vector2(0f, 1f); context.RecentRow.anchorMax = new Vector2(0f, 1f); context.RecentRow.pivot = new Vector2(0f, 1f); context.RecentRow.anchoredPosition = Vector2.zero; context.ChefRow.anchorMin = new Vector2(0f, 1f); context.ChefRow.anchorMax = new Vector2(0f, 1f); context.ChefRow.pivot = new Vector2(0f, 1f); context.ChefRow.anchoredPosition = new Vector2(0f, num5); float availableHoverWidth = GetAvailableHoverWidth(context.Root, parent, num2); float width = Mathf.Min(Mathf.Max(num2, 460f), availableHoverWidth); LayoutHoverPanel(context.RecentHover, width, new Vector2(0f, 6f), new Vector2(0f, 0f)); LayoutHoverPanel(context.ChefHover, width, new Vector2(0f, num5 - slotSize.y - 6f), new Vector2(0f, 1f)); } private static float GetAvailableHoverWidth(RectTransform root, RectTransform parent, float panelWidth) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) Canvas componentInParent = ((Component)root).GetComponentInParent(); if (!((Object)(object)componentInParent == (Object)null)) { Transform transform = ((Component)componentInParent.rootCanvas).transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val != null) { Rect rectInParent = GetRectInParent(val, parent); float num = ((Rect)(ref rectInParent)).xMax - ((Transform)root).localPosition.x - 6f; return Mathf.Max(panelWidth, num); } } return Mathf.Max(panelWidth, 460f); } private static void LayoutHoverPanel(HoverPanelContext panel, float width, Vector2 anchoredPosition, Vector2 pivot) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) panel.Root.anchorMin = new Vector2(0f, 1f); panel.Root.anchorMax = panel.Root.anchorMin; panel.Root.pivot = pivot; panel.Root.sizeDelta = new Vector2(width, 48f); panel.Root.anchoredPosition = anchoredPosition; } private static RectTransform CreateRow(RectTransform parent, string name) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) RectTransform component = new GameObject(name, new Type[1] { typeof(RectTransform) }).GetComponent(); ((Transform)component).SetParent((Transform)(object)parent, false); component.pivot = new Vector2(0f, 1f); return component; } private static HoverPanelContext CreateHoverPanel(RectTransform parent, TMP_Text template, string name) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[2] { typeof(RectTransform), typeof(Image) }); val.SetActive(false); RectTransform component = val.GetComponent(); ((Transform)component).SetParent((Transform)(object)parent, false); Image component2 = val.GetComponent(); ((Graphic)component2).color = DefaultBackground; ((Graphic)component2).raycastTarget = false; TextMeshProUGUI val2 = CreateText(component, template, "Text", new Vector2(8f, 4f), new Vector2(-8f, -4f), (TextAlignmentOptions)514, 15f); ((TMP_Text)val2).enableAutoSizing = true; ((TMP_Text)val2).fontSizeMin = 10f; ((TMP_Text)val2).fontSizeMax = 15f; ((TMP_Text)val2).maxVisibleLines = 2; ((TMP_Text)val2).overflowMode = (TextOverflowModes)1; ((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0; ((TMP_Text)val2).richText = true; ((Component)val2).gameObject.SetActive(true); return new HoverPanelContext { Root = component, Text = val2 }; } private static void EnsureSlots(List slots, RectTransform row, int targetCount, Hud hud, Vector2 slotSize, Vector2 iconSize) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) while (slots.Count > targetCount) { SlotContext slotContext = slots[slots.Count - 1]; slots.RemoveAt(slots.Count - 1); HideSlot(slotContext); Object.Destroy((Object)(object)((Component)slotContext.Root).gameObject); } while (slots.Count < targetCount) { slots.Add(CreateSlot(row, slots.Count, hud, slotSize, iconSize)); } float num = (float)targetCount * slotSize.x + (float)Mathf.Max(0, targetCount - 1) * 1f; row.sizeDelta = new Vector2(num, slotSize.y); for (int i = 0; i < slots.Count; i++) { SlotContext slotContext2 = slots[i]; slotContext2.Root.sizeDelta = slotSize; slotContext2.Root.anchoredPosition = new Vector2((float)i * (slotSize.x + 1f), 0f); ((Graphic)slotContext2.Icon).rectTransform.sizeDelta = iconSize; } } private static SlotContext CreateSlot(RectTransform row, int index, Hud hud, Vector2 slotSize, Vector2 iconSize) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) RectTransform component = new GameObject($"Slot_{index}", new Type[1] { typeof(RectTransform) }).GetComponent(); ((Transform)component).SetParent((Transform)(object)row, false); component.anchorMin = new Vector2(0f, 1f); component.anchorMax = new Vector2(0f, 1f); component.pivot = new Vector2(0f, 1f); component.sizeDelta = slotSize; GameObject val = new GameObject("Icon", new Type[2] { typeof(RectTransform), typeof(Image) }); RectTransform component2 = val.GetComponent(); ((Transform)component2).SetParent((Transform)(object)component, false); component2.anchorMin = new Vector2(0.5f, 0.5f); component2.anchorMax = new Vector2(0.5f, 0.5f); component2.pivot = new Vector2(0.5f, 0.5f); component2.sizeDelta = iconSize; Image component3 = val.GetComponent(); ((Graphic)component3).raycastTarget = false; TextMeshProUGUI cornerText = CreateText(component, hud.m_foodTime[0], "Corner", new Vector2(4f, -2f), new Vector2(-3f, 0f), (TextAlignmentOptions)260, 13f); TextMeshProUGUI footerText = CreateText(component, hud.m_foodTime[0], "Footer", new Vector2(2f, 1f), new Vector2(-2f, 3f), (TextAlignmentOptions)1028, 11f); return new SlotContext { Root = component, Icon = component3, CornerText = cornerText, FooterText = footerText }; } internal static UITooltip? GetOrCreateTooltip(GameObject target, Hud hud) { UITooltip val = target.GetComponent(); if ((Object)(object)val != (Object)null && (Object)(object)val.m_tooltipPrefab != (Object)null) { return val; } if ((Object)(object)hud.m_pieceIconPrefab == (Object)null) { return null; } UITooltip component = hud.m_pieceIconPrefab.GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)component.m_tooltipPrefab == (Object)null) { return null; } if ((Object)(object)val == (Object)null) { val = target.AddComponent(); } val.m_tooltipPrefab = component.m_tooltipPrefab; return val; } internal static string FormatFoodNameForTooltip(string? foodName) { if (!string.IsNullOrWhiteSpace(foodName)) { return "" + foodName + ""; } return string.Empty; } private static TextMeshProUGUI CreateText(RectTransform parent, TMP_Text template, string name, Vector2 offsetMin, Vector2 offsetMax, TextAlignmentOptions alignment, float fontSize) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.SetActive(false); RectTransform component = val.GetComponent(); ((Transform)component).SetParent((Transform)(object)parent, false); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = offsetMin; component.offsetMax = offsetMax; TextMeshProUGUI obj = val.AddComponent(); ((TMP_Text)obj).font = template.font; ((TMP_Text)obj).fontSharedMaterial = template.fontSharedMaterial; ((TMP_Text)obj).fontSize = fontSize; ((TMP_Text)obj).alignment = alignment; ((TMP_Text)obj).textWrappingMode = (TextWrappingModes)0; ((Graphic)obj).raycastTarget = false; ((Graphic)obj).color = Color.white; return obj; } private static void UpdateHoverGuidance(PanelContext context) { Localization instance = Localization.instance; string selectedLanguage = instance.GetSelectedLanguage(); bool flag = DietConfig.GetChefRecentFoodPreferencePercent() > 0f; bool flag2 = DietConfig.GetChefHighTierSelectionStrength() > 0f; float chefMultiplierMin = DietConfig.GetChefMultiplierMin(); float chefMultiplierModeAtMaxCooking = DietConfig.GetChefMultiplierModeAtMaxCooking(); bool flag3 = chefMultiplierModeAtMaxCooking > chefMultiplierMin && !Mathf.Approximately(chefMultiplierModeAtMaxCooking, chefMultiplierMin); if (!context.GuidanceInitialized || !string.Equals(context.GuidanceLanguage, selectedLanguage, StringComparison.OrdinalIgnoreCase) || context.RecentPreferenceEnabled != flag || context.ChefTierEnabled != flag2 || context.ChefMultiplierEnabled != flag3) { context.GuidanceLanguage = selectedLanguage; context.GuidanceInitialized = true; context.RecentPreferenceEnabled = flag; context.ChefTierEnabled = flag2; context.ChefMultiplierEnabled = flag3; context.RecentGuidance = instance.Localize(flag ? "$finedining_diet_recent_chef_guidance" : "$finedining_diet_recent_chef_guidance_disabled"); context.ChefGuidance = instance.Localize((flag2 && flag3) ? "$finedining_diet_chef_cooking_guidance_both" : (flag2 ? "$finedining_diet_chef_cooking_guidance_tier" : (flag3 ? "$finedining_diet_chef_cooking_guidance_multiplier" : "$finedining_diet_chef_cooking_guidance_disabled"))); MarkHoverTextDirty(context.RecentSlots); MarkHoverTextDirty(context.ChefSlots); } } private static void MarkHoverTextDirty(List slots) { foreach (SlotContext slot in slots) { slot.HoverTextDirty = true; } } private static void UpdateRecentSlots(List slots, PlayerFoodStateData state, string guidance) { for (int i = 0; i < slots.Count; i++) { if (i >= state.Recent.Count) { HideSlot(slots[i]); continue; } HistoryEntryData historyEntryData = state.Recent[i]; float num = FoodRules.CalculateDiminishingScale(RecentHistoryService.GetNextStack(state, historyEntryData.Key, isChef: false)); bool flag = num < 1f && !Mathf.Approximately(num, 1f); bool flag2 = ChefCollectionService.GetEntry(state, historyEntryData.Key) != null && flag; float multiplier = (flag2 ? 1f : num); ShowSlot(slots[i], historyEntryData.Key, (historyEntryData.Stack > 1) ? historyEntryData.Stack.ToString(CultureInfo.InvariantCulture) : string.Empty, (flag && !flag2) ? ("x" + num.ToString("0.00", CultureInfo.InvariantCulture)) : string.Empty, isChef: false, flag2, historyEntryData.Stack, multiplier, guidance); } } private static void UpdateChefSlots(List slots, PlayerFoodStateData state, string guidance) { for (int i = 0; i < slots.Count; i++) { if (i >= state.Chef.Count) { HideSlot(slots[i]); continue; } ChefEntryData chefEntryData = state.Chef[i]; ShowSlot(slots[i], chefEntryData.Key, string.Empty, "x" + chefEntryData.Multiplier.ToString("0.00", CultureInfo.InvariantCulture), isChef: true, chefExemptsDiminishing: false, 0, chefEntryData.Multiplier, guidance); } } private static ItemData? GetFoodItem(string key, ObjectDB objectDb) { if (string.IsNullOrWhiteSpace(key)) { return null; } GameObject itemPrefab = objectDb.GetItemPrefab(key); ItemDrop val = (((Object)(object)itemPrefab == (Object)null) ? null : itemPrefab.GetComponent()); if ((Object)(object)val != (Object)null && val.m_itemData != null) { return val.m_itemData; } foreach (GameObject item in objectDb.m_items) { if (!((Object)(object)item == (Object)null)) { val = item.GetComponent(); if (!((Object)(object)val == (Object)null) && val.m_itemData != null && ((Object)item).name == key) { return val.m_itemData; } } } return null; } private static void ShowSlot(SlotContext slot, string key, string cornerText, string footerText, bool isChef, bool chefExemptsDiminishing, int stack, float multiplier, string guidance) { UpdateSlotItem(slot, key); UpdateSlotHoverText(slot, isChef, chefExemptsDiminishing, stack, multiplier, guidance); ((Component)slot.Root).gameObject.SetActive(true); ((TMP_Text)slot.CornerText).text = cornerText; ((Component)slot.CornerText).gameObject.SetActive(!string.IsNullOrWhiteSpace(cornerText)); ((TMP_Text)slot.FooterText).text = footerText; ((Component)slot.FooterText).gameObject.SetActive(!string.IsNullOrWhiteSpace(footerText)); } private static void UpdateSlotItem(SlotContext slot, string key) { ObjectDB instance = ObjectDB.instance; int num = (((Object)(object)instance == (Object)null) ? (-1) : instance.m_items.Count); if (!(slot.ItemKey == key) || !((Object)(object)slot.ItemObjectDb == (Object)(object)instance) || slot.ItemObjectDbCount != num) { ItemData val = (((Object)(object)instance == (Object)null) ? null : GetFoodItem(key, instance)); Sprite val2 = ((val != null) ? val.GetIcon() : null); slot.ItemKey = key; slot.ItemObjectDb = instance; slot.ItemObjectDbCount = num; slot.ItemNameToken = val?.m_shared.m_name ?? string.Empty; slot.Icon.sprite = val2; ((Behaviour)slot.Icon).enabled = (Object)(object)val2 != (Object)null; slot.HoverTextDirty = true; } } private static void UpdateSlotHoverText(SlotContext slot, bool isChef, bool chefExemptsDiminishing, int stack, float multiplier, string guidance) { Localization instance = Localization.instance; string selectedLanguage = instance.GetSelectedLanguage(); if (slot.HoverTextDirty || slot.HoverIsChef != isChef || slot.HoverChefExemptsDiminishing != chefExemptsDiminishing || slot.HoverStack != stack || !Mathf.Approximately(slot.HoverMultiplier, multiplier) || !(slot.HoverLanguage == selectedLanguage) || !(slot.HoverGuidance == guidance)) { string text = FormatFoodNameForTooltip(instance.Localize(slot.ItemNameToken)); string text2 = multiplier.ToString("0.00", CultureInfo.InvariantCulture); string text3 = (isChef ? ((!(multiplier > 1f) || Mathf.Approximately(multiplier, 1f)) ? instance.Localize("$finedining_diet_chef_unchanged", new string[1] { text }) : instance.Localize("$finedining_diet_chef_increase", new string[2] { text, text2 })) : (chefExemptsDiminishing ? instance.Localize("$finedining_diet_recent_chef_exempt", new string[2] { text, stack.ToString(CultureInfo.InvariantCulture) }) : ((multiplier < 1f && !Mathf.Approximately(multiplier, 1f)) ? instance.Localize("$finedining_diet_recent_diminished", new string[3] { text, stack.ToString(CultureInfo.InvariantCulture), text2 }) : ((stack != 1) ? instance.Localize("$finedining_diet_recent", new string[2] { text, stack.ToString(CultureInfo.InvariantCulture) }) : instance.Localize("$finedining_diet_recent_once", new string[1] { text }))))); slot.HoverText = (isChef ? (text3 + "\n" + guidance) : (guidance + "\n" + text3)); slot.HoverTextDirty = false; slot.HoverIsChef = isChef; slot.HoverChefExemptsDiminishing = chefExemptsDiminishing; slot.HoverStack = stack; slot.HoverMultiplier = multiplier; slot.HoverLanguage = selectedLanguage; slot.HoverGuidance = guidance; } } private static void HideSlot(SlotContext slot) { if (!((Object)(object)slot.Root == (Object)null) && !((Object)(object)slot.Icon == (Object)null)) { ((Component)slot.Root).gameObject.SetActive(false); } } private static void UpdateHoverPanel(HoverPanelContext panel, List slots) { SlotContext slotContext = null; foreach (SlotContext slot in slots) { if (!string.IsNullOrWhiteSpace(slot.HoverText) && ((Component)slot.Root).gameObject.activeInHierarchy && ((Behaviour)slot.Icon).enabled && IsHovered(slot.Icon, canHover: true)) { slotContext = slot; break; } } if (panel.HoveredSlot != slotContext) { panel.HoveredSlot = slotContext; panel.HoverStartedAt = Time.unscaledTime; ((Component)panel.Root).gameObject.SetActive(false); return; } string text = slotContext?.HoverText ?? string.Empty; bool flag = slotContext != null && Time.unscaledTime - panel.HoverStartedAt >= 0.5f; if (flag && !string.Equals(((TMP_Text)panel.Text).text, text, StringComparison.Ordinal)) { ((TMP_Text)panel.Text).text = text; } if (((Component)panel.Root).gameObject.activeSelf != flag) { ((Component)panel.Root).gameObject.SetActive(flag); } } internal static void UpdateTooltipHover(Image icon, UITooltip? tooltip, bool canHover) { bool flag = (Object)(object)tooltip != (Object)null && IsHovered(icon, canHover); if (flag && (Object)(object)tooltip != (Object)null && (Object)(object)GetCurrentTooltip() != (Object)(object)tooltip) { tooltip.OnHoverStart(((Component)icon).gameObject); } else if (!flag && (Object)(object)GetCurrentTooltip() == (Object)(object)tooltip) { UITooltip.HideTooltip(); } } private static bool IsHovered(Image icon, bool canHover) { //IL_0011: 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_0037: Unknown result type (might be due to invalid IL or missing references) Canvas canvas = ((Graphic)icon).canvas; Camera val = (((Object)(object)canvas != (Object)null && (int)canvas.renderMode != 0) ? canvas.worldCamera : null); if (canHover && Cursor.visible) { return RectTransformUtility.RectangleContainsScreenPoint(((Graphic)icon).rectTransform, Vector2.op_Implicit(ZInput.mousePosition), val); } return false; } private static UITooltip? GetCurrentTooltip() { return CurrentTooltipField.Invoke(); } } internal static class HudFoodSlots { internal static void EnsureFoodSlots(Hud hud) { //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) int num = hud.m_foodBars.Length; int maxFoodSlots = DietConfig.GetMaxFoodSlots(); if (maxFoodSlots <= num || num == 0 || hud.m_foodIcons.Length < num || hud.m_foodTime.Length < num) { return; } int num2 = num - 1; Image val = hud.m_foodIcons[0]; TMP_Text val2 = hud.m_foodTime[0]; Image val3 = hud.m_foodBars[num2]; Image val4 = hud.m_foodIcons[num2]; TMP_Text val5 = hud.m_foodTime[num2]; if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null || (Object)(object)val4 == (Object)null || (Object)(object)val5 == (Object)null) { return; } Transform parent = ((Component)val).transform.parent; RectTransform val6 = (RectTransform)(object)((parent is RectTransform) ? parent : null); Transform transform = ((Component)val3).transform; RectTransform val7 = (RectTransform)(object)((transform is RectTransform) ? transform : null); Transform parent2 = ((Component)val4).transform.parent; RectTransform val8 = (RectTransform)(object)((parent2 is RectTransform) ? parent2 : null); if ((Object)(object)val6 == (Object)null || (Object)(object)val7 == (Object)null || (Object)(object)val8 == (Object)null || !TryBuildPath((Transform)(object)val6, ((Component)val).transform, out List path) || !TryBuildPath((Transform)(object)val6, val2.transform, out List path2) || (Object)(object)FollowPath((Transform)(object)val8, path) != (Object)(object)((Component)val4).transform || (Object)(object)FollowPath((Transform)(object)val8, path2) != (Object)(object)val5.transform) { return; } Vector3 val9 = default(Vector3); ((Vector3)(ref val9))..ctor(0f, 138f, 0f); Vector3 val10 = default(Vector3); ((Vector3)(ref val10))..ctor(0f, 138f, 0f); if (num >= 2) { Image val11 = hud.m_foodBars[num2 - 1]; Image val12 = hud.m_foodIcons[num2 - 1]; if ((Object)(object)val11 == (Object)null || (Object)(object)val12 == (Object)null) { return; } Transform transform2 = ((Component)val11).transform; RectTransform val13 = (RectTransform)(object)((transform2 is RectTransform) ? transform2 : null); if (val13 == null) { return; } Transform parent3 = ((Component)val12).transform.parent; RectTransform val14 = (RectTransform)(object)((parent3 is RectTransform) ? parent3 : null); if (val14 == null) { return; } val9 = ((Transform)val7).localPosition - ((Transform)val13).localPosition; val10 = ((Transform)val8).localPosition - ((Transform)val14).localPosition; } Array.Resize(ref hud.m_foodBars, maxFoodSlots); Array.Resize(ref hud.m_foodIcons, maxFoodSlots); Array.Resize(ref hud.m_foodTime, maxFoodSlots); RectTransform val15 = val7; RectTransform val16 = val8; for (int i = num; i < maxFoodSlots; i++) { GameObject val17 = Object.Instantiate(((Component)val15).gameObject, ((Transform)val15).parent); RectTransform component = val17.GetComponent(); ((Transform)component).localPosition = ((Transform)val15).localPosition + val9; val15 = component; hud.m_foodBars[i] = val17.GetComponent(); ((Component)hud.m_foodBars[i]).gameObject.SetActive(false); RectTransform component2 = Object.Instantiate(((Component)val16).gameObject, ((Transform)val16).parent).GetComponent(); ((Transform)component2).localPosition = ((Transform)val16).localPosition + val10; val16 = component2; Transform val18 = FollowPath((Transform)(object)component2, path); Transform val19 = FollowPath((Transform)(object)component2, path2); hud.m_foodIcons[i] = ((Component)val18).GetComponent(); hud.m_foodTime[i] = ((Component)val19).GetComponent(); ((Component)hud.m_foodIcons[i]).gameObject.SetActive(false); ((Component)hud.m_foodTime[i]).gameObject.SetActive(false); } } internal static void LimitVisibleSlots(Hud hud, Player player) { PlayerFoodStateData state = FoodStateStore.GetState(player); int currentSlots = FoodSlotProgression.GetCurrentSlots(player, state); for (int i = 0; i < hud.m_foodBars.Length; i++) { bool flag = i < currentSlots; if ((Object)(object)hud.m_foodBars[i] != (Object)null && !flag) { ((Component)hud.m_foodBars[i]).gameObject.SetActive(false); } if (i < hud.m_foodIcons.Length && (Object)(object)hud.m_foodIcons[i] != (Object)null) { Transform parent = ((Component)hud.m_foodIcons[i]).transform.parent; if ((Object)(object)parent != (Object)null) { ((Component)parent).gameObject.SetActive(flag); } } if (i < hud.m_foodTime.Length && (Object)(object)hud.m_foodTime[i] != (Object)null && !flag) { ((Component)hud.m_foodTime[i]).gameObject.SetActive(false); } } } internal static void UpdateTooltips(Hud hud, Player player) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) List foods = player.GetFoods(); for (int i = 0; i < hud.m_foodIcons.Length; i++) { Image val = hud.m_foodIcons[i]; if (!((Object)(object)val == (Object)null)) { UITooltip orCreateTooltip = HudFoodPanels.GetOrCreateTooltip(((Component)val).gameObject, hud); if (!((Object)(object)orCreateTooltip == (Object)null)) { bool flag = i < foods.Count && foods[i]?.m_item?.m_shared != null; string foodName = (flag ? Localization.instance.Localize(foods[i].m_item.m_shared.m_name) : string.Empty); orCreateTooltip.Set(string.Empty, HudFoodPanels.FormatFoodNameForTooltip(foodName), (RectTransform)null, default(Vector2)); HudFoodPanels.UpdateTooltipHover(val, orCreateTooltip, flag && ((Behaviour)val).isActiveAndEnabled); } } } } private static bool TryBuildPath(Transform root, Transform child, out List path) { path = new List(); Transform val = child; while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root) { path.Add(val.GetSiblingIndex()); val = val.parent; } if ((Object)(object)val != (Object)(object)root) { path.Clear(); return false; } path.Reverse(); return true; } private static Transform? FollowPath(Transform root, List path) { Transform val = root; foreach (int item in path) { if (item < 0 || item >= val.childCount) { return null; } val = val.GetChild(item); } return val; } } internal static class CookingProductionBonusCore { internal static float CalculatePerItemChance(float skillFactor, float chanceAtMaxCookingPercent) { double num = NormalizeNonNegative(skillFactor); double num2 = ClampChancePercent(chanceAtMaxCookingPercent) / 100.0; if (num <= 0.0 || num2 <= 0.0) { return 0f; } return (float)ClampProbability(num * num2); } internal static int RollBonusItems(int baseItemCount, float itemChance, Func nextRandomValue) { if (baseItemCount <= 0) { return 0; } int num = int.MaxValue - baseItemCount; if (num <= 0) { return 0; } double num2 = ClampProbability(itemChance); if (num2 <= 0.0) { return 0; } if (num2 >= 1.0) { return Math.Min(baseItemCount, num); } if (nextRandomValue == null) { throw new ArgumentNullException("nextRandomValue"); } int num3 = 0; for (int i = 0; i < baseItemCount; i++) { if (num3 >= num) { break; } if ((double)nextRandomValue() < num2) { num3++; } } return num3; } private static double NormalizeNonNegative(float value) { if (!float.IsNaN(value) && !(value <= 0f)) { return value; } return 0.0; } private static double ClampProbability(double value) { if (double.IsNaN(value) || value <= 0.0) { return 0.0; } if (!double.IsPositiveInfinity(value) && !(value >= 1.0)) { return value; } return 1.0; } private static double ClampChancePercent(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } if (!float.IsPositiveInfinity(value) && !(value >= 25f)) { return value; } return 25.0; } } internal static class CookingProductionBonusSystem { internal const int UseVanillaBonus = -1; private const int MaximumIndependentRolls = 10000; private static readonly char[] ExclusionSeparators = new char[4] { ',', ';', '\r', '\n' }; private static readonly FieldRef CraftRecipeField = AccessTools.FieldRefAccess("m_craftRecipe"); private static bool _largeOutputWarningLogged; internal static int CalculateCookingSkillBonusOrUseVanilla(InventoryGui gui, CraftingStation station, int baseItemCount, float skillFactor) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown if ((Object)station == (Object)null || (int)station.m_craftingSkill != 105) { return -1; } if ((Object)gui == (Object)null || baseItemCount <= 0) { return 0; } Recipe val = CraftRecipeField.Invoke(gui); if ((Object)val == (Object)null || (Object)val.m_item == (Object)null || val.m_item.m_itemData.m_shared.m_maxStackSize <= 1) { return 0; } if (IsExcludedOutputPrefab(((Object)((Component)val.m_item).gameObject).name)) { return 0; } float num = CookingProductionBonusCore.CalculatePerItemChance(skillFactor, DietConfig.GetCookingBonusChanceAtMaxCookingPercent()); if (num <= 0f) { return 0; } if (baseItemCount > 10000 && num < 1f) { if (!_largeOutputWarningLogged) { _largeOutputWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)($"A Cooking recipe produced more than {10000} base items; " + "using Valheim's production-bonus calculation to avoid a long main-thread roll loop.")); } return -1; } return CookingProductionBonusCore.RollBonusItems(baseItemCount, num, NextRandomValue); } internal static float CalculateConfiguredCookingChance(float skillFactor) { return CookingProductionBonusCore.CalculatePerItemChance(skillFactor, DietConfig.GetCookingBonusChanceAtMaxCookingPercent()); } internal static int RollConfiguredBonusItems(string outputPrefabName, int baseItemCount, float skillFactor, float chanceAtMaxCookingPercent) { if (baseItemCount <= 0 || IsExcludedOutputPrefab(outputPrefabName)) { return 0; } float itemChance = CookingProductionBonusCore.CalculatePerItemChance(skillFactor, chanceAtMaxCookingPercent); return CookingProductionBonusCore.RollBonusItems(baseItemCount, itemChance, NextRandomValue); } internal static bool IsExcludedOutputPrefab(string prefabName) { return MatchesExcludedOutputPrefab(prefabName, DietConfig.GetCookingBonusExcludedOutputPrefabs()); } private static float NextRandomValue() { return Random.value; } private static bool MatchesExcludedOutputPrefab(string prefabName, string patterns) { if (string.IsNullOrEmpty(prefabName) || string.IsNullOrWhiteSpace(patterns)) { return false; } string[] array = patterns.Split(ExclusionSeparators, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0 && WildcardMatches(prefabName, text)) { return true; } } return false; } private static bool WildcardMatches(string value, string pattern) { int num = 0; int i = 0; int num2 = -1; int num3 = -1; while (num < value.Length) { if (i < pattern.Length && pattern[i] != '*' && CharactersEqual(value[num], pattern[i])) { num++; i++; continue; } if (i < pattern.Length && pattern[i] == '*') { num2 = i++; num3 = num; continue; } if (num2 < 0) { return false; } i = num2 + 1; num = ++num3; } for (; i < pattern.Length && pattern[i] == '*'; i++) { } return i == pattern.Length; } private static bool CharactersEqual(char left, char right) { if (left != right) { return char.ToUpperInvariant(left) == char.ToUpperInvariant(right); } return true; } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "sighsorry.RepairRequiresMaterials" })] internal static class InventoryGuiCookingProductionBonusPatch { private static readonly MethodInfo GetAmountMethod = AccessTools.Method(typeof(Recipe), "GetAmount", new Type[4] { typeof(int), typeof(int).MakeByRefType(), typeof(ItemData).MakeByRefType(), typeof(int) }, (Type[])null); private static readonly MethodInfo GetCurrentCraftingStationMethod = AccessTools.Method(typeof(Player), "GetCurrentCraftingStation", (Type[])null, (Type[])null); private static readonly MethodInfo BonusHelperMethod = AccessTools.Method(typeof(CookingProductionBonusSystem), "CalculateCookingSkillBonusOrUseVanilla", (Type[])null, (Type[])null); private static readonly MethodInfo RandomValueGetter = AccessTools.PropertyGetter(typeof(Random), "value"); private static readonly FieldInfo CraftUpgradeItemField = AccessTools.Field(typeof(InventoryGui), "m_craftUpgradeItem"); private static readonly FieldInfo CraftBonusChanceField = AccessTools.Field(typeof(InventoryGui), "m_craftBonusChance"); private static readonly FieldInfo CraftBonusAmountField = AccessTools.Field(typeof(InventoryGui), "m_craftBonusAmount"); private static bool _patternWarningLogged; [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { List list = new List(instructions); try { if (!TryInject(list, generator, out string failure)) { LogPatternFailure(failure); } } catch (Exception arg) { LogPatternFailure($"unexpected transpiler error: {arg}"); } return list; } private static bool TryInject(List codes, ILGenerator generator, out string failure) { //IL_0389: Unknown result type (might be due to invalid IL or missing references) //IL_0390: Expected O, but got Unknown //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03b4: Expected O, but got Unknown //IL_03e5: Unknown result type (might be due to invalid IL or missing references) //IL_03ef: Expected O, but got Unknown //IL_0416: Unknown result type (might be due to invalid IL or missing references) //IL_0420: Expected O, but got Unknown //IL_042d: Unknown result type (might be due to invalid IL or missing references) //IL_0437: Expected O, but got Unknown //IL_0457: Unknown result type (might be due to invalid IL or missing references) //IL_0461: Expected O, but got Unknown //IL_0482: Unknown result type (might be due to invalid IL or missing references) //IL_048c: Expected O, but got Unknown failure = string.Empty; int num = FindCall(codes, GetAmountMethod, 0); int num2 = FindCall(codes, GetCurrentCraftingStationMethod, 0); if (num < 0 || num2 < 0 || num >= num2) { failure = "could not locate Recipe.GetAmount and GetCurrentCraftingStation anchors"; return false; } if (num + 1 >= codes.Count || !TryGetStoredLocal(codes[num + 1], out var localIndex)) { failure = "could not resolve the pre-bonus result amount local"; return false; } int index = num2 + 1; int index2 = num2 + 2; int num3 = num2 + 3; if (num3 >= codes.Count || !TryGetStoredLocal(codes[index], out var localIndex2) || !IsLoadConstantZero(codes[index2]) || !TryGetStoredLocal(codes[num3], out var localIndex3)) { failure = "the vanilla crafting-bonus local initialization changed"; return false; } int num4 = FindCall(codes, RandomValueGetter, num3 + 1); int num5 = FindFieldLoad(codes, CraftBonusChanceField, num4 + 1); int num6 = FindFieldLoad(codes, CraftBonusAmountField, num5 + 1); if (num4 < 3 || num5 <= num4 || num6 <= num5) { failure = "could not locate the ordered vanilla production-bonus roll"; return false; } int num7 = num4 - 3; if (num7 <= 0 || !TryGetStoredLocal(codes[num7 - 1], out var localIndex4) || !IsLoadConstantZero(codes[num7]) || !TryGetStoredLocal(codes[num7 + 1], out var localIndex5) || !TryGetBranchLabel(codes[num7 + 2], IsUnconditionalBranch, out var label)) { failure = "could not locate the one-time vanilla bonus-loop initialization"; return false; } int num8 = num6 - 2; int index3 = num6 + 2; int index4 = num6 + 3; int index5 = num6 + 4; int num9 = num6 + 6; if (num8 <= num4 || num9 >= codes.Count || !LoadsLocal(codes[num8], localIndex3) || codes[num6 - 1].opcode != OpCodes.Ldarg_0 || codes[num6 + 1].opcode != OpCodes.Add || !StoresLocal(codes[index3], localIndex3) || !LoadsLocal(codes[index4], localIndex) || !LoadsLocal(codes[index5], localIndex3) || codes[num6 + 5].opcode != OpCodes.Add || !StoresLocal(codes[num9], localIndex)) { failure = "the vanilla production-bonus accumulation pattern changed"; return false; } int num10 = FindInstructionWithLabel(codes, label, num9 + 1); if (num10 < 0 || num10 + 2 >= codes.Count || !LoadsLocal(codes[num10], localIndex5) || !TryGetBranchLabel(codes[num10 + 2], IsBranchLess, out var label2) || !codes[num4].labels.Contains(label2)) { failure = "the vanilla production-bonus loop boundary changed"; return false; } int num11 = FindFieldLoad(codes, CraftUpgradeItemField, num9 + 1); int num12 = num11 - 1; if (num11 <= num9 || num12 < 0 || num11 + 1 >= codes.Count || codes[num12].opcode != OpCodes.Ldarg_0 || !IsBranchTrue(codes[num11 + 1])) { failure = "could not locate the post-bonus inventory-capacity check"; return false; } Label label3 = generator.DefineLabel(); Label label4; if (codes[num12].labels.Count > 0) { label4 = codes[num12].labels[0]; } else { label4 = generator.DefineLabel(); codes[num12].labels.Add(label4); } CodeInstruction val = new CodeInstruction(OpCodes.Ldc_I4_0, (object)null); val.labels.Add(label3); List list = new List { new CodeInstruction(OpCodes.Ldarg_0, (object)null), CreateLoadLocal(localIndex2), CreateLoadLocal(localIndex), CreateLoadLocal(localIndex4), new CodeInstruction(OpCodes.Call, (object)BonusHelperMethod), CloneWithoutMetadata(codes[num3]), CreateLoadLocal(localIndex3), new CodeInstruction(OpCodes.Ldc_I4_0, (object)null), new CodeInstruction(OpCodes.Blt, (object)label3), CreateLoadLocal(localIndex), CreateLoadLocal(localIndex3), new CodeInstruction(OpCodes.Add, (object)null), CloneWithoutMetadata(codes[num + 1]), new CodeInstruction(OpCodes.Br, (object)label4), val, CloneWithoutMetadata(codes[num3]) }; list[0].labels.AddRange(codes[num7].labels); codes[num7].labels.Clear(); list[0].blocks.AddRange(codes[num7].blocks); codes[num7].blocks.Clear(); codes.InsertRange(num7, list); return true; } private static int FindCall(List codes, MethodInfo method, int startIndex) { for (int i = Math.Max(0, startIndex); i < codes.Count; i++) { if ((codes[i].opcode == OpCodes.Call || codes[i].opcode == OpCodes.Callvirt) && object.Equals(codes[i].operand, method)) { return i; } } return -1; } private static int FindFieldLoad(List codes, FieldInfo field, int startIndex) { for (int i = Math.Max(0, startIndex); i < codes.Count; i++) { if ((codes[i].opcode == OpCodes.Ldfld || codes[i].opcode == OpCodes.Ldsfld) && object.Equals(codes[i].operand, field)) { return i; } } return -1; } private static int FindInstructionWithLabel(List codes, Label label, int startIndex) { for (int i = Math.Max(0, startIndex); i < codes.Count; i++) { if (codes[i].labels.Contains(label)) { return i; } } return -1; } private static bool LoadsLocal(CodeInstruction instruction, int localIndex) { if (TryGetLocalIndex(instruction, load: true, out var localIndex2)) { return localIndex2 == localIndex; } return false; } private static bool StoresLocal(CodeInstruction instruction, int localIndex) { if (TryGetLocalIndex(instruction, load: false, out var localIndex2)) { return localIndex2 == localIndex; } return false; } private static bool TryGetStoredLocal(CodeInstruction instruction, out int localIndex) { return TryGetLocalIndex(instruction, load: false, out localIndex); } private static bool TryGetLocalIndex(CodeInstruction instruction, bool load, out int localIndex) { localIndex = -1; OpCode opcode = instruction.opcode; if (load) { if (opcode == OpCodes.Ldloc_0) { localIndex = 0; return true; } if (opcode == OpCodes.Ldloc_1) { localIndex = 1; return true; } if (opcode == OpCodes.Ldloc_2) { localIndex = 2; return true; } if (opcode == OpCodes.Ldloc_3) { localIndex = 3; return true; } if (opcode != OpCodes.Ldloc && opcode != OpCodes.Ldloc_S) { return false; } } else { if (opcode == OpCodes.Stloc_0) { localIndex = 0; return true; } if (opcode == OpCodes.Stloc_1) { localIndex = 1; return true; } if (opcode == OpCodes.Stloc_2) { localIndex = 2; return true; } if (opcode == OpCodes.Stloc_3) { localIndex = 3; return true; } if (opcode != OpCodes.Stloc && opcode != OpCodes.Stloc_S) { return false; } } object operand = instruction.operand; if (!(operand is LocalBuilder localBuilder)) { if (!(operand is LocalVariableInfo localVariableInfo)) { if (!(operand is byte b)) { if (operand is int num) { localIndex = num; return true; } return false; } localIndex = b; return true; } localIndex = localVariableInfo.LocalIndex; return true; } localIndex = localBuilder.LocalIndex; return true; } private static CodeInstruction CreateLoadLocal(int localIndex) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown if (localIndex <= 255) { return (CodeInstruction)(localIndex switch { 0 => (object)new CodeInstruction(OpCodes.Ldloc_0, (object)null), 1 => (object)new CodeInstruction(OpCodes.Ldloc_1, (object)null), 2 => (object)new CodeInstruction(OpCodes.Ldloc_2, (object)null), 3 => (object)new CodeInstruction(OpCodes.Ldloc_3, (object)null), _ => (object)new CodeInstruction(OpCodes.Ldloc_S, (object)(byte)localIndex), }); } return new CodeInstruction(OpCodes.Ldloc, (object)localIndex); } private static bool IsLoadConstantZero(CodeInstruction instruction) { if (!(instruction.opcode == OpCodes.Ldc_I4_0) && (!(instruction.opcode == OpCodes.Ldc_I4) || !object.Equals(instruction.operand, 0))) { if (instruction.opcode == OpCodes.Ldc_I4_S) { return Convert.ToInt32(instruction.operand) == 0; } return false; } return true; } private static bool IsUnconditionalBranch(OpCode opcode) { if (!(opcode == OpCodes.Br)) { return opcode == OpCodes.Br_S; } return true; } private static bool IsBranchLess(OpCode opcode) { if (!(opcode == OpCodes.Blt)) { return opcode == OpCodes.Blt_S; } return true; } private static bool IsBranchTrue(CodeInstruction instruction) { if (!(instruction.opcode == OpCodes.Brtrue)) { return instruction.opcode == OpCodes.Brtrue_S; } return true; } private static bool TryGetBranchLabel(CodeInstruction instruction, Func opcodePredicate, out Label label) { if (opcodePredicate(instruction.opcode) && instruction.operand is Label label2) { label = label2; return true; } label = default(Label); return false; } private static CodeInstruction CloneWithoutMetadata(CodeInstruction source) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown return new CodeInstruction(source.opcode, source.operand); } private static void LogPatternFailure(string reason) { if (!_patternWarningLogged) { _patternWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)("Per-item Cooking bonus patch was not applied; vanilla behavior remains active (" + reason + ").")); } } } internal static class CookingStationAutoPopCore { internal const float CookingExperienceOnAdd = 0.4f; internal const float CookingExperienceOnCollect = 0.6f; internal static float ClampSkillFactor(float value) { if (float.IsNaN(value) || value <= 0f) { return 0f; } if (!float.IsPositiveInfinity(value) && !(value >= 1f)) { return value; } return 1f; } internal static bool ShouldAutoPop(float skillFactor, float roll, bool hasDistinctOvercookStage) { if (!hasDistinctOvercookStage) { return false; } float num = ClampSkillFactor(skillFactor); if (num <= 0f) { return false; } if (num >= 1f) { return true; } if (!float.IsNaN(roll)) { return roll < num; } return false; } internal static bool HasDistinctOvercookStage(float cookTime, string? cookedPrefabName, string? overcookedPrefabName) { if (!float.IsNaN(cookTime) && !float.IsInfinity(cookTime) && cookTime > 0f && !string.IsNullOrEmpty(cookedPrefabName) && !string.IsNullOrEmpty(overcookedPrefabName) && !string.Equals(cookedPrefabName, "Coal", StringComparison.Ordinal)) { return !string.Equals(cookedPrefabName, overcookedPrefabName, StringComparison.Ordinal); } return false; } internal static int ClampBonusCount(int value) { return (value > 0) ? 1 : 0; } } internal readonly struct CookingStationSlotPlan { internal bool AutoPop { get; } internal bool CollectionExperiencePrepaid { get; } internal int BonusCount { get; } internal string ExpectedInput { get; } internal string ExpectedOutput { get; } internal CookingStationSlotPlan(bool autoPop, bool collectionExperiencePrepaid, int bonusCount, string expectedInput, string expectedOutput) { AutoPop = autoPop; CollectionExperiencePrepaid = collectionExperiencePrepaid; BonusCount = CookingStationAutoPopCore.ClampBonusCount(bonusCount); ExpectedInput = expectedInput ?? string.Empty; ExpectedOutput = expectedOutput ?? string.Empty; } internal CookingStationSlotPlan DisableAutoPop() { return new CookingStationSlotPlan(autoPop: false, CollectionExperiencePrepaid, BonusCount, ExpectedInput, ExpectedOutput); } } internal static class CookingStationAutoPopSystem { private sealed class RegistrationMarker { } private sealed class OwnerPendingPlans { internal Dictionary BySender { get; } = new Dictionary(); } private readonly struct PendingOwnerPlan { internal string ExpectedInput { get; } internal CookingStationSlotPlan Plan { get; } internal float StartedAt { get; } internal PendingOwnerPlan(string expectedInput, CookingStationSlotPlan plan, float startedAt) { ExpectedInput = expectedInput; Plan = plan; StartedAt = startedAt; } } internal const string RequestPlanRpc = "FineDining_CookingStation_RequestPlan"; internal const string AutoPopBonusEffectRpc = "FineDining_CookingStation_AutoPopBonusEffect"; internal const string SlotStateKeyPrefix = "sighsorry.FineDining.CookingStation."; internal const int SlotPlanVersion = 1; private const float PendingPlanTimeoutSeconds = 5f; private const int StatusNotDone = 0; private const int StatusDone = 1; private const int StatusBurnt = 2; private static readonly MethodInfo? SpawnItemMethod = AccessTools.DeclaredMethod(typeof(CookingStation), "SpawnItem", new Type[3] { typeof(string), typeof(int), typeof(Vector3) }, (Type[])null); private static ConditionalWeakTable _registeredStations = new ConditionalWeakTable(); private static ConditionalWeakTable _ownerPendingPlans = new ConditionalWeakTable(); private static bool _requestPatchReady; private static bool _experiencePatchReady; private static bool _registrationWarningLogged; private static bool _requestWarningLogged; private static bool _autoPopWarningLogged; private static bool _bonusEffectWarningLogged; internal static bool ManagedAddPatchesReady { get { if (_requestPatchReady) { return _experiencePatchReady; } return false; } } internal static void Reset() { _registeredStations = new ConditionalWeakTable(); _ownerPendingPlans = new ConditionalWeakTable(); _requestPatchReady = false; _experiencePatchReady = false; _registrationWarningLogged = false; _requestWarningLogged = false; _autoPopWarningLogged = false; _bonusEffectWarningLogged = false; } internal static void MarkRequestPatchReady() { _requestPatchReady = true; } internal static void MarkExperiencePatchReady() { _experiencePatchReady = true; } internal static void RegisterRpcs(CookingStation station) { ZNetView nView = GetNView(station); _registeredStations.Remove(station); if ((Object)(object)nView == (Object)null || !nView.IsValid()) { return; } try { nView.Unregister("FineDining_CookingStation_RequestPlan"); nView.Unregister("FineDining_CookingStation_AutoPopBonusEffect"); nView.Register("FineDining_CookingStation_RequestPlan", (Action)delegate(long sender, string itemName, int skillPermille, float autoRoll) { ReceivePlan(station, sender, itemName, skillPermille, autoRoll); }); nView.Register("FineDining_CookingStation_AutoPopBonusEffect", (Action)delegate(long sender, int bonusCount) { ReceiveBonusEffect(station, sender, bonusCount); }); _registeredStations.Add(station, new RegistrationMarker()); } catch (Exception arg) { if (!_registrationWarningLogged) { _registrationWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)$"Could not register the CookingStation auto-eject RPCs; vanilla insertion remains active ({arg})."); } } } internal static void RequestAdd(ZNetView? nview, string vanillaRpcName, object[]? vanillaParameters, Humanoid user, CookingStation station) { if (!ManagedAddPatchesReady) { InvokeVanillaAdd(nview, vanillaRpcName, vanillaParameters); return; } Player localPlayer = Player.m_localPlayer; Player val = (Player)(object)((user is Player) ? user : null); if ((Object)(object)nview == (Object)null || (Object)(object)station == (Object)null || (Object)(object)val == (Object)null || (Object)(object)localPlayer == (Object)null || (Object)(object)val != (Object)(object)localPlayer || vanillaParameters == null || vanillaParameters.Length != 1 || !(vanillaParameters[0] is string text) || string.IsNullOrEmpty(text)) { InvokeVanillaAdd(nview, vanillaRpcName, vanillaParameters); if (localPlayer != null) { ((Character)localPlayer).RaiseSkill((SkillType)105, 0.4f); } return; } bool flag = false; bool flag2 = false; if (nview.IsValid() && _registeredStations.TryGetValue(station, out RegistrationMarker _)) { int num = Mathf.RoundToInt(CookingStationAutoPopCore.ClampSkillFactor(((Character)val).GetSkillFactor((SkillType)105)) * 1000f); float skillFactor = (float)num / 1000f; float value2 = Random.value; flag2 = CookingStationAutoPopCore.ShouldAutoPop(skillFactor, value2, CanAutoPopConversion(station, FindInputConversion(station, text))); try { nview.InvokeRPC("FineDining_CookingStation_RequestPlan", new object[3] { text, num, value2 }); flag = true; } catch (Exception exception) { LogRequestFailure(exception); } } InvokeVanillaAdd(nview, vanillaRpcName, vanillaParameters); ((Character)val).RaiseSkill((SkillType)105, 0.4f); if (flag && flag2) { ((Character)val).RaiseSkill((SkillType)105, 0.6f); } } internal static void RaiseAddExperienceOrDefer(Character character, SkillType skill, float vanillaAmount) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (!ManagedAddPatchesReady) { character.RaiseSkill(skill, vanillaAmount); } } internal static void RaiseCollectionExperience(Character character, SkillType skill, float vanillaAmount, CookingStation station) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (!TryGetFirstDonePlan(station, out var _, out var plan, out var _) || !plan.CollectionExperiencePrepaid) { character.RaiseSkill(skill, vanillaAmount); } } internal static void PrepareAdd(CookingStation station, long sender, string itemName) { ZNetView nView = GetNView(station); ZDO val = (((Object)(object)nView != (Object)null && nView.IsValid()) ? nView.GetZDO() : null); int num = FindFreeSlot(station, val); if ((Object)(object)nView == (Object)null || !nView.IsOwner() || val == null || num < 0) { DiscardPendingPlan(station, sender); return; } ClearPlan(station, num); if (TryTakePendingPlan(station, sender, itemName, out var plan)) { WritePlan(station, num, plan); } } internal static bool TryGetFirstDonePlan(CookingStation station, out int slot, out CookingStationSlotPlan plan, out bool outputMatches) { plan = default(CookingStationSlotPlan); outputMatches = false; if (!TryGetFirstDoneSlot(station, out slot, out string itemName) || !TryReadPlan(station, slot, out plan)) { return false; } outputMatches = string.Equals(itemName, plan.ExpectedOutput, StringComparison.Ordinal); return true; } internal static bool TryGetFirstDoneSlot(CookingStation station, out int slot, out string itemName) { slot = -1; itemName = string.Empty; ZDO zdo = GetZdo(station); if (zdo == null || station.m_slots == null) { return false; } for (int i = 0; i < station.m_slots.Length; i++) { string text = zdo.GetString("slot" + i, ""); if (!string.IsNullOrEmpty(text) && IsDoneItem(station, text)) { slot = i; itemName = text; return true; } } return false; } internal static bool TryReadPlan(CookingStation station, int slot, out CookingStationSlotPlan plan) { plan = default(CookingStationSlotPlan); ZDO zdo = GetZdo(station); if (zdo == null || slot < 0 || zdo.GetInt(GetPlanKey(slot, "version"), 0) != 1) { return false; } string text = zdo.GetString(GetPlanKey(slot, "input"), ""); string text2 = zdo.GetString(GetPlanKey(slot, "output"), ""); if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2)) { return false; } plan = new CookingStationSlotPlan(zdo.GetInt(GetPlanKey(slot, "auto"), 0) != 0, zdo.GetInt(GetPlanKey(slot, "prepaid"), 0) != 0, zdo.GetInt(GetPlanKey(slot, "bonus"), 0), text, text2); return true; } internal static bool ShouldShowAutoEject(CookingStation station, int slot, string currentItemName, string conversionOutputName, bool outputPhase) { if (TryReadPlan(station, slot, out var plan) && IsAutoPopPlanEligible(station, plan)) { return IsMatchingAutoEjectPlan(plan, currentItemName, conversionOutputName, outputPhase); } return false; } private static bool IsMatchingAutoEjectPlan(CookingStationSlotPlan plan, string currentItemName, string conversionOutputName, bool outputPhase) { if (!plan.AutoPop || string.IsNullOrEmpty(currentItemName) || string.Equals(plan.ExpectedOutput, "Coal", StringComparison.Ordinal)) { return false; } if (outputPhase) { return string.Equals(currentItemName, plan.ExpectedOutput, StringComparison.Ordinal); } if (string.Equals(currentItemName, plan.ExpectedInput, StringComparison.Ordinal)) { return string.Equals(conversionOutputName, plan.ExpectedOutput, StringComparison.Ordinal); } return false; } internal static void ClearPlan(CookingStation station, int slot) { ZDO zdo = GetZdo(station); if (zdo != null && slot >= 0) { zdo.Set(GetPlanKey(slot, "input"), string.Empty); zdo.Set(GetPlanKey(slot, "output"), string.Empty); zdo.Set(GetPlanKey(slot, "auto"), 0); zdo.Set(GetPlanKey(slot, "prepaid"), 0); zdo.Set(GetPlanKey(slot, "bonus"), 0); zdo.Set(GetPlanKey(slot, "version"), 0); } } internal static bool IsSlotEmpty(CookingStation station, int slot) { ZDO zdo = GetZdo(station); if (zdo != null && slot >= 0) { return string.IsNullOrEmpty(zdo.GetString("slot" + slot, "")); } return false; } internal static string GetPlanKey(int slot, string field) { if (slot < 0) { throw new ArgumentOutOfRangeException("slot"); } return "sighsorry.FineDining.CookingStation." + slot + "." + field; } internal static void ProcessCompletedSlots(CookingStation station) { ZNetView nView = GetNView(station); ZDO val = (((Object)(object)nView != (Object)null && nView.IsValid()) ? nView.GetZDO() : null); if ((Object)(object)nView == (Object)null || val == null || !nView.IsOwner() || station.m_slots == null) { return; } for (int i = 0; i < station.m_slots.Length; i++) { if (!TryReadPlan(station, i, out var plan)) { continue; } string text = val.GetString("slot" + i, ""); if (string.IsNullOrEmpty(text)) { ClearPlan(station, i); continue; } if (plan.AutoPop && !IsAutoPopPlanEligible(station, plan)) { plan = plan.DisableAutoPop(); WritePlan(station, i, plan); } switch (val.GetInt("slotstatus" + i, 0)) { case 0: if (!string.Equals(text, plan.ExpectedInput, StringComparison.Ordinal)) { PreservePrepaidFallbackOrClear(station, i, plan); } break; case 1: if (string.Equals(text, plan.ExpectedOutput, StringComparison.Ordinal)) { if (plan.AutoPop) { TryAutoPopSlot(station, i, plan); } } else { PreservePrepaidFallbackOrClear(station, i, plan); } break; case 2: { bool flag = (Object)(object)station.m_overCookedItem != (Object)null && string.Equals(text, ((Object)((Component)station.m_overCookedItem).gameObject).name, StringComparison.Ordinal); if (plan.AutoPop && flag) { TryAutoPopSlot(station, i, plan); } else if (plan.CollectionExperiencePrepaid) { WritePlan(station, i, plan.DisableAutoPop()); } else { ClearPlan(station, i); } break; } default: PreservePrepaidFallbackOrClear(station, i, plan); break; } } } private static void ReceivePlan(CookingStation station, long sender, string itemName, int skillPermille, float autoRoll) { ZNetView nView = GetNView(station); bool flag = !ManagedAddPatchesReady || sender == 0L || string.IsNullOrEmpty(itemName); if (!flag) { bool flag2 = ((skillPermille < 0 || skillPermille > 1000) ? true : false); flag = flag2; } if (flag || float.IsNaN(autoRoll) || autoRoll < 0f || autoRoll > 1f || (Object)(object)nView == (Object)null || !nView.IsValid() || !nView.IsOwner()) { return; } ItemConversion val = FindInputConversion(station, itemName); if (!((Object)(object)val?.m_to == (Object)null)) { float skillFactor = (float)skillPermille / 1000f; bool flag3 = CookingStationAutoPopCore.ShouldAutoPop(skillFactor, autoRoll, CanAutoPopConversion(station, val)); int bonusCount = CookingStationAutoPopCore.ClampBonusCount(CookingProductionBonusSystem.RollConfiguredBonusItems(((Object)((Component)val.m_to).gameObject).name, 1, skillFactor, DietConfig.GetCookingBonusChanceAtMaxCookingPercent())); CookingStationSlotPlan plan = new CookingStationSlotPlan(flag3, flag3, bonusCount, itemName, ((Object)((Component)val.m_to).gameObject).name); if (!_ownerPendingPlans.TryGetValue(station, out OwnerPendingPlans value)) { value = new OwnerPendingPlans(); _ownerPendingPlans.Add(station, value); } value.BySender[sender] = new PendingOwnerPlan(itemName, plan, Time.unscaledTime); } } private static bool TryTakePendingPlan(CookingStation station, long sender, string itemName, out CookingStationSlotPlan plan) { plan = default(CookingStationSlotPlan); if (!_ownerPendingPlans.TryGetValue(station, out OwnerPendingPlans value) || !value.BySender.TryGetValue(sender, out var value2)) { return false; } value.BySender.Remove(sender); if (Time.unscaledTime - value2.StartedAt > 5f || !string.Equals(value2.ExpectedInput, itemName, StringComparison.Ordinal)) { return false; } plan = value2.Plan; return true; } private static void DiscardPendingPlan(CookingStation station, long sender) { if (_ownerPendingPlans.TryGetValue(station, out OwnerPendingPlans value)) { value.BySender.Remove(sender); } } private static bool WritePlan(CookingStation station, int slot, CookingStationSlotPlan plan) { ZDO zdo = GetZdo(station); if (zdo == null || slot < 0 || string.IsNullOrEmpty(plan.ExpectedInput) || string.IsNullOrEmpty(plan.ExpectedOutput)) { return false; } try { zdo.Set(GetPlanKey(slot, "input"), plan.ExpectedInput); zdo.Set(GetPlanKey(slot, "output"), plan.ExpectedOutput); zdo.Set(GetPlanKey(slot, "auto"), plan.AutoPop ? 1 : 0); zdo.Set(GetPlanKey(slot, "prepaid"), plan.CollectionExperiencePrepaid ? 1 : 0); zdo.Set(GetPlanKey(slot, "bonus"), CookingStationAutoPopCore.ClampBonusCount(plan.BonusCount)); zdo.Set(GetPlanKey(slot, "version"), 1); int num; if (zdo.GetInt(GetPlanKey(slot, "version"), 0) == 1 && string.Equals(zdo.GetString(GetPlanKey(slot, "input"), ""), plan.ExpectedInput, StringComparison.Ordinal)) { num = (string.Equals(zdo.GetString(GetPlanKey(slot, "output"), ""), plan.ExpectedOutput, StringComparison.Ordinal) ? 1 : 0); if (num != 0) { goto IL_012c; } } else { num = 0; } ClearPlan(station, slot); goto IL_012c; IL_012c: return (byte)num != 0; } catch (Exception exception) { LogRequestFailure(exception); ClearPlan(station, slot); return false; } } private static void PreservePrepaidFallbackOrClear(CookingStation station, int slot, CookingStationSlotPlan plan) { if (plan.CollectionExperiencePrepaid) { WritePlan(station, slot, plan.DisableAutoPop()); } else { ClearPlan(station, slot); } } private static void TryAutoPopSlot(CookingStation station, int slot, CookingStationSlotPlan plan) { //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) if (!IsAutoPopPlanEligible(station, plan)) { WritePlan(station, slot, plan.DisableAutoPop()); return; } ZNetView nView = GetNView(station); ZDO val = (((Object)(object)nView != (Object)null && nView.IsValid()) ? nView.GetZDO() : null); GameObject val2 = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(plan.ExpectedOutput) : null); if ((Object)(object)nView == (Object)null || val == null || !nView.IsOwner() || (Object)(object)val2 == (Object)null || (Object)(object)val2.GetComponent() == (Object)null || station.m_slots == null || slot < 0 || slot >= station.m_slots.Length || (Object)(object)station.m_slots[slot] == (Object)null || SpawnItemMethod == null) { WritePlan(station, slot, plan.DisableAutoPop()); LogAutoPopFailure("the cooked prefab or required CookingStation spawn data is unavailable"); return; } int num = 1 + CookingStationAutoPopCore.ClampBonusCount(plan.BonusCount); int i = 0; Vector3 val3 = ((Component)station).transform.position + ((Component)station).transform.forward * 2f; try { for (; i < num; i++) { SpawnItemMethod.Invoke(station, new object[3] { plan.ExpectedOutput, slot, val3 }); } } catch (Exception ex) { if (i > 0) { ForceClearSlot(slot, nView, val); ClearPlan(station, slot); } else { WritePlan(station, slot, plan.DisableAutoPop()); } LogAutoPopFailure(ex.ToString()); return; } ForceClearSlot(slot, nView, val); ClearPlan(station, slot); BroadcastBonusEffect(station, plan.BonusCount); } private static void BroadcastBonusEffect(CookingStation station, int bonusCount) { bonusCount = CookingStationAutoPopCore.ClampBonusCount(bonusCount); if (bonusCount <= 0) { return; } try { ZNetView nView = GetNView(station); if (!((Object)(object)nView == (Object)null) && nView.IsValid() && nView.IsOwner()) { nView.InvokeRPC(ZNetView.Everybody, "FineDining_CookingStation_AutoPopBonusEffect", new object[1] { bonusCount }); } } catch (Exception exception) { LogBonusEffectFailure(exception); } } private static void ReceiveBonusEffect(CookingStation station, long sender, int bonusCount) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) ZNetView nView = GetNView(station); ZDO val = (((Object)(object)nView != (Object)null && nView.IsValid()) ? nView.GetZDO() : null); bonusCount = CookingStationAutoPopCore.ClampBonusCount(bonusCount); if (val != null && sender == val.GetOwner() && bonusCount > 0) { Vector3 position = ((Component)station).transform.position; if ((Object)(object)DamageText.instance != (Object)null) { DamageText.instance.ShowText((TextType)7, position + Vector3.up, $"+{bonusCount}", true); } if ((Object)(object)InventoryGui.instance != (Object)null) { InventoryGui.instance.m_craftBonusEffect.Create(position, Quaternion.identity, (Transform)null, 1f, -1); } } } private static void ForceClearSlot(int slot, ZNetView nview, ZDO zdo) { zdo.Set("slot" + slot, string.Empty); zdo.Set("slot" + slot, 0f); zdo.Set("slotstatus" + slot, 0); try { nview.InvokeRPC(ZNetView.Everybody, "RPC_SetSlotVisual", new object[2] { slot, string.Empty }); } catch (Exception ex) { LogAutoPopFailure(ex.ToString()); } } private static bool IsDoneItem(CookingStation station, string itemName) { if ((Object)(object)station.m_overCookedItem != (Object)null && itemName == ((Object)((Component)station.m_overCookedItem).gameObject).name) { return true; } if (station.m_conversion == null) { return false; } foreach (ItemConversion item in station.m_conversion) { if ((Object)(object)item?.m_to != (Object)null && itemName == ((Object)((Component)item.m_to).gameObject).name) { return true; } } return false; } private static ItemConversion? FindInputConversion(CookingStation station, string itemName) { if (station.m_conversion == null) { return null; } foreach (ItemConversion item in station.m_conversion) { if ((Object)(object)item?.m_from != (Object)null && ((Object)((Component)item.m_from).gameObject).name == itemName) { return item; } } return null; } internal static bool CanAutoPopConversion(CookingStation? station, ItemConversion? conversion) { if ((Object)(object)station == (Object)null || (Object)(object)conversion?.m_from == (Object)null || (Object)(object)conversion.m_to == (Object)null || (Object)(object)station.m_overCookedItem == (Object)null) { return false; } return CookingStationAutoPopCore.HasDistinctOvercookStage(conversion.m_cookTime, ((Object)((Component)conversion.m_to).gameObject).name, ((Object)((Component)station.m_overCookedItem).gameObject).name); } internal static bool IsAutoPopPlanEligible(CookingStation station, CookingStationSlotPlan plan) { ItemConversion val = FindInputConversion(station, plan.ExpectedInput); if (CanAutoPopConversion(station, val)) { return string.Equals(((Object)((Component)val.m_to).gameObject).name, plan.ExpectedOutput, StringComparison.Ordinal); } return false; } private static int FindFreeSlot(CookingStation station, ZDO? zdo) { if (zdo == null || station.m_slots == null) { return -1; } for (int i = 0; i < station.m_slots.Length; i++) { if (string.IsNullOrEmpty(zdo.GetString("slot" + i, ""))) { return i; } } return -1; } private static void InvokeVanillaAdd(ZNetView? nview, string vanillaRpcName, object[]? vanillaParameters) { if ((Object)(object)nview != (Object)null && vanillaParameters != null) { nview.InvokeRPC(vanillaRpcName, vanillaParameters); } } private static ZNetView? GetNView(CookingStation station) { if (!((Object)(object)station == (Object)null)) { return ((Component)station).GetComponent(); } return null; } private static ZDO? GetZdo(CookingStation station) { ZNetView nView = GetNView(station); if (!((Object)(object)nView != (Object)null) || !nView.IsValid()) { return null; } return nView.GetZDO(); } private static void LogRequestFailure(Exception exception) { if (!_requestWarningLogged) { _requestWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)$"CookingStation slot planning failed; vanilla insertion remains active ({exception})."); } } private static void LogAutoPopFailure(string detail) { if (!_autoPopWarningLogged) { _autoPopWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)("CookingStation auto-pop could not finish; the slot remains manually collectible where possible (" + detail + ").")); } } private static void LogBonusEffectFailure(Exception exception) { if (!_bonusEffectWarningLogged) { _bonusEffectWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)$"CookingStation auto-eject bonus effects could not be broadcast; item output remains complete ({exception})."); } } } [HarmonyPatch(typeof(CookingStation), "Awake")] internal static class CookingStationAutoPopRpcPatch { [HarmonyPostfix] private static void Postfix(CookingStation __instance) { CookingStationAutoPopSystem.RegisterRpcs(__instance); } } [HarmonyPatch(typeof(CookingStation), "CookItem")] [HarmonyPriority(0)] internal static class CookingStationPlannedAddPatch { private static readonly MethodInfo InvokeRpcMethod = AccessTools.Method(typeof(ZNetView), "InvokeRPC", new Type[2] { typeof(string), typeof(object[]) }, (Type[])null); private static readonly MethodInfo RequestAddMethod = AccessTools.Method(typeof(CookingStationAutoPopSystem), "RequestAdd", (Type[])null, (Type[])null); private static bool _patternWarningLogged; [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown List list = new List(instructions); try { int num = -1; int num2 = -1; for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Ldstr && object.Equals(list[i].operand, "RPC_AddItem")) { num = i; break; } } if (num >= 0) { for (int j = num + 1; j < Math.Min(list.Count, num + 12); j++) { if (list[j].opcode == OpCodes.Callvirt && object.Equals(list[j].operand, InvokeRpcMethod)) { num2 = j; break; } } } if (num < 0 || num2 < 0) { LogPatternFailure(); return list; } CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_1, (object)null); val.labels.AddRange(list[num2].labels); list[num2].labels.Clear(); val.blocks.AddRange(list[num2].blocks); list[num2].blocks.Clear(); list.InsertRange(num2, (IEnumerable)(object)new CodeInstruction[2] { val, new CodeInstruction(OpCodes.Ldarg_0, (object)null) }); list[num2 + 2].opcode = OpCodes.Call; list[num2 + 2].operand = RequestAddMethod; CookingStationAutoPopSystem.MarkRequestPatchReady(); } catch (Exception exception) { LogPatternFailure(exception); } return list; } private static void LogPatternFailure(Exception? exception = null) { if (!_patternWarningLogged) { _patternWarningLogged = true; string text = ((exception == null) ? string.Empty : $" ({exception})"); FineDiningPlugin.Log.LogWarning((object)("CookingStation planned-add patch was not applied; vanilla insertion remains active" + text + ".")); } } } [HarmonyPatch(typeof(CookingStation), "RPC_AddItem")] [HarmonyPriority(0)] internal static class CookingStationSlotPlanPatch { [HarmonyPrefix] private static void Prefix(CookingStation __instance, long sender, string itemName) { CookingStationAutoPopSystem.PrepareAdd(__instance, sender, itemName); } } [HarmonyPatch(typeof(CookingStation), "OnInteract")] [HarmonyPriority(800)] internal static class CookingStationPlannedExperiencePatch { private static readonly MethodInfo RaiseSkillMethod = AccessTools.Method(typeof(Character), "RaiseSkill", new Type[2] { typeof(SkillType), typeof(float) }, (Type[])null); private static readonly MethodInfo RaiseAddExperienceMethod = AccessTools.Method(typeof(CookingStationAutoPopSystem), "RaiseAddExperienceOrDefer", (Type[])null, (Type[])null); private static readonly MethodInfo RaiseCollectionExperienceMethod = AccessTools.Method(typeof(CookingStationAutoPopSystem), "RaiseCollectionExperience", (Type[])null, (Type[])null); private static bool _patternWarningLogged; [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown List list = new List(instructions); try { int num = FindRaiseSkillAmount(list, 0.4f); int num2 = FindRaiseSkillAmount(list, 0.6f); if (num < 0 || num2 < 0) { LogPatternFailure(); return list; } list[num + 1].opcode = OpCodes.Call; list[num + 1].operand = RaiseAddExperienceMethod; int num3 = num2 + 1; CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(list[num3].labels); list[num3].labels.Clear(); val.blocks.AddRange(list[num3].blocks); list[num3].blocks.Clear(); list.Insert(num3, val); list[num3 + 1].opcode = OpCodes.Call; list[num3 + 1].operand = RaiseCollectionExperienceMethod; CookingStationAutoPopSystem.MarkExperiencePatchReady(); } catch (Exception exception) { LogPatternFailure(exception); } return list; } private static int FindRaiseSkillAmount(List codes, float amount) { for (int i = 0; i < codes.Count - 1; i++) { if (codes[i].opcode == OpCodes.Ldc_R4 && codes[i].operand is float num && Math.Abs(num - amount) < 0.0001f && (codes[i + 1].opcode == OpCodes.Call || codes[i + 1].opcode == OpCodes.Callvirt) && object.Equals(codes[i + 1].operand, RaiseSkillMethod)) { return i; } } return -1; } private static void LogPatternFailure(Exception? exception = null) { if (!_patternWarningLogged) { _patternWarningLogged = true; string text = ((exception == null) ? string.Empty : $" ({exception})"); FineDiningPlugin.Log.LogWarning((object)("CookingStation planned experience patch was not applied; vanilla experience remains active" + text + ".")); } } } [HarmonyPatch(typeof(CookingStation), "UpdateCooking")] [HarmonyPriority(0)] internal static class CookingStationAutoPopCompletionPatch { [HarmonyPostfix] private static void Postfix(CookingStation __instance) { CookingStationAutoPopSystem.ProcessCompletedSlots(__instance); } } internal static class CookingStationBonusSystem { private static bool _outputLookupWarningLogged; internal static float ApplyConfiguredChance(float calculatedChance, CookingStation station, Humanoid user) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown if (CookingStationAutoPopSystem.TryGetFirstDonePlan(station, out var _, out var plan, out var outputMatches)) { if (!outputMatches || plan.BonusCount <= 0) { return 0f; } return 2f; } if (IsFirstDoneOutputExcluded(station)) { return 0f; } Player val = (Player)(object)((user is Player) ? user : null); if (val == null || !((Object)val != (Object)null)) { return calculatedChance; } return CookingProductionBonusSystem.CalculateConfiguredCookingChance(((Character)val).GetSkillFactor((SkillType)105)); } internal static int GetPlannedBonusAmount(InventoryGui gui, CookingStation station) { if (CookingStationAutoPopSystem.TryGetFirstDonePlan(station, out var _, out var plan, out var outputMatches)) { if (!outputMatches) { return 0; } return plan.BonusCount; } return gui.m_craftBonusAmount; } internal static bool IsFirstDoneOutputExcluded(CookingStation station) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown try { if (!CookingStationAutoPopSystem.TryGetFirstDoneSlot(station, out int _, out string itemName)) { return false; } if ((Object)station.m_overCookedItem != (Object)null && itemName == ((Object)((Component)station.m_overCookedItem).gameObject).name) { return true; } return !string.IsNullOrWhiteSpace(DietConfig.GetCookingBonusExcludedOutputPrefabs()) && CookingProductionBonusSystem.IsExcludedOutputPrefab(itemName); } catch (Exception ex) { if (!_outputLookupWarningLogged) { _outputLookupWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)("Could not resolve a CookingStation output for the exclusion list; the configured chance is still applied (" + ex.Message + ").")); } return false; } } } [HarmonyPatch(typeof(CookingStation), "OnInteract")] [HarmonyPriority(0)] internal static class CookingStationBonusChancePatch { private static readonly MethodInfo RandomValueGetter = AccessTools.PropertyGetter(typeof(Random), "value"); private static readonly FieldInfo CraftBonusChanceField = AccessTools.Field(typeof(InventoryGui), "m_craftBonusChance"); private static readonly FieldInfo CraftBonusAmountField = AccessTools.Field(typeof(InventoryGui), "m_craftBonusAmount"); private static readonly MethodInfo ApplyConfiguredChanceMethod = AccessTools.Method(typeof(CookingStationBonusSystem), "ApplyConfiguredChance", (Type[])null, (Type[])null); private static readonly MethodInfo GetPlannedBonusAmountMethod = AccessTools.Method(typeof(CookingStationBonusSystem), "GetPlannedBonusAmount", (Type[])null, (Type[])null); private static bool _patternWarningLogged; [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Expected O, but got Unknown //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Expected O, but got Unknown List list = new List(instructions); try { int num = FindCall(list, RandomValueGetter, 0); int num2 = FindFieldLoad(list, CraftBonusChanceField, num + 1); int num3 = num2 + 1; int num4 = num3 + 1; if (num < 0 || num2 <= num || num4 >= list.Count || list[num3].opcode != OpCodes.Mul || !IsBranchGreaterOrEqualUnsigned(list[num4].opcode)) { LogPatternFailure(); return list; } CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(list[num4].labels); list[num4].labels.Clear(); val.blocks.AddRange(list[num4].blocks); list[num4].blocks.Clear(); list.InsertRange(num4, (IEnumerable)(object)new CodeInstruction[3] { val, new CodeInstruction(OpCodes.Ldarg_1, (object)null), new CodeInstruction(OpCodes.Call, (object)ApplyConfiguredChanceMethod) }); ReplaceBonusAmountLoads(list); } catch (Exception exception) { LogPatternFailure(exception); } return list; } private static int FindCall(List codes, MethodInfo method, int startIndex) { for (int i = Math.Max(0, startIndex); i < codes.Count; i++) { if ((codes[i].opcode == OpCodes.Call || codes[i].opcode == OpCodes.Callvirt) && object.Equals(codes[i].operand, method)) { return i; } } return -1; } private static void ReplaceBonusAmountLoads(List codes) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown for (int i = 0; i < codes.Count; i++) { if (!(codes[i].opcode != OpCodes.Ldfld) && object.Equals(codes[i].operand, CraftBonusAmountField)) { CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(codes[i].labels); codes[i].labels.Clear(); val.blocks.AddRange(codes[i].blocks); codes[i].blocks.Clear(); codes.Insert(i, val); codes[i + 1].opcode = OpCodes.Call; codes[i + 1].operand = GetPlannedBonusAmountMethod; i++; } } } private static int FindFieldLoad(List codes, FieldInfo field, int startIndex) { for (int i = Math.Max(0, startIndex); i < codes.Count; i++) { if (codes[i].opcode == OpCodes.Ldfld && object.Equals(codes[i].operand, field)) { return i; } } return -1; } private static bool IsBranchGreaterOrEqualUnsigned(OpCode opcode) { if (!(opcode == OpCodes.Bge_Un)) { return opcode == OpCodes.Bge_Un_S; } return true; } private static void LogPatternFailure(Exception? exception = null) { if (!_patternWarningLogged) { _patternWarningLogged = true; string text = ((exception == null) ? string.Empty : $" ({exception})"); FineDiningPlugin.Log.LogWarning((object)("CookingStation bonus settings were not applied; vanilla behavior remains active" + text + ".")); } } } [HarmonyPatch(typeof(CookingStation), "RPC_RemoveDoneItem")] [HarmonyPriority(0)] internal static class CookingStationExcludedOutputGuardPatch { [HarmonyPrefix] private static void Prefix(CookingStation __instance, ref int amount, out int __state) { __state = -1; if (CookingStationAutoPopSystem.TryGetFirstDonePlan(__instance, out var slot, out var plan, out var outputMatches)) { amount = ((!outputMatches) ? 1 : (1 + plan.BonusCount)); __state = slot; } else if (amount > 1 && CookingStationBonusSystem.IsFirstDoneOutputExcluded(__instance)) { amount = 1; } } [HarmonyPostfix] private static void Postfix(CookingStation __instance, int __state, bool __runOriginal) { if (__runOriginal && __state >= 0 && CookingStationAutoPopSystem.IsSlotEmpty(__instance, __state)) { CookingStationAutoPopSystem.ClearPlan(__instance, __state); } } } internal static class FermenterCookingBonusSystem { internal sealed class OwnerTapContext { internal long Sender { get; } internal int RequestId { get; } internal long BatchTicks { get; } internal string OutputPrefabName { get; } internal int BaseItemCount { get; } internal int BonusItemCount { get; } internal OwnerTapContext(long sender, int requestId, long batchTicks, string outputPrefabName, int baseItemCount, int bonusItemCount) { Sender = sender; RequestId = requestId; BatchTicks = batchTicks; OutputPrefabName = outputPrefabName; BaseItemCount = baseItemCount; BonusItemCount = bonusItemCount; } } private sealed class ClientTapRequest { internal int RequestId { get; } internal long PlayerId { get; } internal long Owner { get; } internal long BatchTicks { get; } internal float StartedAt { get; } internal ClientTapRequest(int requestId, long playerId, long owner, long batchTicks, float startedAt) { RequestId = requestId; PlayerId = playerId; Owner = owner; BatchTicks = batchTicks; StartedAt = startedAt; } } private const string RequestTapRpc = "FineDining_Fermenter_RequestTap"; private const string TapCompletedRpc = "FineDining_Fermenter_TapCompleted"; internal const float CookingExperienceOnAdd = 0.4f; private const float CookingExperienceOnCollect = 0.6f; private const float RequestTimeoutSeconds = 10f; private const float InteractionDistanceTolerance = 2f; private const BindingFlags InstanceMemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly MethodInfo? GetStatusMethod = typeof(Fermenter).GetMethod("GetStatus", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); private static readonly MethodInfo? RpcTapMethod = typeof(Fermenter).GetMethod("RPC_Tap", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(long) }, null); private static readonly FieldInfo? DelayedTapItemField = typeof(Fermenter).GetField("m_delayedTapItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static ConditionalWeakTable ClientRequests = new ConditionalWeakTable(); private static ConditionalWeakTable OwnerTapContexts = new ConditionalWeakTable(); private static int _nextRequestId = 1; private static bool _contractWarningLogged; private static bool _requestWarningLogged; private static bool _completionWarningLogged; internal static void ResetRuntime() { ClientRequests = new ConditionalWeakTable(); OwnerTapContexts = new ConditionalWeakTable(); _nextRequestId = 1; _contractWarningLogged = false; _requestWarningLogged = false; _completionWarningLogged = false; } internal static void RegisterRpcs(Fermenter fermenter) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown if ((Object)fermenter == (Object)null || !HasRuntimeContract()) { return; } ZNetView nView = GetNView(fermenter); if (!((Object)nView == (Object)null) && nView.IsValid()) { nView.Unregister("FineDining_Fermenter_RequestTap"); nView.Unregister("FineDining_Fermenter_TapCompleted"); nView.Register("FineDining_Fermenter_RequestTap", (Method)delegate(long sender, int requestId, long playerId, long batchTicks, int skillPermille) { HandleTapRequest(fermenter, sender, requestId, playerId, batchTicks, skillPermille); }); nView.Register("FineDining_Fermenter_TapCompleted", (Method)delegate(long sender, int requestId, long batchTicks, int bonusCount, bool completed) { HandleTapCompleted(fermenter, sender, requestId, batchTicks, bonusCount, completed); }); } } internal static void RequestTap(ZNetView nview, string vanillaRpcName, object[] vanillaParameters, Humanoid user, Fermenter fermenter) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown if ((Object)nview == (Object)null) { return; } if (!((Object)fermenter == (Object)null)) { Player val = (Player)(object)((user is Player) ? user : null); if (val != null && !((Object)val == (Object)null) && HasRuntimeContract()) { if (StationModule.IsFermenterBonusExcluded(fermenter)) { ClientRequests.Remove(fermenter); nview.InvokeRPC(vanillaRpcName, vanillaParameters); return; } ZDO zDO = nview.GetZDO(); if (zDO == null || !IsReady(fermenter)) { nview.InvokeRPC(vanillaRpcName, vanillaParameters); return; } if (ClientRequests.TryGetValue(fermenter, out ClientTapRequest value)) { if (Time.unscaledTime - value.StartedAt < 10f) { return; } ClientRequests.Remove(fermenter); } long owner = zDO.GetOwner(); long batchToken = FermenterEnvironmentSpeedSystem.GetBatchToken(fermenter); if (owner == 0L || batchToken == 0L) { nview.InvokeRPC(vanillaRpcName, vanillaParameters); return; } float skillFactor = ((Character)val).GetSkillFactor((SkillType)105); skillFactor = ((!float.IsNaN(skillFactor) && !(skillFactor <= 0f)) ? Mathf.Min(1f, skillFactor) : 0f); int num = NextRequestId(); int num2 = Mathf.RoundToInt(skillFactor * 1000f); ClientRequests.Add(fermenter, new ClientTapRequest(num, val.GetPlayerID(), owner, batchToken, Time.unscaledTime)); try { nview.InvokeRPC(owner, "FineDining_Fermenter_RequestTap", new object[4] { num, val.GetPlayerID(), batchToken, num2 }); return; } catch (Exception exception) { ClientRequests.Remove(fermenter); LogRequestFailure(exception); nview.InvokeRPC(vanillaRpcName, vanillaParameters); return; } } } nview.InvokeRPC(vanillaRpcName, vanillaParameters); } internal static OwnerTapContext? TakeOwnerTapContext(Fermenter fermenter) { if (!OwnerTapContexts.TryGetValue(fermenter, out OwnerTapContext value)) { return null; } OwnerTapContexts.Remove(fermenter); if (StationModule.IsFermenterBonusExcluded(fermenter)) { RejectTapRequest(fermenter, value.Sender, value.RequestId, value.BatchTicks); return null; } return value; } internal static void CompleteDelayedTap(Fermenter fermenter, OwnerTapContext? context) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) if (context == null) { return; } if (StationModule.IsFermenterBonusExcluded(fermenter)) { RejectTapRequest(fermenter, context.Sender, context.RequestId, context.BatchTicks); return; } int num = 0; try { ItemConversion itemConversion = GetItemConversion(fermenter, GetDelayedTapItem(fermenter)); if (itemConversion == null || (Object)itemConversion.m_to == (Object)null || itemConversion.m_producedItems <= 0) { SendTapResponse(fermenter, context.Sender, context.RequestId, context.BatchTicks, num, completed: false); return; } if (itemConversion.m_producedItems != context.BaseItemCount || ((Object)((Component)itemConversion.m_to).gameObject).name != context.OutputPrefabName) { SendTapResponse(fermenter, context.Sender, context.RequestId, context.BatchTicks, num, completed: true); return; } int num2 = Math.Min(Math.Max(0, context.BonusItemCount), context.BaseItemCount); Vector3 val = (((Object)fermenter.m_outputPoint != (Object)null) ? (fermenter.m_outputPoint.position + Vector3.up * 0.3f) : (((Component)fermenter).transform.position + Vector3.up)); for (int i = 0; i < num2; i++) { ItemDrop.OnCreateNew(Object.Instantiate(itemConversion.m_to, val, Quaternion.identity)); num++; } } catch (Exception exception) { LogCompletionFailure(exception); } SendTapResponse(fermenter, context.Sender, context.RequestId, context.BatchTicks, num, completed: true); } private static void SendTapResponse(Fermenter fermenter, long recipient, int requestId, long batchTicks, int spawnedBonusItems, bool completed) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown try { ZNetView nView = GetNView(fermenter); if ((Object)nView != (Object)null && nView.IsValid() && recipient != 0L) { nView.InvokeRPC(recipient, "FineDining_Fermenter_TapCompleted", new object[4] { requestId, batchTicks, Math.Max(0, spawnedBonusItems), completed }); } } catch (Exception exception) { LogCompletionFailure(exception); } } private static void HandleTapRequest(Fermenter fermenter, long sender, int requestId, long playerId, long batchTicks, int skillPermille) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown if (sender == 0L || requestId <= 0 || (Object)fermenter == (Object)null) { return; } ZNetView nView = GetNView(fermenter); if (skillPermille < 0 || skillPermille > 1000 || (Object)nView == (Object)null || !nView.IsValid() || !nView.IsOwner() || !HasRuntimeContract() || !IsReady(fermenter) || OwnerTapContexts.TryGetValue(fermenter, out OwnerTapContext _)) { RejectTapRequest(fermenter, sender, requestId, batchTicks); return; } if (nView.GetZDO() == null || batchTicks == 0L || FermenterEnvironmentSpeedSystem.GetBatchToken(fermenter) != batchTicks) { RejectTapRequest(fermenter, sender, requestId, batchTicks); return; } Player val = FindRequestingPlayer(sender, playerId); if (val == null || (Object)(object)val == (Object)null || ((Character)val).IsDead() || ((Character)val).IsTeleporting() || !IsWithinInteractionDistance(val, fermenter)) { RejectTapRequest(fermenter, sender, requestId, batchTicks); return; } if (StationModule.IsFermenterBonusExcluded(fermenter)) { HandleExcludedTapRequest(fermenter, sender, requestId, batchTicks); return; } string content = GetContent(nView); ItemConversion itemConversion = GetItemConversion(fermenter, content); if (string.IsNullOrEmpty(content) || itemConversion == null || (Object)itemConversion.m_to == (Object)null || itemConversion.m_producedItems <= 0) { RejectTapRequest(fermenter, sender, requestId, batchTicks); return; } int producedItems = itemConversion.m_producedItems; string name = ((Object)((Component)itemConversion.m_to).gameObject).name; int bonusItemCount = 0; if (itemConversion.m_to.m_itemData.m_shared.m_maxStackSize > 1) { bonusItemCount = CookingProductionBonusSystem.RollConfiguredBonusItems(name, producedItems, (float)skillPermille / 1000f, DietConfig.GetFermenterOutputBonusChanceAtMaxCookingPercent()); } OwnerTapContexts.Add(fermenter, new OwnerTapContext(sender, requestId, batchTicks, name, producedItems, bonusItemCount)); bool delayedTapWasAlreadyScheduled = HasScheduledDelayedTap(fermenter); try { RpcTapMethod.Invoke(fermenter, new object[1] { sender }); if (!string.IsNullOrEmpty(GetContent(nView)) && !TryRecoverScheduledTap(fermenter, batchTicks, delayedTapWasAlreadyScheduled)) { RejectOwnerTap(fermenter, sender, requestId, batchTicks); } } catch (Exception exception) { LogRequestFailure(exception); if (!TryRecoverScheduledTap(fermenter, batchTicks, delayedTapWasAlreadyScheduled)) { RejectOwnerTap(fermenter, sender, requestId, batchTicks); } } } private static void HandleExcludedTapRequest(Fermenter fermenter, long sender, int requestId, long batchTicks) { if (HasScheduledDelayedTap(fermenter)) { RejectTapRequest(fermenter, sender, requestId, batchTicks); return; } try { RpcTapMethod.Invoke(fermenter, new object[1] { sender }); } catch (Exception exception) { LogRequestFailure(exception); } RejectTapRequest(fermenter, sender, requestId, batchTicks); } private static void RejectOwnerTap(Fermenter fermenter, long recipient, int requestId, long batchTicks) { OwnerTapContexts.Remove(fermenter); RejectTapRequest(fermenter, recipient, requestId, batchTicks); } private static void RejectTapRequest(Fermenter fermenter, long recipient, int requestId, long batchTicks) { SendTapResponse(fermenter, recipient, requestId, batchTicks, 0, completed: false); } private static bool HasScheduledDelayedTap(Fermenter fermenter) { try { return ((MonoBehaviour)fermenter).IsInvoking("DelayedTap"); } catch { return false; } } private static bool TryRecoverScheduledTap(Fermenter fermenter, long batchTicks, bool delayedTapWasAlreadyScheduled) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (delayedTapWasAlreadyScheduled || !HasScheduledDelayedTap(fermenter)) { return false; } try { ZNetView nView = GetNView(fermenter); if ((Object)nView == (Object)null) { return false; } ZDO zDO = nView.GetZDO(); if (zDO == null) { return false; } long batchToken = FermenterEnvironmentSpeedSystem.GetBatchToken(fermenter); if (batchToken == 0L) { if (!string.IsNullOrEmpty(GetContent(nView))) { zDO.Set(ZDOVars.s_content, string.Empty); } FermenterEnvironmentSpeedSystem.NotifyBatchCleared(fermenter); return true; } if (batchToken != batchTicks) { return false; } zDO.Set(ZDOVars.s_content, string.Empty); FermenterEnvironmentSpeedSystem.NotifyBatchCleared(fermenter); return true; } catch (Exception exception) { LogRequestFailure(exception); return false; } } internal static void RejectDelayedTap(Fermenter fermenter, OwnerTapContext? context) { if (context != null) { RejectTapRequest(fermenter, context.Sender, context.RequestId, context.BatchTicks); } } private static void HandleTapCompleted(Fermenter fermenter, long sender, int requestId, long batchTicks, int bonusItemCount, bool completed) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) if (!ClientRequests.TryGetValue(fermenter, out ClientTapRequest value) || value.RequestId != requestId || value.BatchTicks != batchTicks || value.Owner != sender) { return; } ClientRequests.Remove(fermenter); if (!completed || StationModule.IsFermenterBonusExcluded(fermenter)) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)localPlayer == (Object)null || localPlayer.GetPlayerID() != value.PlayerId) { return; } ((Character)localPlayer).RaiseSkill((SkillType)105, 0.6f); if (bonusItemCount > 0) { Vector3 val = (((Object)fermenter.m_outputPoint != (Object)null) ? (fermenter.m_outputPoint.position + Vector3.up) : (((Component)fermenter).transform.position + Vector3.up)); if ((Object)DamageText.instance != (Object)null) { DamageText.instance.ShowText((TextType)7, val, $"+{bonusItemCount}", true); } if ((Object)InventoryGui.instance != (Object)null) { InventoryGui.instance.m_craftBonusEffect.Create(val, Quaternion.identity, (Transform)null, 1f, -1); } } } private static Player? FindRequestingPlayer(long sender, long playerId) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)allPlayer != (Object)null && allPlayer.GetPlayerID() == playerId && ((Character)allPlayer).GetOwner() == sender) { return allPlayer; } } return null; } private static bool IsWithinInteractionDistance(Player player, Fermenter fermenter) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //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) float num = Mathf.Max(1f, player.m_maxInteractDistance + 2f); Vector3 val = ((Component)player).transform.position - ((Component)fermenter).transform.position; return ((Vector3)(ref val)).sqrMagnitude <= num * num; } private static ZNetView GetNView(Fermenter fermenter) { return ((Component)fermenter).GetComponent(); } private static string GetContent(ZNetView nview) { ZDO zDO = nview.GetZDO(); if (zDO != null) { return zDO.GetString(ZDOVars.s_content, ""); } return string.Empty; } private static bool IsReady(Fermenter fermenter) { try { return string.Equals((GetStatusMethod?.Invoke(fermenter, null))?.ToString(), "Ready", StringComparison.Ordinal); } catch (Exception exception) { LogRequestFailure(exception); return false; } } private static string GetDelayedTapItem(Fermenter fermenter) { try { return (DelayedTapItemField?.GetValue(fermenter) as string) ?? string.Empty; } catch (Exception exception) { LogCompletionFailure(exception); return string.Empty; } } private static ItemConversion? GetItemConversion(Fermenter fermenter, string itemName) { if (fermenter.m_conversion == null) { return null; } foreach (ItemConversion item in fermenter.m_conversion) { if ((Object)(object)item?.m_from != (Object)null && ((Object)((Component)item.m_from).gameObject).name == itemName) { return item; } } return null; } private static bool HasRuntimeContract() { if (GetStatusMethod != null && RpcTapMethod != null && DelayedTapItemField != null) { return true; } if (!_contractWarningLogged) { _contractWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)"Fermenter Cooking bonus compatibility is unavailable because the expected private game members were not found; vanilla tapping remains active."); } return false; } private static int NextRequestId() { if (_nextRequestId <= 0 || _nextRequestId == int.MaxValue) { _nextRequestId = 1; } return _nextRequestId++; } private static void LogRequestFailure(Exception exception) { if (!_requestWarningLogged) { _requestWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)$"Fermenter Cooking bonus request failed; vanilla output remains available ({exception})."); } } private static void LogCompletionFailure(Exception exception) { if (!_completionWarningLogged) { _completionWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)$"Fermenter Cooking bonus completion failed ({exception})."); } } } [HarmonyPatch(typeof(Fermenter), "AddItem")] internal static class FermenterCookingExperienceAddItemPatch { [HarmonyPostfix] private static void Postfix(Fermenter __instance, Humanoid user, bool __result) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown if (__result && !StationModule.IsFermenterBonusExcluded(__instance)) { Player val = (Player)(object)((user is Player) ? user : null); if (val != null && !((Object)val == (Object)null) && !((Object)Player.m_localPlayer == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { ((Character)val).RaiseSkill((SkillType)105, 0.4f); } } } } [HarmonyPatch(typeof(Fermenter), "Awake")] internal static class FermenterCookingBonusRpcPatch { [HarmonyPostfix] private static void Postfix(Fermenter __instance) { FermenterCookingBonusSystem.RegisterRpcs(__instance); } } [HarmonyPatch(typeof(Fermenter), "Interact")] [HarmonyPriority(0)] internal static class FermenterCookingBonusInteractPatch { private static readonly MethodInfo InvokeRpcMethod = AccessTools.Method(typeof(ZNetView), "InvokeRPC", new Type[2] { typeof(string), typeof(object[]) }, (Type[])null); private static readonly MethodInfo RequestTapMethod = AccessTools.Method(typeof(FermenterCookingBonusSystem), "RequestTap", (Type[])null, (Type[])null); private static bool _patternWarningLogged; [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown List list = new List(instructions); try { int num = -1; int num2 = -1; for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Ldstr && object.Equals(list[i].operand, "RPC_Tap")) { num = i; break; } } if (num >= 0) { for (int j = num + 1; j < Math.Min(list.Count, num + 10); j++) { if (list[j].opcode == OpCodes.Callvirt && object.Equals(list[j].operand, InvokeRpcMethod)) { num2 = j; break; } } } if (num < 0 || num2 < 0) { LogPatternFailure(); return list; } CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_1, (object)null); val.labels.AddRange(list[num2].labels); list[num2].labels.Clear(); val.blocks.AddRange(list[num2].blocks); list[num2].blocks.Clear(); list.InsertRange(num2, (IEnumerable)(object)new CodeInstruction[2] { val, new CodeInstruction(OpCodes.Ldarg_0, (object)null) }); list[num2 + 2].opcode = OpCodes.Call; list[num2 + 2].operand = RequestTapMethod; } catch (Exception exception) { LogPatternFailure(exception); } return list; } private static void LogPatternFailure(Exception? exception = null) { if (!_patternWarningLogged) { _patternWarningLogged = true; string text = ((exception == null) ? string.Empty : $" ({exception})"); FineDiningPlugin.Log.LogWarning((object)("Fermenter Cooking bonus request patch was not applied; vanilla tapping remains active" + text + ".")); } } } [HarmonyPatch(typeof(Fermenter), "DelayedTap")] internal static class FermenterCookingBonusOutputPatch { [HarmonyPrefix] private static void Prefix(Fermenter __instance, out FermenterCookingBonusSystem.OwnerTapContext? __state) { __state = FermenterCookingBonusSystem.TakeOwnerTapContext(__instance); } [HarmonyPostfix] private static void Postfix(Fermenter __instance, FermenterCookingBonusSystem.OwnerTapContext? __state, bool __runOriginal) { if (__runOriginal) { FermenterCookingBonusSystem.CompleteDelayedTap(__instance, __state); } else { FermenterCookingBonusSystem.RejectDelayedTap(__instance, __state); } } } [HarmonyPatch(typeof(Hud), "UpdateFood")] internal static class DietHudPatches { [HarmonyPostfix] private static void Postfix(Hud __instance, Player player) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { HudFoodSlots.EnsureFoodSlots(__instance); HudFoodSlots.LimitVisibleSlots(__instance, player); HudFoodSlots.UpdateTooltips(__instance, player); HudFoodPanels.Update(__instance, player); } } } [HarmonyPatch(typeof(Player))] internal static class DietPlayerFoodPatches { private sealed class VanillaEatBoundaryState { private readonly FoodSnapshot[] _foods; private readonly Food? _matchingFood; internal VanillaEatBoundaryState(Player player, ItemData incomingItem) { List foods = player.GetFoods(); _foods = new FoodSnapshot[foods.Count]; string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(incomingItem); for (int i = 0; i < foods.Count; i++) { _foods[i] = new FoodSnapshot(foods[i]); if (_matchingFood == null && FoodIdentity.GetCanonicalPrefabName(foods[i]) == canonicalPrefabName) { _matchingFood = foods[i]; } } } internal bool TryGetReplacedOrRemovedFood(Player player, out Food? protectedFood) { protectedFood = null; List foods = player.GetFoods(); if (foods.Count > _foods.Length) { return false; } if (_matchingFood != null && foods.Contains(_matchingFood)) { protectedFood = _matchingFood; return true; } foreach (Food item in foods) { int num = -1; for (int i = 0; i < _foods.Length; i++) { if (_foods[i].References(item)) { num = i; break; } } if (num < 0 || !_foods[num].Matches(item)) { protectedFood = item; return true; } } return foods.Count < _foods.Length; } } private readonly struct FoodSnapshot { private readonly Food _food; private readonly ItemData _item; private readonly string _key; internal FoodSnapshot(Food food) { _food = food; _item = food.m_item; _key = FoodIdentity.GetCanonicalPrefabName(food); } internal bool References(Food food) { return _food == food; } internal bool Matches(Food food) { if (References(food) && _item == food.m_item) { return _key == FoodIdentity.GetCanonicalPrefabName(food); } return false; } } [HarmonyPatch("CanEat")] [HarmonyPrefix] private static bool CanEatPrefix(Player __instance, ItemData item, bool showMessages, ref bool __result) { if (!FoodIdentity.IsDirectlyEdible(item)) { return true; } __result = PlayerFoodLogic.CanEat(__instance, item, showMessages); return false; } [HarmonyPatch("EatFood")] [HarmonyPrefix] private static bool EatFoodPrefix(Player __instance, ItemData item, ref bool __result, out VanillaEatBoundaryState? __state) { __state = null; if (!FoodIdentity.IsDirectlyEdible(item)) { __state = new VanillaEatBoundaryState(__instance, item); return true; } __result = PlayerFoodLogic.EatFood(__instance, item); return false; } [HarmonyPatch("EatFood")] [HarmonyPostfix] private static void EatFoodPostfix(Player __instance, ItemData item, bool __result, VanillaEatBoundaryState? __state) { if (!__result) { return; } if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && FoodIdentity.IsDirectlyEdible(item)) { float cookingExperiencePerFoodEaten = DietConfig.GetCookingExperiencePerFoodEaten(); if (cookingExperiencePerFoodEaten > 0f) { ((Character)__instance).RaiseSkill((SkillType)105, cookingExperiencePerFoodEaten); } } if (__state != null) { PlayerFoodStateData state = FoodStateStore.GetState(__instance); RecordVanillaFoodConsumption(__instance, item, state); Food protectedFood; bool num = __state.TryGetReplacedOrRemovedFood(__instance, out protectedFood); if (num) { FoodSlotProgression.ApplyPendingAfterFoodRemoval(__instance, state, trimExcess: false); FoodSlotProgression.TrimExcessFoods(__instance, state, protectedFood); } FoodStateStore.SaveState(__instance, state); if (num) { PlayerFoodLogic.RefreshFoodStats(__instance); } } } private static void RecordVanillaFoodConsumption(Player player, ItemData item, PlayerFoodStateData state) { string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(item); if (string.IsNullOrWhiteSpace(canonicalPrefabName)) { return; } foreach (Food food in player.GetFoods()) { if (FoodIdentity.GetCanonicalPrefabName(food) == canonicalPrefabName) { FoodRules.SetActiveFoodScale(state, canonicalPrefabName, 1f); break; } } } [HarmonyPatch("UpdateFood")] [HarmonyPrefix] private static bool UpdateFoodPrefix(Player __instance, float dt, bool forceUpdate) { PlayerFoodLogic.UpdateFood(__instance, dt, forceUpdate); return false; } [HarmonyPatch("RemoveOneFood")] [HarmonyPrefix] [HarmonyPriority(800)] private static bool RemoveOneFoodPrefix(Player __instance, ref bool __result) { if (!PukeFoodRemovalRuntime.TryRemoveOrderedFood(__instance, out var removed)) { return true; } __result = removed; return false; } [HarmonyPatch("RemoveOneFood")] [HarmonyPostfix] private static void RemoveOneFoodPostfix(Player __instance, bool __result) { if (__result) { PlayerFoodStateData state = FoodStateStore.GetState(__instance); FoodSlotProgression.ApplyPendingAfterFoodRemoval(__instance, state); FoodStateStore.SaveState(__instance, state); PlayerFoodLogic.RefreshFoodStats(__instance); } } [HarmonyPatch("ClearFood")] [HarmonyPostfix] private static void ClearFoodPostfix(Player __instance) { PlayerFoodStateData state = FoodStateStore.GetState(__instance); FoodStateStore.SaveState(__instance, state); FoodSlotProgression.ApplyPendingAfterFoodRemoval(__instance, state); FoodStateStore.SaveState(__instance, state); PlayerFoodLogic.RefreshFoodStats(__instance); } [HarmonyPatch("Load")] [HarmonyPostfix] private static void LoadPostfix(Player __instance) { FoodSlotProgression.Invalidate(__instance); FoodStateStore.Invalidate(__instance); DietModule.RequestDietReconcile(); PlayerFoodStateData state = FoodStateStore.GetState(__instance); ChefCollectionService.EnsureChefCollection(__instance, state); FoodStateStore.SaveState(__instance, state); PlayerFoodLogic.RefreshFoodStats(__instance); } [HarmonyPatch("ResetCharacterKnownItems")] [HarmonyPostfix] private static void ResetCharacterKnownItemsPostfix(Player __instance) { FoodSlotProgression.Invalidate(__instance); DietModule.RequestDietReconcile(); } [HarmonyPatch("OnDeath")] [HarmonyPostfix] private static void OnDeathPostfix(Player __instance) { FoodStateStore.SaveState(__instance); } [HarmonyPatch("OnDestroy")] [HarmonyPrefix] private static void OnDestroyPrefix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { HudFoodPanels.ResetAll(); } FoodStateStore.Invalidate(__instance); FoodSlotProgression.Invalidate(__instance); } } internal static class PukeFoodRemovalRuntime { [ThreadStatic] private static int _pukeUpdateDepth; internal static void EnterPukeUpdate() { if (_pukeUpdateDepth < int.MaxValue) { _pukeUpdateDepth++; } } internal static void ExitPukeUpdate() { if (_pukeUpdateDepth > 0) { _pukeUpdateDepth--; } } internal static bool TryRemoveOrderedFood(Player? player, out bool removed) { removed = false; if (_pukeUpdateDepth <= 0 || (Object)(object)player == (Object)null) { return false; } PukeFoodRemovalOrder pukeFoodRemovalOrder = DietConfig.GetPukeFoodRemovalOrder(); if (pukeFoodRemovalOrder == PukeFoodRemovalOrder.Random) { return false; } List foods = player.GetFoods(); PlayerFoodStateData state = FoodStateStore.GetState(player); int num = SelectRemovalIndex(foods, pukeFoodRemovalOrder, state.Active); if (num >= 0) { foods.RemoveAt(num); removed = true; } return true; } internal static int SelectRemovalIndex(List? foods, PukeFoodRemovalOrder order, IReadOnlyList? consumptionOrder = null) { if (foods == null || foods.Count == 0 || order == PukeFoodRemovalOrder.Random) { return -1; } bool flag = order == PukeFoodRemovalOrder.NewestFirst; int num = -1; float value = 0f; int selectedRank = -1; for (int i = 0; i < foods.Count; i++) { if (TryGetElapsedSinceEaten(foods[i], out var elapsed)) { int num2 = elapsed.CompareTo(value); int consumptionRank = GetConsumptionRank(foods[i], consumptionOrder); bool flag2 = (flag ? (num2 < 0) : (num2 > 0)); bool flag3 = num2 == 0 && IsPreferredTie(i, consumptionRank, num, selectedRank, flag); if (num < 0 || flag2 || flag3) { num = i; value = elapsed; selectedRank = consumptionRank; } } } int num3 = SelectByConsumptionOrder(foods, consumptionOrder, flag); if (num < 0) { if (num3 < 0) { if (!flag) { return 0; } return foods.Count - 1; } return num3; } return num; } private static bool IsPreferredTie(int candidateIndex, int candidateRank, int selectedIndex, int selectedRank, bool newestFirst) { if (candidateRank >= 0 && selectedRank >= 0 && candidateRank != selectedRank) { if (!newestFirst) { return candidateRank < selectedRank; } return candidateRank > selectedRank; } if (!newestFirst) { return candidateIndex < selectedIndex; } return candidateIndex > selectedIndex; } private static int SelectByConsumptionOrder(IReadOnlyList foods, IReadOnlyList? consumptionOrder, bool newestFirst) { int num = -1; int num2 = -1; for (int i = 0; i < foods.Count; i++) { int consumptionRank = GetConsumptionRank(foods[i], consumptionOrder); if (consumptionRank >= 0 && (num < 0 || !(newestFirst ? (consumptionRank <= num2) : (consumptionRank >= num2)))) { num = i; num2 = consumptionRank; } } return num; } private static int GetConsumptionRank(Food? food, IReadOnlyList? consumptionOrder) { if (food == null || consumptionOrder == null) { return -1; } string text = food.m_name?.Trim() ?? string.Empty; for (int i = 0; i < consumptionOrder.Count; i++) { if (consumptionOrder[i]?.Key == text) { return i; } } string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(food); if (canonicalPrefabName == text) { return -1; } for (int j = 0; j < consumptionOrder.Count; j++) { if (consumptionOrder[j]?.Key == canonicalPrefabName) { return j; } } return -1; } internal static bool TryGetElapsedSinceEaten(Food? food, out float elapsed) { elapsed = 0f; SharedData val = food?.m_item?.m_shared; if (val == null) { return false; } float foodBurnTime = val.m_foodBurnTime; float time = food.m_time; if (float.IsNaN(foodBurnTime) || float.IsInfinity(foodBurnTime) || foodBurnTime <= 0f || float.IsNaN(time) || float.IsInfinity(time)) { return false; } elapsed = Math.Max(0f, foodBurnTime - time); if (!float.IsNaN(elapsed)) { return !float.IsInfinity(elapsed); } return false; } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int) })] internal static class DietTooltipPatch { private static readonly MethodInfo MemberwiseCloneMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(typeof(object).FullName, "MemberwiseClone"); private static bool _cloneFailureLogged; [HarmonyPrefix] private static void Prefix(ref ItemData __0, out FoodEffect? __state) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown __state = null; Player localPlayer = Player.m_localPlayer; ItemData val = __0; if ((Object)(object)localPlayer == (Object)null || !FoodIdentity.IsDirectlyEdible(val)) { return; } try { PlayerFoodStateData state = FoodStateStore.GetState(localPlayer); FoodEffect value = FoodRules.PreviewNextFoodEffect(localPlayer, state, val); if (!(value.EffectiveScale < 0f) && !float.IsNaN(value.EffectiveScale) && !float.IsInfinity(value.EffectiveScale)) { ItemData val2 = val.Clone(); val2.m_shared = (SharedData)MemberwiseCloneMethod.Invoke(val.m_shared, null); val2.m_shared.m_food = RoundTooltipStat(value.Health); val2.m_shared.m_foodStamina = RoundTooltipStat(value.Stamina); val2.m_shared.m_foodEitr = RoundTooltipStat(value.Eitr); val2.m_shared.m_foodRegen = RoundTooltipStat(value.Regen); __0 = val2; __state = value; } } catch (Exception ex) { if (!_cloneFailureLogged) { _cloneFailureLogged = true; FineDiningPlugin.Log.LogWarning((object)("Failed to build transformed diet tooltip: " + ex.Message)); } } } [HarmonyPostfix] private static void Postfix(FoodEffect? __state, ref string __result) { if (__state.HasValue) { FoodEffect value = __state.Value; StringBuilder stringBuilder = new StringBuilder(); if (value.IsChef) { AppendModifierLine(stringBuilder, FoodEffectUiText.BuildChefChoiceModifierLine(value.ChefMultiplier)); } if (FoodEffectUiText.TryBuildDiminishingReturnsLine(value.DiminishingScale, out string line)) { AppendModifierLine(stringBuilder, line); } if (FoodEffectUiText.TryBuildStalenessLine(value.FreshnessScale, out string line2) && !FoodEffectUiText.ContainsLine(__result ?? string.Empty, line2)) { AppendModifierLine(stringBuilder, line2); } if (stringBuilder.Length > 0) { __result = __result + "\n\n" + stringBuilder; } } } private static void AppendModifierLine(StringBuilder builder, string line) { if (builder.Length > 0) { builder.Append('\n'); } builder.Append(line); } private static float RoundTooltipStat(float value) { if (float.IsNaN(value) || float.IsInfinity(value)) { return value; } float num = (float)Math.Round(value, 1, MidpointRounding.AwayFromZero); if (!(value > 0f) || num != 0f) { return num; } return 0.1f; } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int) })] internal static class DietPukeTooltipPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(ItemData __0, ref string __result) { if (__0?.m_shared?.m_consumeStatusEffect is SE_Puke && Localization.instance != null) { string text = Localization.instance.Localize("$finedining_diet_tooltip_puke_chef_refresh"); if (!string.IsNullOrWhiteSpace(text) && !FoodEffectUiText.ContainsLine(__result ?? string.Empty, text)) { __result = __result + "\n\n" + text; } } } } [HarmonyPatch(typeof(Feast), "RPC_EatConfirmation")] internal static class PlacedFeastFreshnessConsumptionPatch { internal sealed class State { internal ItemDrop? FoodDrop; internal ItemData? OriginalItem; } [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(Feast __instance, out State? __state) { __state = null; ItemDrop val = __instance?.m_foodItem; ItemDrop val2 = (((Object)(object)__instance != (Object)null) ? ((Component)__instance).GetComponent() : null); if (val?.m_itemData == null || val2?.m_itemData == null) { return; } try { val2.Load(); if (!DecayRuntime.IsCreatorlessPlacedDrop(val2)) { ItemData val3 = val.m_itemData.Clone(); FreshnessRuntime.CopyFreshnessMetadata(val2.m_itemData, val3); __state = new State { FoodDrop = val, OriginalItem = val.m_itemData }; val.m_itemData = val3; } } catch (Exception ex) { FineDiningPlugin.Log.LogWarning((object)("Could not bridge placed-feast freshness into its food item: " + ex.Message)); } } [HarmonyFinalizer] private static Exception? Finalizer(State? __state, Exception? __exception) { if ((Object)(object)__state?.FoodDrop != (Object)null && __state.OriginalItem != null) { __state.FoodDrop.m_itemData = __state.OriginalItem; } return __exception; } } [HarmonyPatch(typeof(SE_Puke), "UpdateStatusEffect")] internal static class DietPukeChefRotationPatch { private readonly struct PukeUpdateState { internal Player? Player { get; } internal int FoodCount { get; } internal PukeUpdateState(Player player, int foodCount) { Player = player; FoodCount = foodCount; } } [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(SE_Puke __instance, out PukeUpdateState __state) { PukeFoodRemovalRuntime.EnterPukeUpdate(); Character character = ((StatusEffect)__instance).m_character; Player val = (Player)(object)((character is Player) ? character : null); __state = (((Object)(object)val != (Object)null && (Object)(object)val == (Object)(object)Player.m_localPlayer) ? new PukeUpdateState(val, val.GetFoods().Count) : default(PukeUpdateState)); } [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(PukeUpdateState __state) { Player player = __state.Player; if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { int num = CountRemovedFoods(__state.FoodCount, player.GetFoods().Count); if (num > 0) { ChefCollectionService.RotateOldest(player, num); } } } [HarmonyFinalizer] [HarmonyPriority(0)] private static Exception? Finalizer(Exception? __exception) { PukeFoodRemovalRuntime.ExitPukeUpdate(); return __exception; } internal static int CountRemovedFoods(int before, int after) { if (before <= after) { return 0; } return before - Math.Max(0, after); } } internal static class CookingSkillTooltipText { internal const string HeadingToken = "$finedining_skill_cooking_heading"; internal const string AutoEjectToken = "$finedining_skill_cooking_auto_eject"; internal const string BonusOutputToken = "$finedining_skill_cooking_bonus_output"; internal const string ChefTierToken = "$finedining_skill_cooking_chef_tier"; internal const string ChefMultiplierToken = "$finedining_skill_cooking_chef_multiplier"; internal const string ChefBothToken = "$finedining_skill_cooking_chef_both"; internal static string Append(string? original, bool bonusOutputEnabled, bool chefTierEnabled, bool chefMultiplierEnabled) { if (original == null) { original = string.Empty; } if (original.IndexOf("$finedining_skill_cooking_heading", StringComparison.Ordinal) >= 0) { return original; } StringBuilder stringBuilder = new StringBuilder("$finedining_skill_cooking_heading"); stringBuilder.Append('\n').Append("$finedining_skill_cooking_auto_eject"); if (bonusOutputEnabled) { stringBuilder.Append('\n').Append("$finedining_skill_cooking_bonus_output"); } string text = ((chefTierEnabled && chefMultiplierEnabled) ? "$finedining_skill_cooking_chef_both" : (chefTierEnabled ? "$finedining_skill_cooking_chef_tier" : (chefMultiplierEnabled ? "$finedining_skill_cooking_chef_multiplier" : string.Empty))); if (text.Length > 0) { stringBuilder.Append('\n').Append(text); } if (original.Length <= 0) { return stringBuilder.ToString(); } return original + "\n\n" + stringBuilder; } internal static bool MatchesSkillDescription(string? tooltipText, string? skillDescription) { if (!string.IsNullOrWhiteSpace(tooltipText) && !string.IsNullOrWhiteSpace(skillDescription)) { return tooltipText.IndexOf(skillDescription, StringComparison.Ordinal) >= 0; } return false; } internal static bool HasFineDiningHeading(string? tooltipText) { if (!string.IsNullOrEmpty(tooltipText)) { return tooltipText.IndexOf("$finedining_skill_cooking_heading", StringComparison.Ordinal) >= 0; } return false; } } [HarmonyPatch(typeof(SkillsDialog), "Setup")] internal static class CookingSkillTooltipPatch { private static bool _failureLogged; [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "randyknapp.mods.epicloot" })] private static void Postfix(SkillsDialog __instance, Player player) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Invalid comparison between Unknown and I4 //IL_0120: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)player == (Object)null) { return; } try { Skills skills = ((Character)player).GetSkills(); List list = ((skills != null) ? skills.GetSkillList() : null); if (list == null) { return; } Skill val = null; int cookingIndex = -1; for (int i = 0; i < list.Count; i++) { Skill val2 = list[i]; if (val2 != null && (int)(val2.m_info?.m_skill).GetValueOrDefault() == 105) { val = val2; cookingIndex = i; break; } } if (val?.m_info == null) { return; } UITooltip val3 = FindCookingTooltip(__instance, cookingIndex, val.m_info.m_description); if (!((Object)(object)val3 == (Object)null)) { string text = CookingSkillTooltipText.Append(val3.m_text, DietConfig.GetCookingBonusChanceAtMaxCookingPercent() > 0f || DietConfig.GetFermenterOutputBonusChanceAtMaxCookingPercent() > 0f, DietConfig.GetChefHighTierSelectionStrength() > 0f, DietConfig.GetChefMultiplierModeAtMaxCooking() > DietConfig.GetChefMultiplierMin()); if (!string.Equals(text, val3.m_text, StringComparison.Ordinal)) { val3.Set(val3.m_topic, text, val3.m_anchor, val3.m_fixedPosition); } } } catch (Exception ex) { if (!_failureLogged) { _failureLogged = true; FineDiningPlugin.Log.LogWarning((object)("Could not extend the Cooking skill tooltip: " + ex.GetBaseException().Message)); } } } private static UITooltip? FindCookingTooltip(SkillsDialog dialog, int cookingIndex, string cookingDescription) { if (dialog.m_elements != null && cookingIndex >= 0 && cookingIndex < dialog.m_elements.Count) { GameObject obj = dialog.m_elements[cookingIndex]; UITooltip val = ((obj != null) ? obj.GetComponentInChildren() : null); if ((Object)(object)val != (Object)null && CookingSkillTooltipText.MatchesSkillDescription(val.m_text, cookingDescription)) { return val; } } InventoryGui componentInParent = ((Component)dialog).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { return null; } UITooltip[] componentsInChildren = ((Component)componentInParent).GetComponentsInChildren(true); UITooltip[] array = componentsInChildren; foreach (UITooltip val2 in array) { if ((Object)(object)val2 != (Object)null && ((Component)val2).gameObject.activeInHierarchy && CookingSkillTooltipText.MatchesSkillDescription(val2.m_text, cookingDescription)) { return val2; } } array = componentsInChildren; foreach (UITooltip val3 in array) { if ((Object)(object)val3 != (Object)null && CookingSkillTooltipText.MatchesSkillDescription(val3.m_text, cookingDescription)) { return val3; } } return null; } } [HarmonyPatch(typeof(UITooltip), "UpdateTextElements")] internal static class CookingSkillTooltipAlignmentPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(UITooltip __instance) { if ((Object)(object)__instance == (Object)null || !CookingSkillTooltipText.HasFineDiningHeading(__instance.m_text) || ((Object)(object)UITooltip.m_current != (Object)null && (Object)(object)UITooltip.m_current != (Object)(object)__instance) || (Object)(object)UITooltip.m_tooltip == (Object)null) { return; } TMP_Text[] componentsInChildren = UITooltip.m_tooltip.GetComponentsInChildren(true); foreach (TMP_Text val in componentsInChildren) { if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, "Text", StringComparison.Ordinal)) { val.horizontalAlignment = (HorizontalAlignmentOptions)1; break; } } } } internal static class PlayerFoodLogic { internal static bool CanEat(Player player, ItemData item, bool showMessages) { List foods = player.GetFoods(); PlayerFoodStateData state = FoodStateStore.GetState(player); int currentSlots = FoodSlotProgression.GetCurrentSlots(player, state); string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(item); foreach (Food item2 in foods) { if (!(FoodIdentity.GetCanonicalPrefabName(item2) != canonicalPrefabName)) { if (item2.CanEatAgain()) { return true; } if (showMessages) { ((Character)player).Message((MessageType)2, Localization.instance.Localize("$msg_nomore", new string[1] { item.m_shared.m_name }), 0, (Sprite)null); } return false; } } foreach (Food item3 in foods) { if (item3.CanEatAgain()) { return true; } } if (foods.Count >= currentSlots) { if (showMessages) { ((Character)player).Message((MessageType)2, "$msg_isfull", 0, (Sprite)null); } return false; } return true; } internal static bool EatFood(Player player, ItemData item) { if (!CanEat(player, item, showMessages: false)) { return false; } string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(item); PlayerFoodStateData state = FoodStateStore.GetState(player); Food val = FindTargetFood(player, state, item); if (val == null || string.IsNullOrWhiteSpace(canonicalPrefabName)) { return false; } bool flag = player.GetFoods().Contains(val); bool replacesDietFood = flag && FoodIdentity.IsDirectlyEdible(val.m_item); if (flag) { FoodSlotProgression.ApplyPendingAfterFoodRemoval(player, state, trimExcess: false); } float multiplier; bool flag2 = ChefCollectionService.TryConsumeChefEntry(player, state, canonicalPrefabName, out multiplier); int stack = RecentHistoryService.RegisterConsumption(state, canonicalPrefabName, flag2); if (flag2) { ChefCollectionService.RefillAfterConsumption(player, state, canonicalPrefabName); } bool fullCourseActive = FoodRules.WillHaveFullCourseAfterEating(player, state, item, flag, replacesDietFood); FoodEffect effect = FoodRules.CalculateFoodEffect(item, stack, flag2, multiplier, fullCourseActive, FoodSlotProgression.GetCurrentSlots(player, state)); FoodRules.SetActiveFoodScale(state, canonicalPrefabName, effect.AppliedScale); ApplyFoodSnapshot(val, item, canonicalPrefabName, effect.EffectiveScale); List foods = player.GetFoods(); if (!foods.Contains(val)) { foods.Add(val); } FoodSlotProgression.TrimExcessFoods(player, state, val); FoodStateStore.SaveState(player, state); string text = BuildFoodMessage(effect); if (!string.IsNullOrWhiteSpace(text)) { ((Character)player).Message((MessageType)2, text, 0, (Sprite)null); } RecalculateFoodStats(player, state); Game instance = Game.instance; if (instance != null) { instance.IncrementPlayerStat((PlayerStatType)49, 1f); } return true; } internal static void UpdateFood(Player player, float dt, bool forceUpdate) { PlayerFoodStateData playerFoodStateData = null; List foods = player.GetFoods(); ref float reference = ref PlayerPrivateAccess.FoodUpdateTimer.Invoke(player); reference += dt; if (reference >= 1f || forceUpdate) { playerFoodStateData = FoodStateStore.GetState(player); reference -= 1f; bool flag = false; int num = 0; for (int num2 = foods.Count - 1; num2 >= 0; num2--) { Food val = foods[num2]; val.m_time -= 1f; if (!(val.m_time > 0f)) { ((Character)player).Message((MessageType)2, "$msg_food_done", 0, (Sprite)null); foods.RemoveAt(num2); flag = true; if (!forceUpdate && FoodIdentity.IsDirectlyEdible(val.m_item)) { num++; } } } if (flag) { FoodSlotProgression.ApplyPendingAfterFoodRemoval(player, playerFoodStateData); FoodStateStore.SaveState(player, playerFoodStateData); } if (num > 0 && (Object)(object)player == (Object)(object)Player.m_localPlayer) { ChefCollectionService.RotateOldest(player, num); } RecalculateFoodStats(player, playerFoodStateData); } if (forceUpdate) { return; } ref float reference2 = ref PlayerPrivateAccess.FoodRegenTimer.Invoke(player); reference2 += dt; if (reference2 < 10f) { return; } reference2 = 0f; if (playerFoodStateData == null) { playerFoodStateData = FoodStateStore.GetState(player); } float num3 = 0f; float fullCourseScale = FoodRules.GetFullCourseScale(player, playerFoodStateData, FoodRules.CountActiveDietFoods(foods)); foreach (Food item in foods) { float num4 = (FoodIdentity.IsDirectlyEdible(item.m_item) ? (FoodRules.GetAppliedScale(player, playerFoodStateData, item) * fullCourseScale) : 1f); num3 += item.m_item.m_shared.m_foodRegen * num4; } if (!(num3 <= 0f)) { float num5 = 1f; ((Character)player).GetSEMan().ModifyHealthRegen(ref num5); ((Character)player).Heal(num3 * num5, true); } } internal static void RefreshFoodStats(Player? player) { if ((Object)(object)player != (Object)null) { RecalculateFoodStats(player, FoodStateStore.GetState(player)); } } private static void RecalculateFoodStats(Player player, PlayerFoodStateData state) { List foods = player.GetFoods(); float fullCourseScale = FoodRules.GetFullCourseScale(player, state, FoodRules.CountActiveDietFoods(foods)); foreach (Food item in foods) { float num = Mathf.Clamp01(item.m_time / item.m_item.m_shared.m_foodBurnTime); num = Mathf.Pow(num, 0.3f); float num2 = (FoodIdentity.IsDirectlyEdible(item.m_item) ? (FoodRules.GetAppliedScale(player, state, item) * fullCourseScale) : 1f); item.m_health = item.m_item.m_shared.m_food * num2 * num; item.m_stamina = item.m_item.m_shared.m_foodStamina * num2 * num; item.m_eitr = item.m_item.m_shared.m_foodEitr * num2 * num; } GetTotalFoodValue(player, foods, out var health, out var stamina, out var eitr); player.SetMaxHealth(health, true); player.SetMaxStamina(stamina, true); PlayerPrivateAccess.SetMaxEitr(player, eitr, flashBar: true); if (eitr > 0f) { player.ShowTutorial("eitr", false); } } private static Food? FindTargetFood(Player player, PlayerFoodStateData state, ItemData item) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown List foods = player.GetFoods(); string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(item); foreach (Food item2 in foods) { if (FoodIdentity.GetCanonicalPrefabName(item2) == canonicalPrefabName) { return item2.CanEatAgain() ? item2 : null; } } if (foods.Count >= FoodSlotProgression.GetCurrentSlots(player, state)) { return GetMostDepletedFood(foods); } return new Food(); } private static Food? GetMostDepletedFood(List foods) { Food val = null; foreach (Food food in foods) { if (food.CanEatAgain() && (val == null || food.m_time < val.m_time)) { val = food; } } return val; } private static void GetTotalFoodValue(Player player, List foods, out float health, out float stamina, out float eitr) { health = player.GetBaseFoodHP(); stamina = player.m_baseStamina; eitr = 0f; foreach (Food food in foods) { health += food.m_health; stamina += food.m_stamina; eitr += food.m_eitr; } } private static void ApplyFoodSnapshot(Food food, ItemData item, string key, float effectiveScale) { food.m_name = key; food.m_item = item; food.m_time = item.m_shared.m_foodBurnTime; food.m_health = item.m_shared.m_food * effectiveScale; food.m_stamina = item.m_shared.m_foodStamina * effectiveScale; food.m_eitr = item.m_shared.m_foodEitr * effectiveScale; } private static string BuildFoodMessage(FoodEffect effect) { string text = string.Empty; if (effect.Health > 0f) { text = text + " +" + FormatValue(effect.Health) + " $item_food_health "; } if (effect.Stamina > 0f) { text = text + " +" + FormatValue(effect.Stamina) + " $item_food_stamina "; } if (effect.Eitr > 0f) { text = text + " +" + FormatValue(effect.Eitr) + " $item_food_eitr "; } if (effect.FullCourseActive) { text = text + " " + Localization.instance.Localize("$finedining_diet_full_course_message", new string[1] { DietConfig.GetFullCourseMultiplier().ToString("0.00", CultureInfo.InvariantCulture) }); } return text; } private static string FormatValue(float value) { if (!Mathf.Approximately(value, Mathf.Round(value))) { return value.ToString("0.0", CultureInfo.InvariantCulture); } return Mathf.RoundToInt(value).ToString(CultureInfo.InvariantCulture); } } internal static class PlayerPrivateAccess { internal delegate void SetMaxEitrDelegate(Player player, float eitr, bool flashBar); internal static readonly FieldRef FoodUpdateTimer = AccessTools.FieldRefAccess("m_foodUpdateTimer"); internal static readonly FieldRef FoodRegenTimer = AccessTools.FieldRefAccess("m_foodRegenTimer"); internal static readonly FieldRef> KnownRecipes = AccessTools.FieldRefAccess>("m_knownRecipes"); internal static readonly FieldRef> KnownMaterials = AccessTools.FieldRefAccess>("m_knownMaterial"); internal static readonly SetMaxEitrDelegate SetMaxEitr = AccessTools.MethodDelegate(AccessTools.DeclaredMethod(typeof(Player), "SetMaxEitr", new Type[2] { typeof(float), typeof(bool) }, (Type[])null), (object)null, true); } internal static class RecentHistoryService { internal static HistoryEntryData? GetEntry(PlayerFoodStateData state, string key) { foreach (HistoryEntryData item in state.Recent) { if (item.Key == key) { return item; } } return null; } internal static int GetNextStack(PlayerFoodStateData state, string key, bool isChef) { if (isChef) { return 1; } HistoryEntryData entry = GetEntry(state, key); if (entry != null && entry.Stack >= 1) { return entry.Stack + 1; } return 1; } internal static int RegisterConsumption(PlayerFoodStateData state, string key, bool isChef) { for (int i = 0; i < state.Recent.Count; i++) { HistoryEntryData historyEntryData = state.Recent[i]; if (!(historyEntryData.Key != key)) { historyEntryData.Stack = (isChef ? 1 : ((historyEntryData.Stack < 1) ? 1 : (historyEntryData.Stack + 1))); if (i != state.Recent.Count - 1) { state.Recent.RemoveAt(i); state.Recent.Add(historyEntryData); } return historyEntryData.Stack; } } state.Recent.Add(new HistoryEntryData { Key = key, Stack = 1 }); Trim(state); return 1; } internal static void Clear(PlayerFoodStateData state) { state.Recent.Clear(); } private static void Trim(PlayerFoodStateData state) { while (state.Recent.Count > DietConfig.GetRecentHistorySize()) { state.Recent.RemoveAt(0); } } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class DietTerminalCommands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__RerollChef; public static ConsoleEvent <1>__ClearRecent; public static ConsoleEvent <2>__PrintState; } private static bool _registered; [HarmonyPostfix] private static void Postfix() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown if (!_registered) { _registered = true; object obj = <>O.<0>__RerollChef; if (obj == null) { ConsoleEvent val = RerollChef; <>O.<0>__RerollChef = val; obj = (object)val; } new ConsoleCommand("fd:rerollchef", "Admin only. Rerolls the local player's Chef collection.", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, true); object obj2 = <>O.<1>__ClearRecent; if (obj2 == null) { ConsoleEvent val2 = ClearRecent; <>O.<1>__ClearRecent = val2; obj2 = (object)val2; } new ConsoleCommand("fd:clearrecent", "Admin only. Clears the local player's recent food history.", (ConsoleEvent)obj2, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, true); object obj3 = <>O.<2>__PrintState; if (obj3 == null) { ConsoleEvent val3 = PrintState; <>O.<2>__PrintState = val3; obj3 = (object)val3; } new ConsoleCommand("fd:printstate", "Admin only. Prints the local player's FineDining diet state.", (ConsoleEvent)obj3, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, true); } } private static void RerollChef(ConsoleEventArgs args) { if (TryGetLocalPlayer(args, out Player player)) { ChefCollectionService.RerollAll(player); RefreshHud(player); Terminal context = args.Context; if (context != null) { context.AddString("FineDining: Chef collection rerolled."); } } } private static void ClearRecent(ConsoleEventArgs args) { if (TryGetLocalPlayer(args, out Player player)) { PlayerFoodStateData state = FoodStateStore.GetState(player); RecentHistoryService.Clear(state); FoodStateStore.SaveState(player, state); RefreshHud(player); Terminal context = args.Context; if (context != null) { context.AddString("FineDining: Recent food history cleared."); } } } private static void PrintState(ConsoleEventArgs args) { if (TryGetLocalPlayer(args, out Player player)) { PlayerFoodStateData state = FoodStateStore.GetState(player); Terminal context = args.Context; if (context != null) { context.AddString("FineDining diet state:"); } Terminal context2 = args.Context; if (context2 != null) { context2.AddString($" KnownFoods: {FoodSlotProgression.GetKnownFoodCount(player)}"); } Terminal context3 = args.Context; if (context3 != null) { context3.AddString($" FoodSlots: {FoodSlotProgression.GetCurrentSlots(player, state)}/{DietConfig.GetMaxFoodSlots()}"); } Terminal context4 = args.Context; if (context4 != null) { context4.AddString($" Recent ({state.Recent.Count}/{DietConfig.GetRecentHistorySize()}): {FormatRecent(state)}"); } Terminal context5 = args.Context; if (context5 != null) { context5.AddString($" Chef ({state.Chef.Count}/{DietConfig.GetChefCollectionSize()}): {FormatChef(state)}"); } Terminal context6 = args.Context; if (context6 != null) { context6.AddString($" Active ({state.Active.Count}): {FormatActive(state)}"); } Terminal context7 = args.Context; if (context7 != null) { context7.AddString($" FullCourseActive: {FoodRules.IsFullCourseActive(player)}"); } } } private static bool TryGetLocalPlayer(ConsoleEventArgs args, out Player player) { player = Player.m_localPlayer; if ((Object)(object)player != (Object)null) { return true; } Terminal context = args.Context; if (context != null) { context.AddString("FineDining: No local player is available."); } return false; } private static void RefreshHud(Player player) { if ((Object)(object)Hud.instance != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer) { HudFoodPanels.Update(Hud.instance, player); } } private static string FormatRecent(PlayerFoodStateData state) { if (state.Recent.Count == 0) { return "(empty)"; } List list = new List(); foreach (HistoryEntryData item in state.Recent) { list.Add($"{item.Key} x{item.Stack}"); } return string.Join(" | ", list); } private static string FormatChef(PlayerFoodStateData state) { if (state.Chef.Count == 0) { return "(empty)"; } List list = new List(); foreach (ChefEntryData item in state.Chef) { list.Add($"{item.Key} x{item.Multiplier:0.00}"); } return string.Join(" | ", list); } private static string FormatActive(PlayerFoodStateData state) { if (state.Active.Count == 0) { return "(empty)"; } List list = new List(); foreach (ActiveFoodData item in state.Active) { list.Add($"{item.Key} scale={item.AppliedScale:0.###}"); } return string.Join(" | ", list); } } internal static class FineDiningLocalization { private static BaseUnityPlugin? _plugin; private static readonly IDeserializer Deserializer = new DeserializerBuilder().IgnoreFields().Build(); private static readonly List fileExtensions = new List(2) { ".json", ".yml" }; private static BaseUnityPlugin Plugin => _plugin ?? throw new InvalidOperationException("FineDining localization is not initialized."); internal static event Action? OnLocalizationComplete; internal static void Initialize(BaseUnityPlugin plugin) { _plugin = plugin; } internal static void Shutdown() { FineDiningLocalization.OnLocalizationComplete = null; _plugin = null; } internal static void LoadLocalizationLater() { if (Localization.instance != null) { LoadLocalization(Localization.instance, Localization.instance.GetSelectedLanguage()); } } internal static void LoadLocalization(Localization __instance, string language) { Dictionary dictionary = new Dictionary(); foreach (string item in from f in Directory.GetFiles(Paths.PluginPath, Plugin.Info.Metadata.Name + ".*", SearchOption.AllDirectories) where fileExtensions.IndexOf(Path.GetExtension(f)) >= 0 select f) { string[] array = Path.GetFileNameWithoutExtension(item).Split(new char[1] { '.' }); if (array.Length >= 2) { string text = array[1]; if (dictionary.ContainsKey(text)) { Debug.LogWarning((object)("Duplicate key " + text + " found for " + Plugin.Info.Metadata.Name + ". The duplicate file found at " + item + " will be skipped.")); } else { dictionary[text] = item; } } } byte[] array2 = LoadTranslationFromAssembly("English"); if (array2 == null) { throw new Exception("Found no English localizations in mod " + Plugin.Info.Metadata.Name + ". Expected an embedded resource translations/English.json or translations/English.yml."); } Dictionary dictionary2 = Deserializer.Deserialize>(Encoding.UTF8.GetString(array2)); if (dictionary2 == null) { throw new Exception("Localization for mod " + Plugin.Info.Metadata.Name + " failed: Localization file was empty."); } string text2 = null; if (language != "English") { if (dictionary.TryGetValue(language, out var value)) { text2 = File.ReadAllText(value); } else { byte[] array3 = LoadTranslationFromAssembly(language); if (array3 != null) { text2 = Encoding.UTF8.GetString(array3); } } } if (text2 == null && dictionary.TryGetValue("English", out var value2)) { text2 = File.ReadAllText(value2); } if (text2 != null) { foreach (KeyValuePair item2 in Deserializer.Deserialize>(text2) ?? new Dictionary()) { dictionary2[item2.Key] = item2.Value; } } foreach (KeyValuePair item3 in dictionary2) { __instance.AddWord(item3.Key, item3.Value); } FineDiningLocalization.OnLocalizationComplete?.Invoke(); } private static byte[]? LoadTranslationFromAssembly(string language) { foreach (string fileExtension in fileExtensions) { byte[] array = ReadEmbeddedFileBytes("translations." + language + fileExtension); if (array != null) { return array; } } return null; } private static byte[]? ReadEmbeddedFileBytes(string resourceFileName) { using MemoryStream memoryStream = new MemoryStream(); Assembly assembly = typeof(FineDiningLocalization).Assembly; string text = assembly.GetManifestResourceNames().FirstOrDefault((string str) => str.EndsWith(resourceFileName, StringComparison.Ordinal)); if (text != null) { assembly.GetManifestResourceStream(text)?.CopyTo(memoryStream); } return (memoryStream.Length == 0L) ? null : memoryStream.ToArray(); } } [HarmonyPatch(typeof(Localization), "SetupLanguage")] internal static class FineDiningLocalizationLanguagePatch { [HarmonyPostfix] private static void Postfix(Localization __instance, string language) { FineDiningLocalization.LoadLocalization(__instance, language); } } [HarmonyPatch(typeof(FejdStartup), "SetupGui")] internal static class FineDiningLocalizationGuiPatch { [HarmonyPostfix] private static void Postfix() { FineDiningLocalization.LoadLocalizationLater(); } } [HarmonyPatch(typeof(InventoryGrid), "UpdateGui", new Type[] { typeof(Player), typeof(ItemData) })] internal static class InventoryGridSpoilageTimerPatch { private const string OverlayObjectName = "sighsorry.FineDining.TimerOverlay"; private const string PauseIconObjectName = "sighsorry.FineDining.TimerPauseIcon"; private static readonly Color RunningTimerColor = new Color(1f, 0.82f, 0.22f, 1f); private static readonly Color PausedTimerColor = new Color(0.44f, 0.78f, 1f, 1f); private static Sprite? _coldPauseSprite; private static int _updateId; private static bool _loggedUiFailure; [HarmonyPriority(0)] [HarmonyAfter(new string[] { "sighsorry.InventorySlots" })] private static void Postfix(InventoryGrid __instance) { try { Render(__instance); } catch (Exception ex) { if (!_loggedUiFailure) { _loggedUiFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not render spoilage timers: " + ex)); } } } private static void Render(InventoryGrid grid) { if (grid?.m_elements == null) { return; } Inventory inventory = grid.m_inventory; if (inventory == null) { HideAllExistingOverlays(grid); return; } if (!DecayRuntime.TryPrepareVisibleInventoryTimers(inventory, out var nowTicks)) { HideAllExistingOverlays(grid); return; } int width = inventory.GetWidth(); if (width <= 0) { HideAllExistingOverlays(grid); return; } int num = NextUpdateId(); foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem == null) { continue; } int num2 = allItem.m_gridPos.y * width + allItem.m_gridPos.x; if (num2 < 0 || num2 >= grid.m_elements.Count) { continue; } Element val = grid.m_elements[num2]; if (!((Object)(object)val?.m_go == (Object)null) && val.m_used && val.m_pos.x == allItem.m_gridPos.x && val.m_pos.y == allItem.m_gridPos.y && DecayRuntime.TryGetSpoilageClock(allItem, nowTicks, out var remainingTicks, out var paused)) { FineDiningTimerOverlayCache fineDiningTimerOverlayCache = EnsureOverlay(val); float unscaledTime = Time.unscaledTime; bool num3 = fineDiningTimerOverlayCache.LastItem != allItem || fineDiningTimerOverlayCache.LastPaused != paused || unscaledTime >= fineDiningTimerOverlayCache.NextTextRefreshAt || string.IsNullOrEmpty(fineDiningTimerOverlayCache.LastText); fineDiningTimerOverlayCache.LastItem = allItem; fineDiningTimerOverlayCache.LastPaused = paused; fineDiningTimerOverlayCache.LastSeenUpdateId = num; if (num3) { double seconds = (double)remainingTicks / 10000000.0; SetOverlayState(fineDiningTimerOverlayCache, FormatRemainingTime(seconds), paused); fineDiningTimerOverlayCache.NextTextRefreshAt = unscaledTime + 1f; } else { ShowOverlay(fineDiningTimerOverlayCache, paused); } } } HideOverlaysNotSeenInUpdate(grid, num); } private static int NextUpdateId() { _updateId++; if (_updateId == 0) { _updateId++; } return _updateId; } private static FineDiningTimerOverlayCache EnsureOverlay(Element element) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) GameObject go = element.m_go; FineDiningTimerOverlayCache fineDiningTimerOverlayCache = go.GetComponent() ?? go.AddComponent(); if ((Object)(object)fineDiningTimerOverlayCache.TimerText != (Object)null) { return fineDiningTimerOverlayCache; } Transform val = go.transform.Find("sighsorry.FineDining.TimerOverlay"); TMP_Text val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent() : null); if ((Object)(object)val2 == (Object)null) { if ((Object)(object)element.m_amount != (Object)null) { val2 = Object.Instantiate(element.m_amount, go.transform, false); ((Object)((Component)val2).gameObject).name = "sighsorry.FineDining.TimerOverlay"; } else { GameObject val3 = new GameObject("sighsorry.FineDining.TimerOverlay", new Type[2] { typeof(RectTransform), typeof(CanvasRenderer) }) { layer = go.layer }; val3.transform.SetParent(go.transform, false); val2 = (TMP_Text)(object)val3.AddComponent(); val2.font = TMP_Settings.defaultFontAsset; } GameObject gameObject = ((Component)val2).gameObject; gameObject.layer = go.layer; gameObject.SetActive(false); RectTransform val4 = (RectTransform)gameObject.transform; val4.anchorMin = new Vector2(0f, 1f); val4.anchorMax = new Vector2(0f, 1f); val4.pivot = new Vector2(0f, 1f); Transform obj = go.transform.Find("binding"); TMP_Text val5 = ((obj != null) ? ((Component)obj).GetComponent() : null); float num = (((Object)(object)val5 != (Object)null && ((Behaviour)val5).enabled) ? 17f : 3f); val4.anchoredPosition = new Vector2(3f, 0f - num); val4.sizeDelta = new Vector2(42f, 16f); ((Transform)val4).localScale = Vector3.one; ((Transform)val4).localRotation = Quaternion.identity; ((Transform)val4).SetAsLastSibling(); val2.text = ""; ((Behaviour)val2).enabled = true; ((Graphic)val2).color = RunningTimerColor; val2.alignment = (TextAlignmentOptions)257; val2.enableAutoSizing = true; val2.fontSizeMin = 8f; val2.fontSizeMax = 12f; val2.textWrappingMode = (TextWrappingModes)0; val2.overflowMode = (TextOverflowModes)0; val2.richText = false; ((Graphic)val2).raycastTarget = false; val2.margin = Vector4.zero; } fineDiningTimerOverlayCache.TimerText = val2; fineDiningTimerOverlayCache.LastText = val2.text; fineDiningTimerOverlayCache.Visible = ((Component)val2).gameObject.activeSelf; return fineDiningTimerOverlayCache; } private static void SetOverlayState(FineDiningTimerOverlayCache cache, string text, bool paused) { TMP_Text timerText = cache.TimerText; if (!((Object)(object)timerText == (Object)null)) { if (!string.Equals(cache.LastText, text, StringComparison.Ordinal) || !string.Equals(timerText.text, text, StringComparison.Ordinal)) { timerText.text = text; cache.LastText = text; } ShowOverlay(cache, paused); } } private static void ShowOverlay(FineDiningTimerOverlayCache cache, bool paused) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) TMP_Text timerText = cache.TimerText; if (!((Object)(object)timerText == (Object)null)) { ((Graphic)timerText).color = (paused ? PausedTimerColor : RunningTimerColor); if (!cache.Visible || !((Component)timerText).gameObject.activeSelf || !((Behaviour)timerText).enabled) { ((Behaviour)timerText).enabled = true; ((Component)timerText).gameObject.SetActive(true); cache.Visible = true; } if (paused) { ShowPauseIcon(cache); } else { HidePauseIcon(cache); } } } private static void ShowPauseIcon(FineDiningTimerOverlayCache cache) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) TMP_Text timerText = cache.TimerText; if ((Object)(object)timerText == (Object)null) { return; } Image val = EnsurePauseIcon(cache, timerText.transform.parent); Sprite coldPauseSprite = GetColdPauseSprite(); if ((Object)(object)val == (Object)null || (Object)(object)coldPauseSprite == (Object)null) { HidePauseIcon(cache); return; } if ((Object)(object)val.sprite != (Object)(object)coldPauseSprite) { val.sprite = coldPauseSprite; } RectTransform rectTransform = timerText.rectTransform; RectTransform rectTransform2 = ((Graphic)val).rectTransform; if (!string.Equals(cache.PauseIconLayoutText, timerText.text, StringComparison.Ordinal)) { float x = timerText.GetPreferredValues(timerText.text).x; rectTransform2.anchoredPosition = new Vector2(rectTransform.anchoredPosition.x + Mathf.Clamp(x, 7f, 38f) + 1f, rectTransform.anchoredPosition.y - 1f); cache.PauseIconLayoutText = timerText.text; } ((Behaviour)val).enabled = true; if (!((Component)val).gameObject.activeSelf) { ((Component)val).gameObject.SetActive(true); } } private static Image? EnsurePauseIcon(FineDiningTimerOverlayCache cache, Transform? parent) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)cache.PauseIcon != (Object)null) { return cache.PauseIcon; } if ((Object)(object)parent == (Object)null) { return null; } Transform val = parent.Find("sighsorry.FineDining.TimerPauseIcon"); Image val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent() : null); if ((Object)(object)val2 == (Object)null) { GameObject val3 = new GameObject("sighsorry.FineDining.TimerPauseIcon", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }) { layer = ((Component)parent).gameObject.layer }; val3.transform.SetParent(parent, false); val2 = val3.GetComponent(); RectTransform rectTransform = ((Graphic)val2).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(0f, 1f); rectTransform.pivot = new Vector2(0f, 1f); rectTransform.sizeDelta = new Vector2(11f, 11f); ((Transform)rectTransform).localScale = Vector3.one; ((Transform)rectTransform).localRotation = Quaternion.identity; ((Transform)rectTransform).SetAsLastSibling(); val2.preserveAspect = true; ((Graphic)val2).raycastTarget = false; ((Graphic)val2).color = Color.white; val3.SetActive(false); } cache.PauseIcon = val2; return val2; } private static Sprite? GetColdPauseSprite() { if ((Object)(object)_coldPauseSprite != (Object)null) { return _coldPauseSprite; } ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return null; } _coldPauseSprite = instance.GetStatusEffect(SEMan.s_statusEffectFrost)?.m_icon ?? instance.GetStatusEffect(SEMan.s_statusEffectFreezing)?.m_icon; return _coldPauseSprite; } private static void HidePauseIcon(FineDiningTimerOverlayCache cache) { Image pauseIcon = cache.PauseIcon; if ((Object)(object)pauseIcon != (Object)null && ((Component)pauseIcon).gameObject.activeSelf) { ((Component)pauseIcon).gameObject.SetActive(false); } } private static void HideOverlaysNotSeenInUpdate(InventoryGrid grid, int updateId) { foreach (Element element in grid.m_elements) { if (!((Object)(object)element?.m_go == (Object)null)) { FineDiningTimerOverlayCache component = element.m_go.GetComponent(); if ((Object)(object)component != (Object)null && component.LastSeenUpdateId != updateId) { HideOverlay(component); } } } } private static void HideAllExistingOverlays(InventoryGrid grid) { foreach (Element element in grid.m_elements) { if (!((Object)(object)element?.m_go == (Object)null)) { FineDiningTimerOverlayCache component = element.m_go.GetComponent(); if ((Object)(object)component != (Object)null) { HideOverlay(component); } } } } private static void HideOverlay(FineDiningTimerOverlayCache cache) { TMP_Text timerText = cache.TimerText; if ((Object)(object)timerText != (Object)null && (cache.Visible || ((Component)timerText).gameObject.activeSelf)) { ((Component)timerText).gameObject.SetActive(false); } HidePauseIcon(cache); cache.Visible = false; cache.LastItem = null; cache.LastPaused = false; cache.PauseIconLayoutText = ""; cache.NextTextRefreshAt = 0f; } internal static string FormatRemainingTime(double seconds) { if (double.IsNaN(seconds) || double.IsInfinity(seconds) || seconds < 0.0) { return "--"; } if (seconds < 3600.0) { return Math.Max(1L, (long)Math.Ceiling(seconds / 60.0)) + "m"; } double num = Math.Ceiling(seconds / 3600.0); if (!(num >= 9.223372036854776E+18)) { return (long)num + "h"; } return "--"; } } internal sealed class FineDiningTimerOverlayCache : MonoBehaviour { internal TMP_Text? TimerText; internal Image? PauseIcon; internal ItemData? LastItem; internal string LastText = ""; internal float NextTextRefreshAt; internal int LastSeenUpdateId; internal bool Visible; internal bool LastPaused; internal string PauseIconLayoutText = ""; } internal static class FoodEffectUiText { internal const string RunningColorHex = "#FFD138"; internal const string PausedColorHex = "#70C8FF"; internal const string PositiveModifierColorHex = "#9FE870"; internal const string PenaltyModifierColorHex = "#FFB454"; internal const string NeutralModifierColorHex = "#B8B8B8"; private const string RunningLineKey = "$finedining_tooltip_spoils_in"; private const string PausedLineKey = "$finedining_tooltip_paused"; private const string DayUnitKey = "$finedining_duration_day"; private const string HourUnitKey = "$finedining_duration_hour"; private const string MinuteUnitKey = "$finedining_duration_minute"; private const string EnglishRunningLine = "Spoils in {0}"; private const string EnglishPausedLine = "Cold environment paused spoilage · remaining {0} ❄"; private const string ChefChoiceLineKey = "$finedining_diet_tooltip_chef_choice"; private const string DiminishingReturnsLineKey = "$finedining_diet_tooltip_diminishing_returns"; private const string StalenessLineKey = "$finedining_tooltip_staleness"; private const string EnglishChefChoiceLine = "Chef's Choice: food stats x{1}"; private const string EnglishDiminishingReturnsLine = "Diminishing returns: food stats x{1}"; private const string EnglishStalenessLine = "Staleness: food stats x{1}"; internal static string BuildStatusLine(long remainingTicks, bool paused) { string text = FormatDetailedRemaining(remainingTicks); string text2 = FormatLocalized(paused ? "$finedining_tooltip_paused" : "$finedining_tooltip_spoils_in", paused ? "Cold environment paused spoilage · remaining {0} ❄" : "Spoils in {0}", text); string text3 = (paused ? "#70C8FF" : "#FFD138"); return "" + text2 + ""; } internal static string BuildChefChoiceModifierLine(float multiplier) { float num = Math.Max(1f, RoundMultiplierForDisplay(multiplier)); string chefChoiceModifierColor = GetChefChoiceModifierColor(num); return BuildModifierLine("$finedining_diet_tooltip_chef_choice", "Chef's Choice: food stats x{1}", chefChoiceModifierColor, num); } internal static bool TryBuildDiminishingReturnsLine(float multiplier, out string line) { return TryBuildPenaltyModifierLine("$finedining_diet_tooltip_diminishing_returns", "Diminishing returns: food stats x{1}", multiplier, out line); } internal static bool TryBuildStalenessLine(float multiplier, out string line) { return TryBuildPenaltyModifierLine("$finedining_tooltip_staleness", "Staleness: food stats x{1}", multiplier, out line); } internal static float RoundMultiplierForDisplay(float multiplier) { if (float.IsNaN(multiplier) || float.IsInfinity(multiplier)) { return 1f; } return (float)Math.Round(Math.Max(0f, multiplier), 2, MidpointRounding.AwayFromZero); } internal static string GetChefChoiceModifierColor(float multiplier) { if (!(RoundMultiplierForDisplay(multiplier) > 1f)) { return "#B8B8B8"; } return "#9FE870"; } internal static bool TryGetPenaltyDisplayMultiplier(float multiplier, out float displayedMultiplier) { displayedMultiplier = RoundMultiplierForDisplay(multiplier); return displayedMultiplier < 1f; } private static bool TryBuildPenaltyModifierLine(string key, string englishFallback, float multiplier, out string line) { if (!TryGetPenaltyDisplayMultiplier(multiplier, out var displayedMultiplier)) { line = string.Empty; return false; } line = BuildModifierLine(key, englishFallback, "#FFB454", displayedMultiplier); return true; } private static string BuildModifierLine(string key, string englishFallback, string color, float displayedMultiplier) { string text = displayedMultiplier.ToString("0.00", CultureInfo.InvariantCulture); return FormatLocalized(key, englishFallback, color, text); } internal static string FormatDetailedRemaining(long remainingTicks) { double num = Math.Ceiling((double)Math.Max(0L, remainingTicks) / 600000000.0); long num2 = ((num >= 9.223372036854776E+18) ? long.MaxValue : Math.Max(1L, (long)num)); long num3 = num2 / 1440; long num4 = num2 % 1440; long num5 = num4 / 60; long num6 = num4 % 60; List list = new List(3); if (num3 > 0) { list.Add(FormatLocalized("$finedining_duration_day", "{0}d", num3)); } if (num5 > 0) { list.Add(FormatLocalized("$finedining_duration_hour", "{0}h", num5)); } if (num6 > 0 || list.Count == 0) { list.Add(FormatLocalized("$finedining_duration_minute", "{0}m", (num6 > 0) ? num6 : 1)); } return string.Join(" ", list); } private static string FormatLocalized(string key, string englishFallback, params object[] values) { string text = null; Localization instance = Localization.instance; if (instance != null) { text = instance.Localize(key); } if (!string.IsNullOrWhiteSpace(text)) { try { string text2 = string.Format(CultureInfo.InvariantCulture, text, values); bool flag = true; for (int i = 0; i < values.Length; i++) { string text3 = Convert.ToString(values[i], CultureInfo.InvariantCulture) ?? ""; if (text3.Length > 0 && text2.IndexOf(text3, StringComparison.Ordinal) < 0) { flag = false; break; } } if (flag) { return text2; } } catch (FormatException) { } } return string.Format(CultureInfo.InvariantCulture, englishFallback, values); } internal static bool ContainsLine(string text, string line) { if (string.Equals(text, line, StringComparison.Ordinal)) { return true; } string text2 = "\n" + line; int num = text.IndexOf(text2, StringComparison.Ordinal); if (num >= 0) { if (num + text2.Length != text.Length) { return text[num + text2.Length] == '\n'; } return true; } return false; } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(int) })] internal static class ItemDataSpoilageTooltipPatch { private static bool _loggedFailure; [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(ItemData __instance, ref string __result) { if (__instance == null || __instance.m_stack <= 0) { return; } try { if (DecayRuntime.TryGetWorldTicks(out var ticks) && DecayRuntime.TryGetSpoilageClock(__instance, ticks, out var remainingTicks, out var paused)) { string text = FoodEffectUiText.BuildStatusLine(remainingTicks, paused); if (!FoodEffectUiText.ContainsLine(__result ?? "", text)) { __result = (string.IsNullOrEmpty(__result) ? text : (__result + "\n" + text)); } } } catch (Exception ex) { if (!_loggedFailure) { _loggedFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not append spoilage state to an item tooltip: " + ex)); } } } } internal static class WorldItemSpoilageHover { private static bool _loggedFailure; internal static void Append(GameObject? host, bool refreshItemFromZdo, ref string hoverText) { if ((Object)(object)host == (Object)null || string.IsNullOrEmpty(hoverText)) { return; } try { ItemDrop component = host.GetComponent(); if ((Object)(object)component == (Object)null) { return; } if (refreshItemFromZdo) { component.Load(); } if (DecayRuntime.TryGetWorldTicks(out var ticks) && TryBuildTimerLine(component, ticks, out string timerLine)) { if (!FoodEffectUiText.ContainsLine(hoverText, timerLine)) { hoverText = hoverText + "\n" + timerLine; } ItemData val = component.m_itemData; Feast component2 = host.GetComponent(); if (component2?.m_foodItem?.m_itemData != null) { val = component2.m_foodItem.m_itemData.Clone(); FreshnessRuntime.CopyFreshnessMetadata(component.m_itemData, val); } if (FoodEffectUiText.TryBuildStalenessLine(FreshnessRuntime.GetFoodStatMultiplier(val), out string line) && !FoodEffectUiText.ContainsLine(hoverText, line)) { hoverText = hoverText + "\n" + line; } } } catch (Exception ex) { if (!_loggedFailure) { _loggedFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not render a world-item spoilage timer: " + ex)); } } } internal static bool TryBuildTimerLine(ItemDrop? worldDrop, long nowTicks, out string timerLine) { timerLine = string.Empty; if (DecayRuntime.IsCreatorlessPlacedDrop(worldDrop) || !DecayRuntime.TryGetSpoilageClock(worldDrop?.m_itemData, nowTicks, out var remainingTicks, out var paused)) { return false; } timerLine = FoodEffectUiText.BuildStatusLine(remainingTicks, paused); return true; } } [HarmonyPatch(typeof(Feast), "GetHoverText")] internal static class FeastSpoilageHoverPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Feast __instance, ref string __result) { WorldItemSpoilageHover.Append((__instance != null) ? ((Component)__instance).gameObject : null, refreshItemFromZdo: true, ref __result); } } [HarmonyPatch(typeof(ItemDrop), "GetHoverText")] internal static class ItemDropWorldItemSpoilageHoverPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(ItemDrop __instance, ref string __result) { WorldItemSpoilageHover.Append((__instance != null) ? ((Component)__instance).gameObject : null, refreshItemFromZdo: false, ref __result); } } internal static class FoodClassifier { private static readonly StringComparer PrefabComparer = StringComparer.OrdinalIgnoreCase; private static readonly HashSet FarmingHarvestPrefabs = new HashSet(PrefabComparer); private static readonly HashSet CookingStationInputPrefabs = new HashSet(PrefabComparer); private static readonly HashSet CookingStationOutputPrefabs = new HashSet(PrefabComparer); private static readonly HashSet FermentedFoodPrefabs = new HashSet(PrefabComparer); private static readonly HashSet UnfermentedFoodPrefabs = new HashSet(PrefabComparer); private static readonly HashSet FeastMaterialPrefabs = new HashSet(PrefabComparer); private static readonly HashSet FeastResultPrefabs = new HashSet(PrefabComparer); private static readonly HashSet FishPrefabs = new HashSet(PrefabComparer); private static bool _cacheReady; private static int _cachedObjectDbId = -1; private static int _cachedZNetSceneId = -1; private static int _cachedItemCount = -1; private static int _cachedNamedPrefabCount = -1; private static int _cachedNetPrefabCount = -1; private static int _cachedNonNetPrefabCount = -1; internal static bool IsReady => EnsureCache(); internal static void Invalidate() { _cacheReady = false; _cachedObjectDbId = -1; _cachedZNetSceneId = -1; _cachedItemCount = -1; _cachedNamedPrefabCount = -1; _cachedNetPrefabCount = -1; _cachedNonNetPrefabCount = -1; ClearClassificationSets(); SpoilageReferenceGenerator.Invalidate(); } internal static bool TryClassify(ItemData? item, out SpoilageGroup group) { group = SpoilageGroup.OtherEdible; if (item?.m_shared == null || !EnsureCache()) { return false; } string prefabName = GetPrefabName(item); if (string.IsNullOrWhiteSpace(prefabName)) { return false; } return TrySelectGroup(FarmingHarvestPrefabs.Contains(prefabName), CookingStationInputPrefabs.Contains(prefabName), CookingStationOutputPrefabs.Contains(prefabName), FermentedFoodPrefabs.Contains(prefabName), UnfermentedFoodPrefabs.Contains(prefabName), FeastMaterialPrefabs.Contains(prefabName), FeastResultPrefabs.Contains(prefabName), FishPrefabs.Contains(prefabName), IsEdible(item), out group); } internal static bool TrySelectGroup(bool farmingHarvest, bool cookingStationInput, bool cookingStationOutput, bool fermentedFood, bool unfermentedFood, bool feastMaterial, bool feastResult, bool fish, bool edible, out SpoilageGroup group) { group = SpoilageGroup.OtherEdible; if (farmingHarvest) { group = SpoilageGroup.FarmingHarvest; } else if (feastMaterial) { group = SpoilageGroup.FeastMaterial; } else if (feastResult) { group = SpoilageGroup.FeastResult; } else if (fermentedFood) { group = SpoilageGroup.FermentedFood; } else if (cookingStationOutput || (edible && cookingStationInput)) { group = SpoilageGroup.CookingStationOutput; } else if (cookingStationInput) { group = SpoilageGroup.CookingStationInput; } else if (fish) { group = SpoilageGroup.Fish; } else if (unfermentedFood) { group = SpoilageGroup.UnfermentedFood; } else if (!edible) { return false; } return true; } internal static bool IsEdible(ItemData? item) { return FoodIdentity.IsDirectlyEdible(item); } private static bool HasDirectFoodStats(SharedData shared) { if (!(shared.m_food > 0f) && !(shared.m_foodStamina > 0f)) { return shared.m_foodEitr > 0f; } return true; } private static bool LooksLikeFeastRoutingFood(SharedData shared) { if (!HasDirectFoodStats(shared)) { return shared.m_isDrink; } return true; } private static string GetPrefabName(ItemData? item) { return FoodIdentity.GetCanonicalPrefabName(item); } private static string CleanPrefabName(string? name) { return FoodIdentity.NormalizePrefabName(name); } private static bool EnsureCache() { ObjectDB instance = ObjectDB.instance; ZNetScene instance2 = ZNetScene.instance; if (!IsDatabaseReady(instance, instance2)) { return false; } int instanceID = ((Object)instance).GetInstanceID(); int instanceID2 = ((Object)instance2).GetInstanceID(); int count = instance.m_items.Count; int count2 = instance2.m_namedPrefabs.Count; int count3 = instance2.m_prefabs.Count; int count4 = instance2.m_nonNetViewPrefabs.Count; if (_cacheReady && _cachedObjectDbId == instanceID && _cachedZNetSceneId == instanceID2 && _cachedItemCount == count && _cachedNamedPrefabCount == count2 && _cachedNetPrefabCount == count3 && _cachedNonNetPrefabCount == count4) { return true; } bool cacheReady = _cacheReady; try { BuildCache(instance, instance2); _cachedObjectDbId = instanceID; _cachedZNetSceneId = instanceID2; _cachedItemCount = count; _cachedNamedPrefabCount = count2; _cachedNetPrefabCount = count3; _cachedNonNetPrefabCount = count4; _cacheReady = true; if (cacheReady) { DecayRuntime.InvalidateAll(); SpoilageReferenceGenerator.Invalidate(); } return true; } catch (Exception ex) { Invalidate(); FineDiningPlugin.Log.LogWarning((object)("Failed to build food classification cache: " + ex)); return false; } } private static bool IsDatabaseReady(ObjectDB? objectDb, ZNetScene? scene) { if ((Object)(object)objectDb != (Object)null && objectDb.m_items != null && (Object)(object)scene != (Object)null && scene.m_namedPrefabs != null && scene.m_prefabs != null && scene.m_nonNetViewPrefabs != null && objectDb.m_items.Count > 0) { return scene.m_namedPrefabs.Count + scene.m_prefabs.Count + scene.m_nonNetViewPrefabs.Count > 0; } return false; } private static void BuildCache(ObjectDB objectDb, ZNetScene scene) { List scanRoots = CollectScenePrefabs(scene); HashSet cultivatedRootNames = AddGrownPrefabRoots(scanRoots); HashSet source = BuildFarmingHarvestPrefabSet(scanRoots, cultivatedRootNames); HashSet hashSet = new HashSet(PrefabComparer); HashSet hashSet2 = new HashSet(PrefabComparer); Dictionary> reverseConversionEdges = new Dictionary>(PrefabComparer); HashSet directlyEdiblePrefabs = new HashSet(PrefabComparer); AddCookingStationConversions(scanRoots, hashSet, hashSet2, reverseConversionEdges, directlyEdiblePrefabs); BuildFermenterFoodPrefabSets(scanRoots, reverseConversionEdges, directlyEdiblePrefabs, out HashSet unfermentedFoods, out HashSet fermentedFoods); BuildFeastPrefabSets(objectDb, out HashSet feastMaterials, out HashSet feastResults); HashSet source2 = BuildFishPrefabSet(scanRoots); ReplaceContents(FarmingHarvestPrefabs, source); ReplaceContents(CookingStationInputPrefabs, hashSet); ReplaceContents(CookingStationOutputPrefabs, hashSet2); ReplaceContents(FermentedFoodPrefabs, fermentedFoods); ReplaceContents(UnfermentedFoodPrefabs, unfermentedFoods); ReplaceContents(FeastMaterialPrefabs, feastMaterials); ReplaceContents(FeastResultPrefabs, feastResults); ReplaceContents(FishPrefabs, source2); } private static List CollectScenePrefabs(ZNetScene scene) { List list = new List(); HashSet seenInstanceIds = new HashSet(); AddScenePrefabs(scene.m_namedPrefabs.Values, list, seenInstanceIds); AddScenePrefabs(scene.m_prefabs, list, seenInstanceIds); AddScenePrefabs(scene.m_nonNetViewPrefabs, list, seenInstanceIds); return list; } private static void AddScenePrefabs(IEnumerable source, ICollection target, ISet seenInstanceIds) { foreach (GameObject item in source) { if (!((Object)(object)item == (Object)null)) { int instanceID = ((Object)item).GetInstanceID(); if (seenInstanceIds.Add(instanceID)) { target.Add(item); } } } } private static HashSet AddGrownPrefabRoots(List scanRoots) { HashSet hashSet = new HashSet(PrefabComparer); HashSet hashSet2 = new HashSet(); HashSet hashSet3 = new HashSet(); foreach (GameObject scanRoot in scanRoots) { if ((Object)(object)scanRoot != (Object)null) { hashSet3.Add(((Object)scanRoot).GetInstanceID()); } } for (int i = 0; i < scanRoots.Count; i++) { GameObject val = scanRoots[i]; if ((Object)(object)val == (Object)null || !hashSet2.Add(((Object)val).GetInstanceID())) { continue; } try { Plant[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Plant val2 in componentsInChildren) { if (val2?.m_grownPrefabs == null) { continue; } GameObject[] grownPrefabs = val2.m_grownPrefabs; foreach (GameObject val3 in grownPrefabs) { if (!((Object)(object)val3 == (Object)null)) { int instanceID = ((Object)val3).GetInstanceID(); string text = CleanPrefabName(((Object)val3).name); if (text.Length > 0) { hashSet.Add(text); } if (hashSet3.Add(instanceID)) { scanRoots.Add(val3); } } } } } catch { } } return hashSet; } private static HashSet BuildFarmingHarvestPrefabSet(IEnumerable scanRoots, ISet cultivatedRootNames) { HashSet hashSet = new HashSet(PrefabComparer); foreach (GameObject scanRoot in scanRoots) { if ((Object)(object)scanRoot == (Object)null) { continue; } bool cultivatedRoot = cultivatedRootNames.Contains(CleanPrefabName(((Object)scanRoot).name)); try { Pickable[] componentsInChildren = scanRoot.GetComponentsInChildren(true); foreach (Pickable val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { AddPickableOutputs(val, hashSet, cultivatedRoot); } } } catch { } } return hashSet; } internal static bool ShouldIncludePickableOutput(bool cultivatedRoot, bool directlyEdible, bool hasSeedPrefabSuffix) { if (!directlyEdible) { if (cultivatedRoot) { return !hasSeedPrefabSuffix; } return false; } return true; } internal static bool HasSeedPrefabSuffix(string? prefabName) { string text = CleanPrefabName(prefabName); if (!text.EndsWith("Seed", StringComparison.OrdinalIgnoreCase)) { return text.EndsWith("Seeds", StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsDirectEdibleItemPrefab(GameObject? itemPrefab) { ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null); if (val?.m_itemData?.m_shared != null) { return IsEdible(val.m_itemData); } return false; } private static void AddPickableOutputs(Pickable pickable, ISet harvested, bool cultivatedRoot) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) AddPickableOutput(harvested, pickable.m_itemPrefab, cultivatedRoot); if (pickable.m_extraDrops?.m_drops == null) { return; } foreach (DropData drop in pickable.m_extraDrops.m_drops) { AddPickableOutput(harvested, drop.m_item, cultivatedRoot); } } private static void AddPickableOutput(ISet harvested, GameObject? itemPrefab, bool cultivatedRoot) { bool hasSeedPrefabSuffix = HasSeedPrefabSuffix((itemPrefab != null) ? ((Object)itemPrefab).name : null); bool directlyEdible = IsDirectEdibleItemPrefab(itemPrefab); if (ShouldIncludePickableOutput(cultivatedRoot, directlyEdible, hasSeedPrefabSuffix)) { AddGameObjectItemPrefab(harvested, itemPrefab); } } private static void AddCookingStationConversions(IEnumerable scanRoots, ISet inputs, ISet outputs, IDictionary> reverseConversionEdges, ISet directlyEdiblePrefabs) { HashSet hashSet = new HashSet(); foreach (GameObject scanRoot in scanRoots) { if ((Object)(object)scanRoot == (Object)null) { continue; } CookingStation[] componentsInChildren; try { componentsInChildren = scanRoot.GetComponentsInChildren(true); } catch { continue; } CookingStation[] array = componentsInChildren; foreach (CookingStation val in array) { try { if ((Object)(object)val == (Object)null || !hashSet.Add(((Object)val).GetInstanceID()) || val.m_conversion == null) { continue; } foreach (ItemConversion item in val.m_conversion) { try { if (item != null) { ItemDrop val2 = item.m_from; ItemDrop to = item.m_to; AddItemDropPrefab(inputs, val2); AddItemDropPrefab(outputs, to); AddConversionEdge(val2, to, reverseConversionEdges, directlyEdiblePrefabs); } } catch { } } } catch { } } } } private static void BuildFermenterFoodPrefabSets(IEnumerable scanRoots, IDictionary> reverseConversionEdges, ISet directlyEdiblePrefabs, out HashSet unfermentedFoods, out HashSet fermentedFoods) { fermentedFoods = new HashSet(PrefabComparer); List> list = new List>(); HashSet hashSet = new HashSet(); foreach (GameObject scanRoot in scanRoots) { if ((Object)(object)scanRoot == (Object)null) { continue; } Fermenter[] componentsInChildren; try { componentsInChildren = scanRoot.GetComponentsInChildren(true); } catch { continue; } Fermenter[] array = componentsInChildren; foreach (Fermenter val in array) { try { if ((Object)(object)val == (Object)null || !hashSet.Add(((Object)val).GetInstanceID()) || val.m_conversion == null) { continue; } foreach (ItemConversion item in val.m_conversion) { try { ItemDrop input = item?.m_from; ItemDrop val2 = item?.m_to; if (val2?.m_itemData?.m_shared != null && IsEdible(val2.m_itemData)) { AddItemDropPrefab(fermentedFoods, val2); } if (AddConversionEdge(input, val2, reverseConversionEdges, directlyEdiblePrefabs, out KeyValuePair edge)) { list.Add(edge); } } catch { } } } catch { } } } unfermentedFoods = FindFoodReachableFermenterInputs(list, reverseConversionEdges, directlyEdiblePrefabs); } private static bool AddConversionEdge(ItemDrop? input, ItemDrop? output, IDictionary> reverseConversionEdges, ISet directlyEdiblePrefabs) { KeyValuePair edge; return AddConversionEdge(input, output, reverseConversionEdges, directlyEdiblePrefabs, out edge); } private static bool AddConversionEdge(ItemDrop? input, ItemDrop? output, IDictionary> reverseConversionEdges, ISet directlyEdiblePrefabs, out KeyValuePair edge) { edge = default(KeyValuePair); string itemDropPrefabName = GetItemDropPrefabName(input); string itemDropPrefabName2 = GetItemDropPrefabName(output); if (itemDropPrefabName.Length == 0 || itemDropPrefabName2.Length == 0) { return false; } if (!reverseConversionEdges.TryGetValue(itemDropPrefabName2, out HashSet value)) { value = new HashSet(PrefabComparer); reverseConversionEdges.Add(itemDropPrefabName2, value); } value.Add(itemDropPrefabName); try { if (IsEdible(output?.m_itemData)) { directlyEdiblePrefabs.Add(itemDropPrefabName2); } } catch { } edge = new KeyValuePair(itemDropPrefabName, itemDropPrefabName2); return true; } private static HashSet FindFoodReachableFermenterInputs(IEnumerable> fermenterConversions, IDictionary> reverseConversionEdges, IEnumerable directlyEdiblePrefabs) { HashSet hashSet = new HashSet(directlyEdiblePrefabs, PrefabComparer); Queue queue = new Queue(hashSet); while (queue.Count > 0) { string key = queue.Dequeue(); if (!reverseConversionEdges.TryGetValue(key, out HashSet value)) { continue; } foreach (string item in value) { if (hashSet.Add(item)) { queue.Enqueue(item); } } } HashSet hashSet2 = new HashSet(PrefabComparer); foreach (KeyValuePair fermenterConversion in fermenterConversions) { if (hashSet.Contains(fermenterConversion.Value)) { hashSet2.Add(fermenterConversion.Key); } } return hashSet2; } private static void BuildFeastPrefabSets(ObjectDB objectDb, out HashSet feastMaterials, out HashSet feastResults) { //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Invalid comparison between Unknown and I4 //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Invalid comparison between Unknown and I4 //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Invalid comparison between Unknown and I4 //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Invalid comparison between Unknown and I4 Dictionary dictionary = new Dictionary(PrefabComparer); foreach (GameObject item in objectDb.m_items) { try { ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent() : null); string text = (((Object)(object)val != (Object)null) ? CleanPrefabName(((Object)((Component)val).gameObject).name) : ""); if (text.Length > 0 && !dictionary.ContainsKey(text)) { dictionary.Add(text, val); } } catch { } } feastMaterials = new HashSet(PrefabComparer); feastResults = new HashSet(PrefabComparer); foreach (KeyValuePair item2 in dictionary) { try { ItemDrop value = item2.Value; SharedData val2 = value.m_itemData?.m_shared; Feast component = ((Component)value).GetComponent(); if (val2 == null || (Object)(object)component == (Object)null) { continue; } ItemDrop foodItem = component.m_foodItem; string itemDropPrefabName = GetItemDropPrefabName(foodItem); bool flag = itemDropPrefabName.Length > 0 && !itemDropPrefabName.Equals(item2.Key, StringComparison.OrdinalIgnoreCase); SharedData val3 = foodItem?.m_itemData?.m_shared; bool flag2 = (Object)(object)foodItem != (Object)null && ((Object)(object)((Component)foodItem).GetComponent() != (Object)null || (val3 != null && ((int)val3.m_itemType == 2 || LooksLikeFeastRoutingFood(val3)))); if ((int)val2.m_itemType == 1) { if (flag && flag2) { feastMaterials.Add(item2.Key); feastResults.Add(itemDropPrefabName); } } else if (flag && flag2 && (int)val2.m_itemType != 2 && !LooksLikeFeastRoutingFood(val2)) { feastMaterials.Add(item2.Key); feastResults.Add(itemDropPrefabName); } else { feastResults.Add(item2.Key); } } catch { } } foreach (KeyValuePair item3 in dictionary) { try { SharedData val4 = item3.Value.m_itemData?.m_shared; if (val4 != null && (int)val4.m_itemType == 1) { ItemDrop appendToolTip = val4.m_appendToolTip; string itemDropPrefabName2 = GetItemDropPrefabName(appendToolTip); if (itemDropPrefabName2.Length != 0 && !itemDropPrefabName2.Equals(item3.Key, StringComparison.OrdinalIgnoreCase) && !((Object)(object)appendToolTip == (Object)null) && (feastResults.Contains(itemDropPrefabName2) || (Object)(object)((Component)appendToolTip).GetComponent() != (Object)null)) { feastMaterials.Add(item3.Key); feastResults.Add(itemDropPrefabName2); } } } catch { } } } private static HashSet BuildFishPrefabSet(IEnumerable scanRoots) { HashSet hashSet = new HashSet(PrefabComparer); HashSet hashSet2 = new HashSet(); foreach (GameObject scanRoot in scanRoots) { if ((Object)(object)scanRoot == (Object)null) { continue; } try { Fish[] componentsInChildren = scanRoot.GetComponentsInChildren(true); foreach (Fish val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && hashSet2.Add(((Object)val).GetInstanceID())) { AddGameObjectItemPrefab(hashSet, ((Component)val).gameObject); AddItemDropPrefab(hashSet, ((Component)val).GetComponentInParent()); AddGameObjectItemPrefab(hashSet, val.m_pickupItem); } } } catch { } } return hashSet; } private static string GetItemDropPrefabName(ItemDrop? itemDrop) { if (!((Object)(object)itemDrop == (Object)null)) { return CleanPrefabName(((Object)((Component)itemDrop).gameObject).name); } return ""; } private static void AddItemDropPrefab(ISet target, ItemDrop? itemDrop) { string itemDropPrefabName = GetItemDropPrefabName(itemDrop); if (!string.IsNullOrWhiteSpace(itemDropPrefabName)) { target.Add(itemDropPrefabName); } } private static void AddGameObjectItemPrefab(ISet target, GameObject? prefab) { if (!((Object)(object)prefab == (Object)null) && !((Object)(object)prefab.GetComponent() == (Object)null)) { string text = CleanPrefabName(((Object)prefab).name); if (!string.IsNullOrWhiteSpace(text)) { target.Add(text); } } } private static void ReplaceContents(ISet target, IEnumerable source) { target.Clear(); foreach (string item in source) { target.Add(item); } } private static void ClearClassificationSets() { FarmingHarvestPrefabs.Clear(); CookingStationInputPrefabs.Clear(); CookingStationOutputPrefabs.Clear(); FermentedFoodPrefabs.Clear(); UnfermentedFoodPrefabs.Clear(); FeastMaterialPrefabs.Clear(); FeastResultPrefabs.Clear(); FishPrefabs.Clear(); } } internal sealed class FoodPrefabOwnerSnapshot { private readonly Dictionary _owners; internal FoodPrefabOwnerSnapshot(Dictionary owners) { _owners = owners; } internal string GetOwnerName(string? prefabName) { string text = FoodIdentity.NormalizePrefabName(prefabName); if (text.Length <= 0 || !_owners.TryGetValue(text, out string value)) { return "Unknown / Untracked"; } return value; } } internal static class FoodPrefabOwnerResolver { private sealed class PluginSnapshot { internal string OwnerName { get; set; } = "Unknown / Untracked"; internal string PluginName { get; set; } = ""; internal string PluginGuid { get; set; } = ""; internal string AssemblyName { get; set; } = ""; internal string[] ResourceNames { get; set; } = Array.Empty(); } internal const string VanillaOwnerName = "Valheim"; internal const string UnknownOwnerName = "Unknown / Untracked"; private const int MinimumHeuristicTokenLength = 5; private static readonly HashSet VanillaPrefabNames = new HashSet(StringComparer.OrdinalIgnoreCase); private static bool _vanillaCatalogLoaded; private static bool _vanillaCatalogWarningLogged; internal static FoodPrefabOwnerSnapshot GetSnapshot(IEnumerable prefabNames) { List list = (from name in (prefabNames ?? Enumerable.Empty()).Select(FoodIdentity.NormalizePrefabName) where name.Length > 0 select name).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy((string name) => name, StringComparer.OrdinalIgnoreCase).ThenBy((string name) => name, StringComparer.Ordinal) .ToList(); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (list.Count == 0) { return new FoodPrefabOwnerSnapshot(dictionary); } try { HashSet targets = new HashSet(list, StringComparer.OrdinalIgnoreCase); Dictionary mappings = CollectJotunnOwners(targets); EnsureVanillaCatalogLoaded(); Dictionary> mappings2 = CollectAssetBundleOwners(targets, BuildPluginSnapshots()); foreach (string item in list) { string text = ResolveMappedOwner(item, mappings); if (text.Length == 0 && IsVanillaPrefab(item)) { text = "Valheim"; } if (text.Length == 0) { text = ResolveUniqueBundleOwner(item, mappings2); } dictionary[item] = ((text.Length > 0) ? NormalizeOwnerName(text) : "Unknown / Untracked"); } } catch (Exception ex) { FineDiningPlugin.Log.LogWarning((object)("Could not resolve one or more food prefab owners; unresolved entries will be grouped under 'Unknown / Untracked': " + ex.GetBaseException().Message)); foreach (string item2 in list) { dictionary[item2] = "Unknown / Untracked"; } } return new FoodPrefabOwnerSnapshot(dictionary); } internal static int GetOwnerSortBucket(string? ownerName) { string text = NormalizeOwnerName(ownerName); if (text.Equals("Valheim", StringComparison.OrdinalIgnoreCase)) { return 0; } if (!text.Equals("Unknown / Untracked", StringComparison.OrdinalIgnoreCase)) { return 1; } return 2; } internal static string NormalizeOwnerName(string? ownerName) { if (string.IsNullOrWhiteSpace(ownerName)) { return "Unknown / Untracked"; } StringBuilder stringBuilder = new StringBuilder(); foreach (char c in ownerName) { if (c == '\r' || c == '\n') { stringBuilder.Append(' '); } else if (!char.IsControl(c)) { stringBuilder.Append(c); } } string text = stringBuilder.ToString().Trim(); if (text.Length <= 0) { return "Unknown / Untracked"; } return text; } private static Dictionary CollectJotunnOwners(HashSet targets) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); HashSet lookupCandidates = new HashSet(targets.SelectMany(EnumerateLookupCandidates), StringComparer.OrdinalIgnoreCase); try { foreach (CustomPrefab prefab in ModRegistry.GetPrefabs()) { AddJotunnOwner(prefab.Prefab, (CustomEntity)(object)prefab, lookupCandidates, dictionary); } foreach (CustomItem item in ModRegistry.GetItems()) { AddJotunnOwner(item.ItemPrefab, (CustomEntity)(object)item, lookupCandidates, dictionary); } foreach (CustomPiece piece in ModRegistry.GetPieces()) { AddJotunnOwner(piece.PiecePrefab, (CustomEntity)(object)piece, lookupCandidates, dictionary); } } catch (Exception ex) { FineDiningPlugin.Log.LogDebug((object)("Could not enumerate all Jotunn registry owners: " + ex.GetBaseException().Message)); } return dictionary; } private static void AddJotunnOwner(GameObject? prefab, CustomEntity customEntity, HashSet lookupCandidates, Dictionary owners) { string text = FoodIdentity.NormalizePrefabName((prefab != null) ? ((Object)prefab).name : null); if (text.Length == 0 || !lookupCandidates.Contains(text) || owners.ContainsKey(text)) { return; } BepInPlugin sourceMod = customEntity.SourceMod; string text2 = (((sourceMod != null) ? sourceMod.GUID : null) ?? "").Trim(); if (text2.Length > 0 && Chainloader.PluginInfos.TryGetValue(text2, out var value)) { owners[text] = NormalizeOwnerName(string.IsNullOrWhiteSpace(value.Metadata.Name) ? value.Metadata.GUID : value.Metadata.Name); return; } BepInPlugin sourceMod2 = customEntity.SourceMod; string text3 = (((sourceMod2 != null) ? sourceMod2.Name : null) ?? "").Trim(); string text4 = NormalizeOwnerName((text3.Length > 0) ? text3 : text2); if (!text4.Equals("Unknown / Untracked", StringComparison.OrdinalIgnoreCase)) { owners[text] = text4; } } private static Dictionary> CollectAssetBundleOwners(HashSet targets, List plugins) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); AssetBundle[] array; try { array = (from bundle in AssetBundle.GetAllLoadedAssetBundles() where (Object)(object)bundle != (Object)null select bundle).OrderBy((AssetBundle bundle) => ((Object)bundle).name ?? "", StringComparer.OrdinalIgnoreCase).ToArray(); } catch { return dictionary; } AssetBundle[] array2 = array; foreach (AssetBundle val in array2) { string text = ResolveBundleOwner(((Object)val).name ?? "", plugins); if (text.Length == 0) { continue; } string[] allAssetNames; try { allAssetNames = val.GetAllAssetNames(); } catch { continue; } string[] array3 = allAssetNames; foreach (string text2 in array3) { if (!text2.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) { continue; } string text3 = FoodIdentity.NormalizePrefabName(Path.GetFileNameWithoutExtension(text2)); if (text3.Length != 0 && targets.Contains(text3)) { if (!dictionary.TryGetValue(text3, out var value)) { value = new HashSet(StringComparer.OrdinalIgnoreCase); dictionary.Add(text3, value); } value.Add(text); } } } return dictionary; } private static string ResolveBundleOwner(string bundleName, List plugins) { string normalizedBundleName = (bundleName ?? "").Trim(); if (normalizedBundleName.Length == 0) { return ""; } string[] array = (from plugin in plugins where plugin.ResourceNames.Any((string resourceName) => IsBundleResourceMatch(resourceName, normalizedBundleName)) select plugin.OwnerName).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); if (array.Length != 0) { if (array.Length != 1) { return ""; } return array[0]; } string bundleToken = NormalizeToken(Path.GetFileNameWithoutExtension(normalizedBundleName)); if (bundleToken.Length < 5) { return ""; } string[] array2 = (from plugin in plugins where IsTokenMatch(bundleToken, NormalizeToken(plugin.PluginName)) || IsTokenMatch(bundleToken, NormalizeToken(plugin.PluginGuid)) || IsTokenMatch(bundleToken, NormalizeToken(plugin.AssemblyName)) select plugin.OwnerName).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); if (array2.Length != 1) { return ""; } return array2[0]; } private static bool IsBundleResourceMatch(string? resourceName, string bundleName) { string text = (resourceName ?? "").Trim(); if (text.Length > 0) { if (!text.Equals(bundleName, StringComparison.OrdinalIgnoreCase)) { return text.EndsWith("." + bundleName, StringComparison.OrdinalIgnoreCase); } return true; } return false; } private static bool IsTokenMatch(string bundleToken, string pluginToken) { if (pluginToken.Length >= 5) { if (bundleToken.IndexOf(pluginToken, StringComparison.OrdinalIgnoreCase) < 0) { return pluginToken.IndexOf(bundleToken, StringComparison.OrdinalIgnoreCase) >= 0; } return true; } return false; } private static string NormalizeToken(string value) { StringBuilder stringBuilder = new StringBuilder(); string text = value ?? ""; foreach (char c in text) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } private static List BuildPluginSnapshots() { List list = new List(); foreach (PluginInfo value in Chainloader.PluginInfos.Values) { string text = (value.Metadata.Name ?? "").Trim(); string text2 = (value.Metadata.GUID ?? "").Trim(); string assemblyName = ""; string[] resourceNames = Array.Empty(); try { Assembly obj = ((object)value.Instance)?.GetType().Assembly; assemblyName = obj?.GetName().Name ?? ""; resourceNames = obj?.GetManifestResourceNames() ?? Array.Empty(); } catch { } list.Add(new PluginSnapshot { OwnerName = NormalizeOwnerName((text.Length > 0) ? text : text2), PluginName = text, PluginGuid = text2, AssemblyName = assemblyName, ResourceNames = resourceNames }); } return list; } private static string ResolveMappedOwner(string prefabName, IReadOnlyDictionary mappings) { foreach (string item in EnumerateLookupCandidates(prefabName)) { if (mappings.TryGetValue(item, out string value) && value.Length > 0) { return value; } } return ""; } private static string ResolveUniqueBundleOwner(string prefabName, IReadOnlyDictionary> mappings) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string item in EnumerateLookupCandidates(prefabName)) { if (mappings.TryGetValue(item, out HashSet value)) { hashSet.UnionWith(value); } } if (hashSet.Count != 1) { return ""; } return hashSet.First(); } private static bool IsVanillaPrefab(string prefabName) { return EnumerateLookupCandidates(prefabName).Any(VanillaPrefabNames.Contains); } private static IEnumerable EnumerateLookupCandidates(string prefabName) { string normalized = FoodIdentity.NormalizePrefabName(prefabName); if (normalized.Length != 0) { yield return normalized; int num = normalized.IndexOf(':'); if (num > 0) { yield return normalized.Substring(0, num); } } } private static void EnsureVanillaCatalogLoaded() { if (_vanillaCatalogLoaded) { return; } string text = Path.Combine(Application.dataPath, "StreamingAssets", "SoftRef"); string[] obj = new string[2] { Path.Combine(text, "manifest"), Path.Combine(text, "manifest_extended") }; int num = 0; int num2 = 0; string[] array = obj; foreach (string text2 in array) { if (!File.Exists(text2)) { continue; } num++; try { foreach (string item in File.ReadLines(text2)) { int num3 = item.IndexOf("path in bundle:", StringComparison.OrdinalIgnoreCase); if (num3 < 0) { continue; } string text3 = item.Substring(num3 + "path in bundle:".Length).Trim(); if (text3.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) { string text4 = FoodIdentity.NormalizePrefabName(Path.GetFileNameWithoutExtension(text3)); if (text4.Length > 0) { VanillaPrefabNames.Add(text4); } } } num2++; } catch (Exception ex) { WarnVanillaCatalogOnce("Could not read vanilla prefab manifest '" + text2 + "': " + ex.GetBaseException().Message); } } _vanillaCatalogLoaded = num2 > 0; if (num == 0) { WarnVanillaCatalogOnce("Vanilla prefab manifests were not found under '" + text + "'; vanilla reference entries may be grouped under 'Unknown / Untracked'."); } } private static void WarnVanillaCatalogOnce(string message) { if (!_vanillaCatalogWarningLogged) { _vanillaCatalogWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)message); } } } internal readonly struct AssignedLifetimeSnapshot { internal string? RawValue { get; } internal long Value { get; } internal bool Valid { get; } internal AssignedLifetimeSnapshot(string? rawValue, long value, bool valid) { RawValue = rawValue; Value = value; Valid = valid; } } internal static class FreshnessRuntime { internal const string AssignedLifetimeDataKey = "sighsorry.FineDining.AssignedLifetimeTicks"; internal const float DefaultMinimumFoodMultiplier = 0.75f; private static ConfigEntry? _minimumFoodMultiplier; internal static float MinimumFoodMultiplier => Mathf.Clamp01(_minimumFoodMultiplier?.Value ?? 0.75f); internal static void Initialize(ConfigFile config, ConfigSync configSync) { Shutdown(); _minimumFoodMultiplier = BindMinimumFoodMultiplier(config); configSync.AddConfigEntry(_minimumFoodMultiplier).SynchronizedConfig = true; } internal static ConfigEntry BindMinimumFoodMultiplier(ConfigFile config) { return config.Bind(ConfigPresentation.Spoilage.Name, "Stale Food Minimum Multiplier", 0.75f, ConfigPresentation.Synced("Minimum multiplier applied to a directly edible item's health, stamina, eitr, and health regeneration at zero freshness. Intermediate freshness is interpolated linearly between this value and 1.", ConfigPresentation.Spoilage, 500, (AcceptableValueBase?)(object)new AcceptableValueRange(0f, 1f))); } internal static void Shutdown() { _minimumFoodMultiplier = null; } internal static float GetFoodStatMultiplier(ItemData? item) { if (!FoodIdentity.IsDirectlyEdible(item)) { return 1f; } float ratio; return CalculateFoodStatMultiplier(TryGetFreshnessRatio(item, out ratio) ? ratio : 1f); } internal static float CalculateFoodStatMultiplier(float freshnessRatio) { return CalculateFoodStatMultiplierForMinimum(freshnessRatio, MinimumFoodMultiplier); } internal static float CalculateFoodStatMultiplierForMinimum(float freshnessRatio, float minimumMultiplier) { float num = ClampRatio(minimumMultiplier); return Mathf.Clamp(num + (1f - num) * ClampRatio(freshnessRatio), num, 1f); } internal static bool TryGetFreshnessRatio(ItemData? item, out float ratio) { ratio = 1f; if (item == null || !DecayRuntime.TryGetExpiryTicks(item, out var clockValue) || !DecayRuntime.TryGetWorldTicks(out var ticks) || !SpoilageClock.TryDecodeClockValue(clockValue, ticks, out var remainingTicks, out var _)) { return false; } long lifetime; long num = (TryGetAssignedLifetime(item, out lifetime) ? lifetime : ResolveRuleLifetime(item, remainingTicks)); ratio = ClampRatio((double)remainingTicks / (double)num); return true; } internal static bool EnsureTrackedMetadata(ItemData item, long assignedLifetimeTicks) { if (item == null) { return false; } if (item.m_customData == null) { item.m_customData = new Dictionary(); } if (TryGetAssignedLifetime(item, out var _)) { return false; } item.m_customData["sighsorry.FineDining.AssignedLifetimeTicks"] = NormalizeLifetime(assignedLifetimeTicks).ToString(CultureInfo.InvariantCulture); return true; } internal static bool ClearTrackedMetadata(ItemData? item) { if (item?.m_customData != null) { return item.m_customData.Remove("sighsorry.FineDining.AssignedLifetimeTicks"); } return false; } internal static void CopyFreshnessMetadata(ItemData? source, ItemData? destination) { if (destination != null) { if (destination.m_customData == null) { destination.m_customData = new Dictionary(); } CopyOrRemove(source, destination, "sighsorry.FineDining.ExpiryWorldTicks"); CopyOrRemove(source, destination, "sighsorry.FineDining.AssignedLifetimeTicks"); } } internal static AssignedLifetimeSnapshot CaptureAssignedLifetime(ItemData? item) { string value = null; item?.m_customData?.TryGetValue("sighsorry.FineDining.AssignedLifetimeTicks", out value); long parsed; bool valid = TryParsePositiveLong(value, out parsed); return new AssignedLifetimeSnapshot(value, parsed, valid); } internal static bool ComposeAssignedLifetime(ItemData target, AssignedLifetimeSnapshot destination, AssignedLifetimeSnapshot source) { if (target == null || !source.Valid) { return false; } long num; if (destination.Valid) { num = Math.Max(destination.Value, source.Value); } else { if (destination.RawValue != null) { return false; } num = source.Value; } if (target.m_customData == null) { target.m_customData = new Dictionary(); } string text = num.ToString(CultureInfo.InvariantCulture); if (target.m_customData.TryGetValue("sighsorry.FineDining.AssignedLifetimeTicks", out var value) && string.Equals(value, text, StringComparison.Ordinal)) { return false; } target.m_customData["sighsorry.FineDining.AssignedLifetimeTicks"] = text; return true; } internal static string? ComposeAssignedLifetimeValues(string? destinationValue, string? sourceValue) { long parsed; bool flag = TryParsePositiveLong(destinationValue, out parsed); if (!TryParsePositiveLong(sourceValue, out var parsed2)) { return destinationValue; } if (!flag) { if (destinationValue != null) { return destinationValue; } return parsed2.ToString(CultureInfo.InvariantCulture); } return Math.Max(parsed, parsed2).ToString(CultureInfo.InvariantCulture); } internal static bool CanMergeAssignedLifetimeValues(string? destinationValue, string? sourceValue) { if (destinationValue == null || TryParsePositiveLong(destinationValue, out var parsed)) { if (sourceValue != null) { return TryParsePositiveLong(sourceValue, out parsed); } return true; } return false; } internal static bool TryGetAssignedLifetime(ItemData? item, out long lifetime) { lifetime = 0L; if (item?.m_customData != null && item.m_customData.TryGetValue("sighsorry.FineDining.AssignedLifetimeTicks", out var value)) { return TryParsePositiveLong(value, out lifetime); } return false; } private static long ResolveRuleLifetime(ItemData item, long fallbackRemaining) { ResolvedSpoilageRule resolvedSpoilageRule = SpoilagePolicy.Resolve(item); if (resolvedSpoilageRule.State != SpoilageRuleState.Enabled) { return NormalizeLifetime(fallbackRemaining); } return NormalizeLifetime(resolvedSpoilageRule.LifetimeTicks); } private static void CopyOrRemove(ItemData? source, ItemData destination, string key) { if (source?.m_customData != null && source.m_customData.TryGetValue(key, out var value)) { destination.m_customData[key] = value; } else { destination.m_customData.Remove(key); } } private static bool TryParsePositiveLong(string? value, out long parsed) { parsed = 0L; if (value != null && long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed) && parsed > 0) { return string.Equals(value, parsed.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal); } return false; } private static long NormalizeLifetime(long ticks) { return Math.Max(10000000L, ticks); } private static float ClampRatio(double ratio) { if (double.IsNaN(ratio) || double.IsInfinity(ratio)) { return 1f; } return (float)Math.Max(0.0, Math.Min(1.0, ratio)); } } internal static class GeneratedPrefabRegistry { internal const string IceboxPrefabName = "FineDining_Icebox"; internal const string RottenProducePrefabName = "FineDining_RottenProduce"; internal const string RottenFoodPrefabName = "FineDining_RottenFood"; internal const string RottenProducePukeStatusEffectName = "FineDining_PukeRottenProduce"; internal const string RottenFoodPukeStatusEffectName = "FineDining_PukeRottenFood"; internal const string IceboxNameToken = "$finedining_icebox"; internal const string IceboxDescriptionToken = "$finedining_icebox_description"; internal const string RottenProduceNameToken = "$finedining_rotten_produce"; internal const string RottenProduceDescriptionToken = "$finedining_rotten_produce_description"; internal const string RottenFoodNameToken = "$finedining_rotten_food"; internal const string RottenFoodDescriptionToken = "$finedining_rotten_food_description"; private const string IceboxSourcePrefabName = "piece_chest"; private const string RottenProduceSourcePrefabName = "Resin"; private const string RottenFoodSourcePrefabName = "BreadDough"; private const string PukeSourceItemPrefabName = "RottenMeat"; private const string PukeSourceStatusEffectName = "Puke"; private const string GeneratedPukeStatusEffectCategory = "FineDining_Puke"; private const float RottenProducePukeDurationSeconds = 5f; private const float RottenFoodPukeDurationSeconds = 10f; private const string IceboxMaterialName = "antifreezegland"; private const string RottenMaterialName = "LoxMeatRotten"; private const float IceboxHealth = 1000f; private const int GeneratedIconSize = 128; private static readonly MethodInfo? MemberwiseCloneMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly HashSet ReportedProblems = new HashSet(StringComparer.Ordinal); private static readonly HashSet RenderedIcons = new HashSet(StringComparer.Ordinal); private static GameObject? _iceboxPrefab; private static GameObject? _rottenProducePrefab; private static GameObject? _rottenFoodPrefab; private static SE_Puke? _rottenProducePukeStatusEffect; private static SE_Puke? _rottenFoodPukeStatusEffect; private static Sprite? _iceboxIcon; private static Sprite? _rottenProduceIcon; private static Sprite? _rottenFoodIcon; private static Material? _iceboxMaterial; private static Material? _rottenMaterial; private static bool _initialized; internal static int IceboxPrefabHash => StringExtensionMethods.GetStableHashCode("FineDining_Icebox"); internal static void Initialize() { if (!_initialized) { _initialized = true; PrefabManager.OnVanillaPrefabsAvailable += CreateContent; PrefabManager.OnPrefabsRegistered += OnJotunnRegistriesReady; ItemManager.OnItemsRegistered += OnJotunnRegistriesReady; PieceManager.OnPiecesRegistered += OnJotunnRegistriesReady; } } internal static void Shutdown() { if (_initialized) { PrefabManager.OnVanillaPrefabsAvailable -= CreateContent; PrefabManager.OnPrefabsRegistered -= OnJotunnRegistriesReady; ItemManager.OnItemsRegistered -= OnJotunnRegistriesReady; PieceManager.OnPiecesRegistered -= OnJotunnRegistriesReady; _initialized = false; } } private static void CreateContent() { try { ReconcileManagedContent(); if (_rottenProducePukeStatusEffect == null) { _rottenProducePukeStatusEffect = CreatePukeStatusEffect("FineDining_PukeRottenProduce", 5f); } if (_rottenFoodPukeStatusEffect == null) { _rottenFoodPukeStatusEffect = CreatePukeStatusEffect("FineDining_PukeRottenFood", 10f); } if (_rottenProducePukeStatusEffect != null && _rottenProducePrefab == null) { _rottenProducePrefab = CreateGeneratedItem("FineDining_RottenProduce", "Resin", "$finedining_rotten_produce", "$finedining_rotten_produce_description", _rottenProducePukeStatusEffect, ref _rottenProduceIcon); } if (_rottenFoodPukeStatusEffect != null && _rottenFoodPrefab == null) { _rottenFoodPrefab = CreateGeneratedItem("FineDining_RottenFood", "BreadDough", "$finedining_rotten_food", "$finedining_rotten_food_description", _rottenFoodPukeStatusEffect, ref _rottenFoodIcon); } if (_iceboxPrefab == null) { _iceboxPrefab = CreateIcebox(); } if (_rottenProducePrefab != null && _rottenFoodPrefab != null && _iceboxPrefab != null) { LogInfoOnce("jotunn-content", "FineDining custom content was created and handed to Jotunn."); } RefreshConfiguredContent(); } catch (Exception arg) { LogProblemOnce("jotunn-content", $"Could not create FineDining custom content through Jotunn: {arg}"); } } private static void ReconcileManagedContent() { CustomItem item = ItemManager.Instance.GetItem("FineDining_RottenProduce"); CustomItem item2 = ItemManager.Instance.GetItem("FineDining_RottenFood"); CustomPiece piece = PieceManager.Instance.GetPiece("FineDining_Icebox"); _rottenProducePrefab = AliveOrNull((item != null) ? item.ItemPrefab : null) ?? AliveOrNull(_rottenProducePrefab); _rottenFoodPrefab = AliveOrNull((item2 != null) ? item2.ItemPrefab : null) ?? AliveOrNull(_rottenFoodPrefab); _iceboxPrefab = AliveOrNull((piece != null) ? piece.PiecePrefab : null) ?? AliveOrNull(_iceboxPrefab); StatusEffect obj = ((item == null) ? null : item.ItemDrop?.m_itemData?.m_shared?.m_consumeStatusEffect); _rottenProducePukeStatusEffect = AliveOrNull((SE_Puke?)(object)((obj is SE_Puke) ? obj : null)) ?? AliveOrNull(_rottenProducePukeStatusEffect); StatusEffect obj2 = ((item2 == null) ? null : item2.ItemDrop?.m_itemData?.m_shared?.m_consumeStatusEffect); _rottenFoodPukeStatusEffect = AliveOrNull((SE_Puke?)(object)((obj2 is SE_Puke) ? obj2 : null)) ?? AliveOrNull(_rottenFoodPukeStatusEffect); } private static void OnJotunnRegistriesReady() { ReconcileManagedContent(); RefreshConfiguredContent(); FoodClassifier.Invalidate(); DecayRuntime.InvalidateAll(); } internal static void RefreshConfiguredContent() { try { if (AliveOrNull(_rottenProducePrefab) != null && AliveOrNull(_rottenProducePukeStatusEffect) != null) { ConfigureGeneratedItem(_rottenProducePrefab, "FineDining_RottenProduce", "$finedining_rotten_produce", "$finedining_rotten_produce_description", _rottenProducePukeStatusEffect, ref _rottenProduceIcon); } if (AliveOrNull(_rottenFoodPrefab) != null && AliveOrNull(_rottenFoodPukeStatusEffect) != null) { ConfigureGeneratedItem(_rottenFoodPrefab, "FineDining_RottenFood", "$finedining_rotten_food", "$finedining_rotten_food_description", _rottenFoodPukeStatusEffect, ref _rottenFoodIcon); } RefreshIceboxConfiguredContentCore(); } catch (Exception arg) { LogProblemOnce("jotunn-refresh", $"Could not refresh FineDining custom content: {arg}"); } } internal static void RefreshIceboxConfiguredContent() { try { RefreshIceboxConfiguredContentCore(); } catch (Exception arg) { LogProblemOnce("jotunn-icebox-refresh", $"Could not refresh FineDining Icebox content: {arg}"); } } private static void RefreshIceboxConfiguredContentCore() { if (AliveOrNull(_iceboxPrefab) != null) { ConfigureIceboxPrefab(_iceboxPrefab); RefreshIceboxBuildContent(); } IceboxSubsystem.ApplyStoredRecipesToLoadedIceboxes(); } internal static bool IsGeneratedReplacementPrefabName(string? prefabName) { if (!string.Equals(prefabName, "FineDining_RottenProduce", StringComparison.Ordinal)) { return string.Equals(prefabName, "FineDining_RottenFood", StringComparison.Ordinal); } return true; } internal static bool EnsureGeneratedReplacementAvailable(string? prefabName) { GameObject val = ((prefabName == "FineDining_RottenProduce") ? AliveOrNull(_rottenProducePrefab) : ((!(prefabName == "FineDining_RottenFood")) ? null : AliveOrNull(_rottenFoodPrefab))); GameObject val2 = val; ObjectDB val3 = AliveOrNull(ObjectDB.instance); ZNetScene val4 = AliveOrNull(ZNetScene.instance); if (val2 == null || val3 == null || val4 == null) { return false; } int stableHashCode = StringExtensionMethods.GetStableHashCode(prefabName); GameObject? obj = AliveOrNull(val3.GetItemPrefab(prefabName)); GameObject value; bool flag = val4.m_namedPrefabs.TryGetValue(stableHashCode, out value) && value == val2; StatusEffect val5 = val2.GetComponent()?.m_itemData?.m_shared?.m_consumeStatusEffect; bool flag2 = val5 != null && val3.GetStatusEffect(val5.NameHash()) == val5; bool num = obj == val2 && flag && flag2; if (!num) { LogProblemOnce("replacement-not-ready:" + prefabName, "Jotunn has not installed generated replacement '" + prefabName + "' into every live registry; expired source items will be retained."); } return num; } internal static bool IsIcebox(Container? container) { if (container != null) { return IsIcebox(((Component)container).gameObject); } return false; } internal static bool IsIcebox(GameObject? gameObject) { if (gameObject != null) { return IsIceboxPrefabName(Utils.GetPrefabName(gameObject)); } return false; } internal static bool IsIceboxPrefabName(string? prefabName) { return string.Equals(FoodIdentity.NormalizePrefabName(prefabName), "FineDining_Icebox", StringComparison.Ordinal); } internal static Sprite? GetIceboxIcon() { Sprite val = AliveOrNull(_iceboxIcon); if (val != null) { return val; } GameObject? obj = AliveOrNull(_iceboxPrefab); if (obj == null) { return null; } return obj.GetComponent()?.m_icon; } private static SE_Puke? CreatePukeStatusEffect(string statusEffectName, float durationSeconds) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown GameObject prefab = PrefabManager.Instance.GetPrefab("RottenMeat"); StatusEffect obj = ((prefab == null) ? null : prefab.GetComponent()?.m_itemData?.m_shared?.m_consumeStatusEffect); SE_Puke value = (SE_Puke)(object)((obj is SE_Puke) ? obj : null); value = AliveOrNull(value) ?? Cache.GetPrefab("Puke"); if (value == null) { LogProblemOnce("puke-source:" + statusEffectName, "Could not create '" + statusEffectName + "': vanilla status effect 'Puke' was not found."); return null; } SE_Puke val = Object.Instantiate(value); ((Object)val).name = statusEffectName; ((StatusEffect)val).m_nameHash = StringExtensionMethods.GetStableHashCode(statusEffectName); ((StatusEffect)val).m_ttl = durationSeconds; ((StatusEffect)val).m_category = "FineDining_Puke"; Object.DontDestroyOnLoad((Object)(object)val); if (ItemManager.Instance.AddStatusEffect(new CustomStatusEffect((StatusEffect)(object)val, false))) { return val; } Object.Destroy((Object)(object)val); LogProblemOnce("puke-registration:" + statusEffectName, "Jotunn refused status effect '" + statusEffectName + "', usually because that name is already registered."); return null; } private static GameObject? CreateGeneratedItem(string prefabName, string sourcePrefabName, string nameToken, string descriptionToken, SE_Puke consumeStatusEffect, ref Sprite? storedIcon) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown if (AliveOrNull(PrefabManager.Instance.GetPrefab(prefabName)) != null) { LogProblemOnce("item-collision:" + prefabName, "Jotunn could not create '" + prefabName + "' because that prefab name is already occupied."); return null; } CustomItem val = new CustomItem(prefabName, sourcePrefabName); GameObject val2 = AliveOrNull(val.ItemPrefab); ItemDrop val3 = AliveOrNull(val.ItemDrop); if (val2 == null || val3 == null) { LogProblemOnce("item-source:" + prefabName, "Could not clone source item '" + sourcePrefabName + "' as '" + prefabName + "'."); return null; } ConfigureGeneratedItem(val2, prefabName, nameToken, descriptionToken, consumeStatusEffect, ref storedIcon); if (!ItemManager.Instance.AddItem(val)) { LogProblemOnce("item-registration:" + prefabName, "Jotunn refused generated item '" + prefabName + "'."); return null; } return val2; } private static GameObject? CreateIcebox() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown if (AliveOrNull(PrefabManager.Instance.GetPrefab("FineDining_Icebox")) != null) { LogProblemOnce("piece-collision:FineDining_Icebox", "Jotunn could not create 'FineDining_Icebox' because that prefab name is already occupied."); return null; } CustomPiece val = new CustomPiece("FineDining_Icebox", "piece_chest", PieceTables.Hammer); GameObject val2 = AliveOrNull(val.PiecePrefab); if (val2 == null || AliveOrNull(val.Piece) == null || val2.GetComponent() == null || val2.GetComponent() == null) { LogProblemOnce("piece-source:FineDining_Icebox", "Could not clone 'piece_chest' as 'FineDining_Icebox' with its required components."); return null; } ConfigureIceboxPrefab(val2); if (!PieceManager.Instance.AddPiece(val)) { LogProblemOnce("piece-registration:FineDining_Icebox", "Jotunn refused generated piece 'FineDining_Icebox'."); return null; } return val2; } private static void ConfigureGeneratedItem(GameObject prefab, string prefabName, string nameToken, string descriptionToken, SE_Puke consumeStatusEffect, ref Sprite? storedIcon) { ItemDrop component = prefab.GetComponent(); if (component == null) { LogProblemOnce("itemdrop:" + prefabName, "Generated prefab '" + prefabName + "' has no ItemDrop component."); return; } SharedData configuredSharedData = GetConfiguredSharedData(component, prefabName); if (!IsConfiguredGeneratedSharedData(configuredSharedData, nameToken, consumeStatusEffect)) { ItemData val = component.m_itemData.Clone(); SharedData shared = CloneSharedData(configuredSharedData); SanitizeAsRottenConsumable(shared, nameToken, descriptionToken, consumeStatusEffect); val.m_shared = shared; component.m_itemData = val; } else { SanitizeAsRottenConsumable(component.m_itemData.m_shared, nameToken, descriptionToken, consumeStatusEffect); } component.m_itemData.m_stack = 1; component.m_itemData.m_quality = 1; component.m_itemData.m_variant = 0; component.m_itemData.m_crafterID = 0L; component.m_itemData.m_crafterName = string.Empty; component.m_itemData.m_customData.Clear(); component.m_itemData.m_equipped = false; component.m_itemData.m_dropPrefab = prefab; if (storedIcon == null) { storedIcon = component.m_itemData.m_shared.m_icons?.FirstOrDefault(); } Material val2 = ResolveMaterial("LoxMeatRotten", ref _rottenMaterial); if (val2 == null) { LogProblemOnce("material:LoxMeatRotten", "Material 'LoxMeatRotten' was not found; generated rotten items use their source visuals."); return; } ApplyMaterialOverride(prefab, val2); TryApplyRenderedItemIcon(prefab, component, prefabName, ref storedIcon); } private static bool IsConfiguredGeneratedSharedData(SharedData? shared, string expectedNameToken, SE_Puke consumeStatusEffect) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 if (shared != null && (int)shared.m_itemType == 2 && string.Equals(shared.m_name, expectedNameToken, StringComparison.Ordinal) && (Object)(object)shared.m_appendToolTip == (Object)null) { return (object)shared.m_consumeStatusEffect == consumeStatusEffect; } return false; } private static SharedData GetConfiguredSharedData(ItemDrop itemDrop, string prefabName) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown if (itemDrop.m_itemData?.m_shared == null) { LogProblemOnce("shared:" + prefabName, "Generated prefab '" + prefabName + "' has no item SharedData."); if (itemDrop.m_itemData == null) { itemDrop.m_itemData = new ItemData(); } itemDrop.m_itemData.m_shared = new SharedData(); } return itemDrop.m_itemData.m_shared; } private static void SanitizeAsRottenConsumable(SharedData shared, string nameToken, string descriptionToken, SE_Puke consumeStatusEffect) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) shared.m_name = nameToken; shared.m_description = descriptionToken; shared.m_subtitle = string.Empty; shared.m_dlc = string.Empty; shared.m_itemType = (ItemType)2; shared.m_attachOverride = (ItemType)0; shared.m_buildPieces = null; shared.m_questItem = false; shared.m_autoStack = true; shared.m_maxQuality = 1; shared.m_scaleByQuality = 0f; shared.m_scaleWeightByQuality = 0f; shared.m_value = 0; shared.m_useDurability = false; shared.m_destroyBroken = false; shared.m_canBeReparied = false; shared.m_maxDurability = 0f; shared.m_durabilityPerLevel = 0f; shared.m_useDurabilityDrain = 0f; shared.m_durabilityDrain = 0f; shared.m_setName = string.Empty; shared.m_setSize = 0; shared.m_setStatusEffect = null; shared.m_equipStatusEffect = null; shared.m_consumeStatusEffect = (StatusEffect)(object)consumeStatusEffect; shared.m_appendToolTip = null; shared.m_food = 0f; shared.m_foodStamina = 0f; shared.m_foodEitr = 0f; shared.m_foodBurnTime = 0f; shared.m_foodRegen = 0f; shared.m_isDrink = false; shared.m_eitrRegenModifier = 0f; shared.m_movementModifier = 0f; shared.m_homeItemsStaminaModifier = 0f; shared.m_heatResistanceModifier = 0f; shared.m_jumpStaminaModifier = 0f; shared.m_attackStaminaModifier = 0f; shared.m_blockStaminaModifier = 0f; shared.m_dodgeStaminaModifier = 0f; shared.m_swimStaminaModifier = 0f; shared.m_sneakStaminaModifier = 0f; shared.m_runStaminaModifier = 0f; if (shared.m_maxStackSize < 1) { shared.m_maxStackSize = 1; } if (shared.m_icons == null) { shared.m_icons = Array.Empty(); } shared.m_variants = ((shared.m_icons.Length != 0) ? 1 : 0); } private static SharedData CloneSharedData(SharedData? source) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_0023: Unknown result type (might be due to invalid IL or missing references) SharedData val; try { val = ((source != null && MemberwiseCloneMethod != null) ? ((SharedData)MemberwiseCloneMethod.Invoke(source, null)) : new SharedData()); } catch (Exception ex) { FineDiningPlugin.Log.LogDebug((object)("Could not copy generated item SharedData; using safe defaults: " + ex.Message)); val = new SharedData(); } val.m_icons = source?.m_icons?.ToArray() ?? Array.Empty(); val.m_helmetHairSettings = ((source?.m_helmetHairSettings != null) ? new List(source.m_helmetHairSettings) : new List()); val.m_helmetBeardSettings = ((source?.m_helmetBeardSettings != null) ? new List(source.m_helmetBeardSettings) : new List()); val.m_damageModifiers = ((source?.m_damageModifiers != null) ? new List(source.m_damageModifiers) : new List()); val.m_itemStandOffsets = ((source?.m_itemStandOffsets != null) ? new List(source.m_itemStandOffsets) : new List()); return val; } private static void ConfigureIceboxPrefab(GameObject prefab) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) Piece component = prefab.GetComponent(); Container component2 = prefab.GetComponent(); WearNTear component3 = prefab.GetComponent(); if (component == null || component2 == null || component3 == null) { LogProblemOnce("icebox-components", "Generated prefab 'FineDining_Icebox' is missing a required component."); return; } component.m_name = "$finedining_icebox"; component.m_description = "$finedining_icebox_description"; component.m_category = (PieceCategory)0; component2.m_name = "$finedining_icebox"; component2.m_width = 8; component2.m_height = IceboxSubsystem.StorageRows; component3.m_health = 1000f; if (_iceboxIcon == null) { _iceboxIcon = component.m_icon; } Material val = ResolveMaterial("antifreezegland", ref _iceboxMaterial); if (val == null) { LogProblemOnce("material:antifreezegland", "Material 'antifreezegland' was not found; 'FineDining_Icebox' uses the source chest visuals."); return; } ApplyMaterialOverride(prefab, val); TryApplyRenderedPieceIcon(prefab, component, ref _iceboxIcon); } private static void RefreshIceboxBuildContent() { GameObject val = AliveOrNull(_iceboxPrefab); ObjectDB val2 = AliveOrNull(ObjectDB.instance); Piece val3 = ((val != null) ? val.GetComponent() : null); if (val == null || val2 == null || val3 == null) { return; } if (!IceboxSubsystem.TryCreateRequirements(val2, IceboxSubsystem.Recipe, out Requirement[] requirements)) { val3.m_enabled = false; RemoveIceboxFromCurrentPieceTable(val); return; } val3.m_resources = requirements; val3.m_enabled = true; try { PieceManager.Instance.RegisterPieceInPieceTable(val, PieceTables.Hammer, (string)null); RefreshLocalPieceTable(PieceManager.Instance.GetPieceTable(PieceTables.Hammer)); } catch (Exception ex) { LogDebugOnce("icebox-piece-table", "Icebox build-table refresh is waiting for Hammer: " + ex.Message); } } private static void RemoveIceboxFromCurrentPieceTable(GameObject prefab) { PieceTable pieceTable = PieceManager.Instance.GetPieceTable(PieceTables.Hammer); if (pieceTable != null) { pieceTable.m_pieces.RemoveAll((GameObject existing) => existing == prefab); RefreshLocalPieceTable(pieceTable); } } private static void RefreshLocalPieceTable(PieceTable? pieceTable) { if (pieceTable != null && Player.m_localPlayer != null && Player.m_localPlayer.m_buildPieces != null && Player.m_localPlayer.m_buildPieces == pieceTable) { ((Humanoid)Player.m_localPlayer).SetPlaceMode(pieceTable); } } internal static GameObject? FindItemPrefab(ObjectDB? objectDb, string prefabName) { if (objectDb != null) { GameObject val = AliveOrNull(objectDb.GetItemPrefab(prefabName)); if (val == null || !string.Equals(((Object)val).name, prefabName, StringComparison.Ordinal)) { return null; } return val; } GameObject val2 = AliveOrNull(PrefabManager.Instance.GetPrefab(prefabName)); if (val2 == null || !string.Equals(((Object)val2).name, prefabName, StringComparison.Ordinal) || val2.GetComponent() == null) { return null; } return val2; } private static void TryApplyRenderedItemIcon(GameObject prefab, ItemDrop itemDrop, string prefabName, ref Sprite? storedIcon) { if (RenderedIcons.Contains(prefabName)) { Sprite val = AliveOrNull(storedIcon); if (val != null) { itemDrop.m_itemData.m_shared.m_icons = (Sprite[])(object)new Sprite[1] { val }; itemDrop.m_itemData.m_variant = 0; } return; } Sprite val2 = RenderGeneratedIcon(prefab, prefabName + "_Icon"); if (val2 != null) { storedIcon = val2; itemDrop.m_itemData.m_shared.m_icons = (Sprite[])(object)new Sprite[1] { val2 }; itemDrop.m_itemData.m_variant = 0; itemDrop.m_itemData.m_shared.m_variants = 1; RenderedIcons.Add(prefabName); } } private static void TryApplyRenderedPieceIcon(GameObject prefab, Piece piece, ref Sprite? storedIcon) { if (RenderedIcons.Contains("FineDining_Icebox")) { Sprite val = AliveOrNull(storedIcon); if (val != null) { piece.m_icon = val; } return; } Sprite val2 = RenderGeneratedIcon(prefab, "FineDining_Icebox_Icon"); if (val2 != null) { storedIcon = val2; piece.m_icon = val2; RenderedIcons.Add("FineDining_Icebox"); } } private static Sprite? RenderGeneratedIcon(GameObject prefab, string iconName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if ((int)SystemInfo.graphicsDeviceType == 4) { return null; } try { Sprite val = RenderManager.Instance.Render(new RenderRequest(prefab) { Width = 128, Height = 128, Rotation = Quaternion.Euler(23f, 51f, 25.8f), ParticleSimulationTime = -1f, UseCache = false }); if (val != null) { ((Object)val).name = iconName; } return val; } catch (Exception ex) { LogProblemOnce("icon-render:" + iconName, "Could not render icon '" + iconName + "' through Jotunn; using the source icon instead: " + ex.Message); return null; } } private static Material? ResolveMaterial(string materialName, ref Material? cached) { cached = AliveOrNull(cached); if (cached != null) { return cached; } cached = AliveOrNull(Cache.GetPrefab(materialName)); if (cached != null) { return cached; } string normalizedTarget = NormalizeMaterialName(materialName); cached = Cache.GetPrefabs(typeof(Material)).Values.OfType().Select(AliveOrNull).FirstOrDefault((Func)((Material material) => material != null && string.Equals(NormalizeMaterialName(((Object)material).name), normalizedTarget, StringComparison.OrdinalIgnoreCase))); return cached; } private static void ApplyMaterialOverride(GameObject prefab, Material material) { Renderer[] componentsInChildren = prefab.GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (val != null && !((object)val).GetType().Name.Equals("ParticleSystemRenderer", StringComparison.Ordinal)) { Material[] sharedMaterials = val.sharedMaterials; if (sharedMaterials.Length != 0 && !sharedMaterials.All((Material existing) => existing == material)) { val.sharedMaterials = Enumerable.Repeat(material, sharedMaterials.Length).ToArray(); } } } } private static string NormalizeMaterialName(string? materialName) { return (materialName ?? string.Empty).Replace(" (Instance)", string.Empty).Trim(); } private static T? AliveOrNull(T? value) where T : Object { if (value == null || !((Object)(object)value != (Object)null)) { return default(T); } return value; } internal static void LogProblemOnce(string key, string message) { if (ReportedProblems.Add("warning:" + key)) { FineDiningPlugin.Log.LogWarning((object)message); } } private static void LogInfoOnce(string key, string message) { if (ReportedProblems.Add("info:" + key)) { FineDiningPlugin.Log.LogInfo((object)message); } } internal static void LogDebugOnce(string key, string message) { if (ReportedProblems.Add("debug:" + key)) { FineDiningPlugin.Log.LogDebug((object)message); } } } internal sealed class IceboxLimitSnapshot { internal IReadOnlyDictionary Overrides { get; } internal IceboxLimitSnapshot(IReadOnlyDictionary overrides) { Overrides = overrides; } internal int GetLimit(string accountId, int defaultLimit) { string key = IceboxSubsystem.NormalizeAccountId(accountId); if (!Overrides.TryGetValue(key, out var value)) { return defaultLimit; } return value; } } internal static class IceboxLimitPolicy { private sealed class IceboxLimitYaml { public Dictionary? Overrides { get; set; } } internal const string FileName = "Icebox.yml"; private static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(1.0); private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).WithDuplicateKeyChecking().Build(); private static readonly IceboxLimitSnapshot EmptySnapshot = new IceboxLimitSnapshot(new Dictionary(StringComparer.Ordinal)); private static IceboxLimitSnapshot _current = EmptySnapshot; private static DateTime _nextPollUtc = DateTime.MinValue; private static DateTime _lastProcessedWriteUtc = DateTime.MinValue; private static long _lastProcessedLength = -1L; private static bool _initialized; private static bool _authorityWasServer; internal static IceboxLimitSnapshot Current => _current; private static string DirectoryPath => Path.Combine(Paths.ConfigPath, "FineDining"); internal static string FilePath => Path.Combine(DirectoryPath, "Icebox.yml"); internal static void Initialize() { if (!_initialized) { _initialized = true; _current = EmptySnapshot; _nextPollUtc = DateTime.MinValue; _lastProcessedWriteUtc = DateTime.MinValue; _lastProcessedLength = -1L; _authorityWasServer = false; } } internal static void Tick() { if (!_initialized) { return; } if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()) { _authorityWasServer = false; return; } DateTime utcNow = DateTime.UtcNow; bool flag = !_authorityWasServer; _authorityWasServer = true; if (flag || !(utcNow < _nextPollUtc)) { _nextPollUtc = utcNow.Add(PollInterval); EnsureFileExists(); ReloadIfChanged(flag); } } internal static void Shutdown() { _initialized = false; _authorityWasServer = false; _current = EmptySnapshot; _nextPollUtc = DateTime.MinValue; _lastProcessedWriteUtc = DateTime.MinValue; _lastProcessedLength = -1L; } private static void EnsureFileExists() { if (File.Exists(FilePath)) { return; } try { Directory.CreateDirectory(DirectoryPath); File.WriteAllText(FilePath, "# FineDining per-Steam64 Icebox limit overrides (server only).\n# The synchronized Icebox Default Placement Limit config applies to unlisted accounts.\n# This file accepts only overrides; the legacy defaultLimit field is not supported.\n# 0: deny placement, -1: unlimited, positive: maximum count.\n# Quote Steam64 ids so YAML always treats them as strings.\noverrides: {}\n# overrides:\n# \"76561198000000000\": 6\n# \"76561198000000001\": -1\n"); FineDiningPlugin.Log.LogInfo((object)("Created server-only Icebox limit policy '" + FilePath + "'.")); } catch (Exception ex) { FineDiningPlugin.Log.LogWarning((object)("Could not create Icebox limit policy '" + FilePath + "': " + ex.GetBaseException().Message)); } } private static void ReloadIfChanged(bool force) { if (!File.Exists(FilePath)) { return; } DateTime lastWriteTimeUtc; long length; string yaml; try { FileInfo fileInfo = new FileInfo(FilePath); lastWriteTimeUtc = fileInfo.LastWriteTimeUtc; length = fileInfo.Length; if (!force && lastWriteTimeUtc == _lastProcessedWriteUtc && length == _lastProcessedLength) { return; } yaml = File.ReadAllText(FilePath); } catch (Exception ex) { FineDiningPlugin.Log.LogWarning((object)("Could not read Icebox limit policy '" + FilePath + "'; keeping the last-known-good policy. " + ex.GetBaseException().Message)); return; } _lastProcessedWriteUtc = lastWriteTimeUtc; _lastProcessedLength = length; if (!TryParse(yaml, out IceboxLimitSnapshot snapshot, out string error)) { FineDiningPlugin.Log.LogError((object)("Could not parse Icebox limit policy '" + FilePath + "'; keeping the last-known-good policy. " + error)); return; } _current = snapshot; FineDiningPlugin.Log.LogInfo((object)$"Applied Icebox limit policy: overrides={snapshot.Overrides.Count}."); } private static bool TryParse(string yaml, out IceboxLimitSnapshot? snapshot, out string error) { snapshot = null; error = string.Empty; try { if (string.IsNullOrWhiteSpace(yaml)) { throw new InvalidDataException("The document cannot be empty."); } IceboxLimitYaml iceboxLimitYaml = Deserializer.Deserialize(yaml) ?? throw new InvalidDataException("The document cannot be null."); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (iceboxLimitYaml.Overrides != null) { foreach (KeyValuePair @override in iceboxLimitYaml.Overrides) { string text = IceboxSubsystem.NormalizeAccountId(@override.Key); if (!ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result == 0L) { throw new InvalidDataException("overrides key '" + @override.Key + "' must be a quoted, non-zero Steam64 id."); } ValidateLimit(@override.Value, "overrides['" + @override.Key + "']"); if (dictionary.ContainsKey(text)) { throw new InvalidDataException("overrides contains duplicate normalized Steam64 id '" + text + "'."); } dictionary.Add(text, @override.Value); } } snapshot = new IceboxLimitSnapshot(dictionary); return true; } catch (Exception ex) { error = ex.GetBaseException().Message; return false; } } private static void ValidateLimit(int limit, string field) { if (limit < -1) { throw new InvalidDataException(field + " must be -1, 0, or a positive integer."); } } } internal static class IceboxMapPins { private const string RequestPinsRpc = "FineDining_RequestIceboxPins"; private const string ReceiveSnapshotRpc = "FineDining_ReceiveIceboxPins"; private const string InvalidatePinsRpc = "FineDining_InvalidateIceboxPins"; private const int MaxSnapshotEntries = 8192; private static readonly TimeSpan RequestRetryInterval = TimeSpan.FromSeconds(3.0); private static readonly TimeSpan MinimumServerRequestInterval = TimeSpan.FromMilliseconds(250.0); private static readonly TimeSpan ServerRequestHistoryLifetime = TimeSpan.FromSeconds(30.0); private static readonly Dictionary LocalSnapshot = new Dictionary(); private static readonly Dictionary LocalPins = new Dictionary(); private static readonly Dictionary LastServerRequestUtc = new Dictionary(); private static readonly List ScratchPeerIds = new List(); private static ZRoutedRpc? _boundRoutedRpc; private static Minimap? _boundMinimap; private static Minimap? _pinTypeMinimap; private static PinType _pinType = (PinType)3; private static int _lastRevision; private static int _lastBroadcastRevision; private static int _nextRequestId; private static int _pendingRequestId; private static DateTime _lastRequestUtc = DateTime.MinValue; private static DateTime _nextServerRequestPruneUtc = DateTime.MinValue; private static bool _snapshotReady; private static bool _renderDirty; private static bool _initialized; private static MethodInfo? _addPinMethod; private static object? _defaultPinAuthor; internal static void Initialize() { if (!_initialized) { _initialized = true; ResetSession(); } } internal static void Shutdown() { ResetSession(); _initialized = false; } internal static void ResetSession() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) ClearLocalPins(clearSnapshot: true); _boundRoutedRpc = null; _boundMinimap = null; _pinTypeMinimap = null; _pinType = (PinType)3; _lastRevision = 0; _lastBroadcastRevision = 0; _nextRequestId = 0; _pendingRequestId = 0; _lastRequestUtc = DateTime.MinValue; _nextServerRequestPruneUtc = DateTime.MinValue; _snapshotReady = false; _renderDirty = false; LastServerRequestUtc.Clear(); ScratchPeerIds.Clear(); } internal static void EnsureRpcBindings() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && _boundRoutedRpc != instance) { instance.Register("FineDining_RequestIceboxPins", (Action)HandleRequestPinsRpc); instance.Register("FineDining_ReceiveIceboxPins", (Action)HandleReceiveSnapshotRpc); instance.Register("FineDining_InvalidateIceboxPins", (Action)HandleInvalidatePinsRpc); _boundRoutedRpc = instance; } } internal static void Tick() { if (!_initialized) { return; } EnsureRpcBindings(); BroadcastAuthoritativeInvalidation(); if (!IceboxSubsystem.ShowMapPins) { DisablePins(); } else if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)ZNet.instance == (Object)null) && !((Object)(object)Minimap.instance == (Object)null)) { if (ZNet.instance.IsServer()) { RefreshLocalHostSnapshot(); } else { RefreshRemoteSnapshot(); } if (_boundMinimap != Minimap.instance) { ClearLocalPins(clearSnapshot: false); _boundMinimap = Minimap.instance; _renderDirty = true; } if (_snapshotReady && (_renderDirty || LocalPins.Count != LocalSnapshot.Count)) { ApplySnapshotToMinimap(_boundMinimap); _renderDirty = false; } } } internal static void HandleConfigChanged() { if (!IceboxSubsystem.ShowMapPins) { DisablePins(); } else { InvalidateSnapshot(); } } internal static void ApplyPinScale(Minimap? minimap) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 if ((Object)(object)minimap == (Object)null || _boundMinimap != minimap) { return; } float num = (((int)minimap.m_mode == 2) ? minimap.m_pinSizeLarge : minimap.m_pinSizeSmall); foreach (PinData value in LocalPins.Values) { RectTransform val = value?.m_uiElement; if (!((Object)(object)val == (Object)null)) { val.SetSizeWithCurrentAnchors((Axis)0, num); val.SetSizeWithCurrentAnchors((Axis)1, num); } } } internal static void HandleMinimapDestroyed(Minimap minimap) { if (_boundMinimap == minimap) { LocalPins.Clear(); _boundMinimap = null; if (_pinTypeMinimap == minimap) { _pinTypeMinimap = null; } _renderDirty = true; } } private static void BroadcastAuthoritativeInvalidation() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !IceboxQuotaService.IsAuthoritativeIndexReady) { return; } int revision = IceboxQuotaService.Revision; if (revision != _lastBroadcastRevision) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "FineDining_InvalidateIceboxPins", new object[1] { (object)new ZPackage() }); _lastBroadcastRevision = revision; } } } private static void RefreshLocalHostSnapshot() { if (!IceboxQuotaService.IsAuthoritativeIndexReady) { return; } int revision = IceboxQuotaService.Revision; if (!_snapshotReady || revision != _lastRevision) { string text = IceboxQuotaService.ResolveLocalAccountId(); if (text.Length != 0) { ReplaceLocalSnapshot(IceboxQuotaService.GetEntriesForAccount(text)); _lastRevision = revision; _snapshotReady = true; _pendingRequestId = 0; _renderDirty = true; } } } private static void RefreshRemoteSnapshot() { DateTime utcNow = DateTime.UtcNow; if ((!_snapshotReady || _pendingRequestId != 0) && !(utcNow - _lastRequestUtc < RequestRetryInterval)) { SendRemoteSnapshotRequest(); } } private static void SendRemoteSnapshotRequest() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown EnsureRpcBindings(); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && !((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer()) { int num = (_pendingRequestId = (_nextRequestId = ((_nextRequestId == int.MaxValue) ? 1 : (_nextRequestId + 1)))); _lastRequestUtc = DateTime.UtcNow; ZPackage val = new ZPackage(); val.Write(num); instance.InvokeRoutedRPC(instance.GetServerPeerID(), "FineDining_RequestIceboxPins", new object[1] { val }); } } private static void HandleRequestPinsRpc(long senderUid, ZPackage package) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || package == null) { return; } int num; try { num = package.ReadInt(); } catch { return; } if (num <= 0) { return; } DateTime utcNow = DateTime.UtcNow; PruneServerRequestHistory(utcNow); if (!LastServerRequestUtc.TryGetValue(senderUid, out var value) || !(utcNow - value < MinimumServerRequestInterval)) { LastServerRequestUtc[senderUid] = utcNow; if (IceboxQuotaService.TryResolveSenderIdentity(senderUid, out long _, out string accountId)) { SendFullSnapshot(senderUid, num, accountId); } } } private static void SendFullSnapshot(long receiverUid, int requestId, string accountId) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { IReadOnlyList entriesForAccount = IceboxQuotaService.GetEntriesForAccount(accountId); ZPackage val = new ZPackage(); val.Write(requestId); val.Write(entriesForAccount.Count); for (int i = 0; i < entriesForAccount.Count; i++) { val.Write(entriesForAccount[i].ZdoId); val.Write(entriesForAccount[i].Position); } instance.InvokeRoutedRPC(receiverUid, "FineDining_ReceiveIceboxPins", new object[1] { val }); } } private static void HandleReceiveSnapshotRpc(long senderUid, ZPackage package) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_00ab: Unknown result type (might be due to invalid IL or missing references) if (!IceboxQuotaService.IsAuthoritativeServerSender(senderUid) || package == null || !IceboxSubsystem.ShowMapPins) { return; } int num; int num2; try { num = package.ReadInt(); num2 = package.ReadInt(); } catch { return; } if (num2 < 0 || num2 > 8192 || _pendingRequestId == 0 || num != _pendingRequestId) { return; } Dictionary dictionary = new Dictionary(); try { for (int i = 0; i < num2; i++) { ZDOID val = package.ReadZDOID(); Vector3 position = package.ReadVector3(); if (!((ZDOID)(ref val)).IsNone()) { dictionary[val] = new IceboxPinSnapshotEntry(val, position); } } } catch { return; } LocalSnapshot.Clear(); foreach (KeyValuePair item in dictionary) { LocalSnapshot[item.Key] = item.Value; } _pendingRequestId = 0; _snapshotReady = true; _renderDirty = true; } private static void HandleInvalidatePinsRpc(long senderUid, ZPackage package) { if (IceboxQuotaService.IsAuthoritativeServerSender(senderUid) && package != null && IceboxSubsystem.ShowMapPins) { InvalidateSnapshot(); } } private static void PruneServerRequestHistory(DateTime nowUtc) { if (nowUtc < _nextServerRequestPruneUtc) { return; } _nextServerRequestPruneUtc = nowUtc.Add(ServerRequestHistoryLifetime); ScratchPeerIds.Clear(); foreach (KeyValuePair item in LastServerRequestUtc) { if (!(nowUtc - item.Value >= ServerRequestHistoryLifetime)) { ZNet instance = ZNet.instance; if (((instance != null) ? instance.GetPeer(item.Key) : null) != null) { continue; } } ScratchPeerIds.Add(item.Key); } for (int i = 0; i < ScratchPeerIds.Count; i++) { LastServerRequestUtc.Remove(ScratchPeerIds[i]); } } private static void ReplaceLocalSnapshot(IReadOnlyList entries) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) LocalSnapshot.Clear(); for (int i = 0; i < entries.Count; i++) { IceboxPinSnapshotEntry value = entries[i]; LocalSnapshot[value.ZdoId] = value; } } private static void ApplySnapshotToMinimap(Minimap? minimap) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: 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_009e: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)minimap == (Object)null) { return; } Sprite val = ResolveIceboxIcon(); EnsureCustomPinType(minimap, val); HashSet hashSet = new HashSet(); foreach (IceboxPinSnapshotEntry value2 in LocalSnapshot.Values) { hashSet.Add(value2.ZdoId); if (!LocalPins.TryGetValue(value2.ZdoId, out PinData value) || value == null || minimap.m_pins == null || !minimap.m_pins.Contains(value)) { value = TryAddPin(minimap, value2.Position, _pinType); if (value == null) { continue; } LocalPins[value2.ZdoId] = value; } if (value == null) { continue; } value.m_pos = value2.Position; value.m_doubleSize = true; if ((Object)(object)val != (Object)null) { value.m_icon = val; if ((Object)(object)value.m_iconElement != (Object)null) { value.m_iconElement.sprite = val; } } } List list = new List(); foreach (ZDOID key in LocalPins.Keys) { if (!hashSet.Contains(key)) { list.Add(key); } } for (int i = 0; i < list.Count; i++) { RemoveLocalPin(list[i]); } minimap.m_pinUpdateRequired = true; } private static void EnsureCustomPinType(Minimap minimap, Sprite? icon) { //IL_005b: 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_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_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) if (minimap.m_visibleIconTypes == null || minimap.m_icons == null) { return; } if (_pinTypeMinimap != minimap || (int)_pinType < 0 || (int)_pinType >= minimap.m_visibleIconTypes.Length) { int num = minimap.m_visibleIconTypes.Length; bool[] array = new bool[num + 1]; Array.Copy(minimap.m_visibleIconTypes, array, num); array[num] = true; minimap.m_visibleIconTypes = array; _pinType = (PinType)num; _pinTypeMinimap = minimap; minimap.m_icons.Add(new SpriteData { m_name = _pinType, m_icon = icon }); return; } for (int i = 0; i < minimap.m_icons.Count; i++) { SpriteData val = minimap.m_icons[i]; if (val.m_name == _pinType) { if ((Object)(object)icon != (Object)null && (Object)(object)val.m_icon != (Object)(object)icon) { val.m_icon = icon; minimap.m_icons[i] = val; } return; } } minimap.m_icons.Add(new SpriteData { m_name = _pinType, m_icon = icon }); } private static Sprite? ResolveIceboxIcon() { Sprite iceboxIcon = GeneratedPrefabRegistry.GetIceboxIcon(); if ((Object)(object)iceboxIcon != (Object)null) { return iceboxIcon; } ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.GetPrefab(IceboxSubsystem.PrefabHash) : null); Piece val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)val2?.m_icon != (Object)null) { return val2.m_icon; } ZNetScene instance2 = ZNetScene.instance; GameObject val3 = ((instance2 != null) ? instance2.GetPrefab("piece_chest") : null); if (!((Object)(object)val3 != (Object)null)) { return null; } return val3.GetComponent()?.m_icon; } private static void RemoveLocalPin(ZDOID zdoId) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (!LocalPins.TryGetValue(zdoId, out PinData value)) { return; } LocalPins.Remove(zdoId); if (value != null) { Minimap? boundMinimap = _boundMinimap; if (boundMinimap != null) { boundMinimap.RemovePin(value); } } } private static void ClearLocalPins(bool clearSnapshot) { if ((Object)(object)_boundMinimap != (Object)null) { foreach (PinData value in LocalPins.Values) { if (value != null) { _boundMinimap.RemovePin(value); } } } LocalPins.Clear(); if (clearSnapshot) { LocalSnapshot.Clear(); _lastRevision = 0; } } private static void DisablePins() { if (LocalPins.Count > 0 || LocalSnapshot.Count > 0) { ClearLocalPins(clearSnapshot: true); } else { _lastRevision = 0; } InvalidateSnapshot(); _renderDirty = false; } private static void InvalidateSnapshot() { _snapshotReady = false; _pendingRequestId = 0; _lastRequestUtc = DateTime.MinValue; } private static PinData? TryAddPin(Minimap minimap, Vector3 position, PinType pinType) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) try { if (_addPinMethod == null) { MethodInfo[] methods = typeof(Minimap).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "AddPin" && methodInfo.GetParameters().Length == 7) { _addPinMethod = methodInfo; _defaultPinAuthor = Activator.CreateInstance(methodInfo.GetParameters()[6].ParameterType); break; } } } object? obj = _addPinMethod?.Invoke(minimap, new object[7] { position, pinType, string.Empty, false, false, 0L, _defaultPinAuthor }); return (PinData?)((obj is PinData) ? obj : null); } catch (Exception ex) { FineDiningPlugin.Log.LogWarning((object)("Could not add an Icebox minimap pin: " + ex.GetBaseException().Message)); return null; } } } [HarmonyPatch(typeof(Minimap), "UpdatePins")] internal static class MinimapUpdatePinsIceboxMapPinsPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Minimap __instance) { IceboxMapPins.ApplyPinScale(__instance); } } internal readonly struct IceboxPinSnapshotEntry { internal ZDOID ZdoId { get; } internal Vector3 Position { get; } internal IceboxPinSnapshotEntry(ZDOID zdoId, Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) ZdoId = zdoId; Position = position; } } internal static class IceboxQuotaService { private sealed class IndexedIcebox { internal ZDOID ZdoId; internal long CreatorPlayerId; internal string OwnerAccountId; internal Vector3 Position; internal IndexedIcebox(ZDOID zdoId, long creatorPlayerId, string ownerAccountId, Vector3 position) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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_001e: Unknown result type (might be due to invalid IL or missing references) ZdoId = zdoId; CreatorPlayerId = creatorPlayerId; OwnerAccountId = ownerAccountId; Position = position; } } private sealed class PendingPlacement { internal long SenderUid; internal DateTime FirstSeenUtc; internal bool RefundEligible; internal bool HasPlacementNotice; internal PendingPlacement(long senderUid, DateTime firstSeenUtc, bool refundEligible, bool hasPlacementNotice) { SenderUid = senderUid; FirstSeenUtc = firstSeenUtc; RefundEligible = refundEligible; HasPlacementNotice = hasPlacementNotice; } } private const string PlacementNoticeRpc = "FineDining_IceboxPlacementNotice"; private const string PlacementRejectedRpc = "FineDining_IceboxPlacementRejected"; internal const string PlacementLimitMessageToken = "$finedining_icebox_limit_reached"; private const int MaxPendingPerSender = 64; private static readonly TimeSpan ScanInterval = TimeSpan.FromSeconds(30.0); private static readonly TimeSpan PlacementSettleDelay = TimeSpan.FromMilliseconds(250.0); private static readonly TimeSpan PendingReplicationTimeout = TimeSpan.FromSeconds(20.0); private static readonly Dictionary Indexed = new Dictionary(); private static readonly Dictionary CountsByAccount = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary Pending = new Dictionary(); private static readonly HashSet LocallyNotifiedPlacements = new HashSet(); private static readonly HashSet RejectedPlacements = new HashSet(); private static readonly List ScanBuffer = new List(); private static readonly HashSet ScanSeenIds = new HashSet(); private static readonly HashSet ReconcileBaselineIds = new HashSet(); private static readonly List ScratchIds = new List(); private static ZDOMan? _trackedZdoMan; private static ZRoutedRpc? _boundRoutedRpc; private static DateTime _nextScanUtc = DateTime.MinValue; private static int _reconcileScanIndex; private static bool _reconcileScanInProgress; private static bool _worldScanComplete; private static int _revision; private static bool _initialized; private static readonly FieldInfo? PlayerInfoUserInfoField = typeof(PlayerInfo).GetField("m_userInfo", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static int Revision => _revision; internal static bool IsAuthoritativeIndexReady { get { if (_worldScanComplete && (Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } internal static void Initialize() { if (!_initialized) { _initialized = true; ResetSession(); } } internal static void Shutdown() { ResetSession(); _initialized = false; } internal static void ResetSession() { if (_trackedZdoMan != null) { ZDOMan? trackedZdoMan = _trackedZdoMan; trackedZdoMan.m_onZDODestroyed = (Action)Delegate.Remove(trackedZdoMan.m_onZDODestroyed, new Action(HandleZdoDestroyed)); _trackedZdoMan = null; } _boundRoutedRpc = null; _nextScanUtc = DateTime.MinValue; _reconcileScanIndex = 0; _reconcileScanInProgress = false; _worldScanComplete = false; _revision = 0; Indexed.Clear(); CountsByAccount.Clear(); Pending.Clear(); LocallyNotifiedPlacements.Clear(); RejectedPlacements.Clear(); ScanBuffer.Clear(); ScanSeenIds.Clear(); ReconcileBaselineIds.Clear(); ScratchIds.Clear(); } internal static void EnsureRpcBindings() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && _boundRoutedRpc != instance) { instance.Register("FineDining_IceboxPlacementNotice", (Action)HandlePlacementNoticeRpc); instance.Register("FineDining_IceboxPlacementRejected", (Action)HandlePlacementRejectedRpc); _boundRoutedRpc = instance; } } internal static void Tick() { if (!_initialized) { return; } EnsureRpcBindings(); if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !_worldScanComplete) { return; } ZDOMan instance = ZDOMan.instance; if (instance != null) { EnsureTrackedZdoMan(instance); DateTime utcNow = DateTime.UtcNow; if (!_reconcileScanInProgress && utcNow >= _nextScanUtc) { BeginReconcileScan(); } if (_reconcileScanInProgress) { AdvanceReconcileScan(instance, utcNow); } ProcessPendingPlacements(utcNow); } } internal static void OnAuthoritativeWorldLoaded(ZDOMan zdoMan) { if (zdoMan == null || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } if (_trackedZdoMan != null && _trackedZdoMan != zdoMan) { ZDOMan? trackedZdoMan = _trackedZdoMan; trackedZdoMan.m_onZDODestroyed = (Action)Delegate.Remove(trackedZdoMan.m_onZDODestroyed, new Action(HandleZdoDestroyed)); } Indexed.Clear(); CountsByAccount.Clear(); Pending.Clear(); RejectedPlacements.Clear(); _revision = 0; EnsureTrackedZdoMan(zdoMan); PrepareScan(zdoMan); for (int i = 0; i < ScanBuffer.Count; i++) { ZDO zdo = ScanBuffer[i]; if (IceboxSubsystem.IsIcebox(zdo)) { AddGrandfatheredIcebox(zdo); } } _worldScanComplete = true; _reconcileScanIndex = 0; _reconcileScanInProgress = false; _nextScanUtc = DateTime.UtcNow.Add(ScanInterval); BumpRevision(); FineDiningPlugin.Log.LogInfo((object)$"Indexed {Indexed.Count} existing Icebox(es); existing boxes are grandfathered against placement limits."); } internal static void NotifyLocallyPlacedIcebox(Piece piece) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) if (!IceboxSubsystem.IsIcebox(piece)) { return; } Player localPlayer = Player.m_localPlayer; ZNetView nview = piece.m_nview; if ((Object)(object)localPlayer == (Object)null || (Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner() || piece.GetCreator() == 0L || piece.GetCreator() != localPlayer.GetPlayerID()) { return; } ZDO zDO = nview.GetZDO(); if (IceboxSubsystem.IsIcebox(zDO) && LocallyNotifiedPlacements.Add(zDO.m_uid)) { EnsureRpcBindings(); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { LocallyNotifiedPlacements.Remove(zDO.m_uid); return; } ZPackage val = new ZPackage(); val.Write(zDO.m_uid); bool flag = localPlayer.NoCostCheat() || ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey())); val.Write(!flag); instance.InvokeRoutedRPC(instance.GetServerPeerID(), "FineDining_IceboxPlacementNotice", new object[1] { val }); } } internal static IReadOnlyList GetEntriesForAccount(string accountId) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) string text = IceboxSubsystem.NormalizeAccountId(accountId); if (text.Length == 0 || Indexed.Count == 0) { return Array.Empty(); } List list = new List(); foreach (IndexedIcebox value in Indexed.Values) { if (AccountIdsEqual(value.OwnerAccountId, text)) { list.Add(new IceboxPinSnapshotEntry(value.ZdoId, value.Position)); } } list.Sort(delegate(IceboxPinSnapshotEntry left, IceboxPinSnapshotEntry right) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) ZDOID zdoId = left.ZdoId; long userID = ((ZDOID)(ref zdoId)).UserID; zdoId = right.ZdoId; int num = userID.CompareTo(((ZDOID)(ref zdoId)).UserID); if (num == 0) { zdoId = left.ZdoId; uint iD = ((ZDOID)(ref zdoId)).ID; zdoId = right.ZdoId; return iD.CompareTo(((ZDOID)(ref zdoId)).ID); } return num; }); return list; } internal static bool TryResolveSenderIdentity(long senderUid, out long playerId, out string accountId) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) playerId = 0L; accountId = string.Empty; if (senderUid == 0L || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && ((Character)localPlayer).GetOwner() == senderUid) { playerId = localPlayer.GetPlayerID(); accountId = ResolveLocalAccountId(); if (playerId != 0L && accountId.Length > 0) { return true; } } ZNetPeer peer = ZNet.instance.GetPeer(senderUid); if (peer == null || ((ZDOID)(ref peer.m_characterID)).IsNone()) { return false; } ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(peer.m_characterID) : null); playerId = ((val != null) ? val.GetLong(ZDOVars.s_playerID, 0L) : 0); accountId = ResolveAccountIdForCharacter(peer.m_characterID); if (playerId != 0L) { return accountId.Length > 0; } return false; } internal static bool TryResolveOnlineCreatorIdentity(long creatorPlayerId, out long senderUid, out string accountId) { //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_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) senderUid = 0L; accountId = string.Empty; if (creatorPlayerId == 0L || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && localPlayer.GetPlayerID() == creatorPlayerId) { senderUid = ((Character)localPlayer).GetOwner(); accountId = ResolveLocalAccountId(); if (senderUid != 0L && accountId.Length > 0) { return true; } } List players = ZNet.instance.m_players; if (players == null) { return false; } for (int i = 0; i < players.Count; i++) { PlayerInfo val = players[i]; ZDOMan instance = ZDOMan.instance; ZDO obj = ((instance != null) ? instance.GetZDO(val.m_characterID) : null); if (((obj != null) ? obj.GetLong(ZDOVars.s_playerID, 0L) : 0) == creatorPlayerId) { accountId = NormalizePlayerInfoAccount(val); senderUid = FindPeerByCharacter(val.m_characterID)?.m_uid ?? ((ZDOID)(ref val.m_characterID)).UserID; if (senderUid != 0L) { return accountId.Length > 0; } return false; } } return false; } internal static string ResolveLocalAccountId() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return string.Empty; } ZDOID characterID = instance.m_characterID; List players = instance.m_players; if (players != null) { for (int i = 0; i < players.Count; i++) { if (players[i].m_characterID == characterID) { return NormalizePlayerInfoAccount(players[i]); } } } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { int num = 0; while (players != null && num < players.Count) { ZDOMan instance2 = ZDOMan.instance; ZDO obj = ((instance2 != null) ? instance2.GetZDO(players[num].m_characterID) : null); if (((obj != null) ? obj.GetLong(ZDOVars.s_playerID, 0L) : 0) == localPlayer.GetPlayerID()) { return NormalizePlayerInfoAccount(players[num]); } num++; } } return string.Empty; } internal static bool IsAuthoritativeServerSender(long senderUid) { ZRoutedRpc instance = ZRoutedRpc.instance; if (senderUid != 0L && instance != null) { return senderUid == instance.GetServerPeerID(); } return false; } private static void HandlePlacementNoticeRpc(long senderUid, ZPackage package) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || package == null) { return; } ZDOID val; bool flag; try { val = package.ReadZDOID(); flag = package.ReadBool(); } catch { return; } if (((ZDOID)(ref val)).IsNone() || ((ZDOID)(ref val)).UserID != senderUid || Indexed.ContainsKey(val) || RejectedPlacements.Contains(val)) { return; } int num = 0; foreach (PendingPlacement value2 in Pending.Values) { if (value2.SenderUid == senderUid) { num++; } } if (num >= 64) { return; } if (Pending.TryGetValue(val, out PendingPlacement value)) { if (value.SenderUid == 0L) { value.SenderUid = senderUid; } if (!value.HasPlacementNotice) { value.RefundEligible = flag; value.HasPlacementNotice = true; } else { value.RefundEligible &= flag; } } else { Pending[val] = new PendingPlacement(senderUid, DateTime.UtcNow, flag, hasPlacementNotice: true); } } private static void HandlePlacementRejectedRpc(long senderUid, ZPackage package) { if (IsAuthoritativeServerSender(senderUid) && package != null) { int limit; try { limit = package.ReadInt(); } catch { return; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { ((Character)localPlayer).Message((MessageType)2, FormatPlacementLimitMessage(limit), 0, (Sprite)null); } } } private static void ProcessPendingPlacements(DateTime nowUtc) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) if (Pending.Count == 0) { return; } ScratchIds.Clear(); ScratchIds.AddRange(Pending.Keys); for (int i = 0; i < ScratchIds.Count; i++) { ZDOID val = ScratchIds[i]; if (!Pending.TryGetValue(val, out PendingPlacement value) || nowUtc - value.FirstSeenUtc < PlacementSettleDelay) { continue; } if (Indexed.ContainsKey(val) || RejectedPlacements.Contains(val)) { Pending.Remove(val); continue; } ZDOMan instance = ZDOMan.instance; ZDO val2 = ((instance != null) ? instance.GetZDO(val) : null); if (val2 == null || !val2.IsValid()) { if (nowUtc - value.FirstSeenUtc >= PendingReplicationTimeout) { Pending.Remove(val); } continue; } if (!IceboxSubsystem.IsIcebox(val2)) { Pending.Remove(val); continue; } long num = val2.GetLong(ZDOVars.s_creator, 0L); long senderUid = value.SenderUid; bool flag; long playerId; string accountId; if (senderUid != 0L) { flag = TryResolveSenderIdentity(senderUid, out playerId, out accountId); } else { flag = TryResolveOnlineCreatorIdentity(num, out senderUid, out accountId); playerId = num; } if (!flag || num == 0L) { if (!(nowUtc - value.FirstSeenUtc < PendingReplicationTimeout)) { RejectPlacement(val2, senderUid, 0, showMessage: false, value.RefundEligible); } continue; } long owner = val2.GetOwner(); long sessionID = ZDOMan.GetSessionID(); bool flag2 = owner == senderUid || owner == sessionID; if (num != playerId || !flag2) { RejectPlacement(val2, senderUid, 0, showMessage: false, value.RefundEligible); continue; } string text = IceboxSubsystem.NormalizeAccountId(accountId); AttributeUnresolvedGrandfatheredIceboxes(num, text); int limit = IceboxLimitPolicy.Current.GetLimit(text, IceboxSubsystem.PlacementLimit); int value2; int num2 = (CountsByAccount.TryGetValue(text, out value2) ? value2 : 0); if (limit == 0 || (limit > 0 && num2 >= limit)) { RejectPlacement(val2, senderUid, limit, showMessage: true, value.RefundEligible); continue; } if (!string.Equals(val2.GetString("sighsorry.FineDining.IceboxOwnerAccountId", string.Empty), text, StringComparison.Ordinal)) { val2.Set("sighsorry.FineDining.IceboxOwnerAccountId", text); ZDOMan instance2 = ZDOMan.instance; if (instance2 != null) { instance2.ForceSendZDO(val2.m_uid); } } Pending.Remove(val); AddIndexed(new IndexedIcebox(val, num, text, val2.GetPosition()), updateRevision: true); } } private static void AddGrandfatheredIcebox(ZDO zdo) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) long creatorPlayerId = zdo.GetLong(ZDOVars.s_creator, 0L); string text = IceboxSubsystem.NormalizeAccountId(zdo.GetString("sighsorry.FineDining.IceboxOwnerAccountId", string.Empty)); if (text.Length == 0 && TryResolveOnlineCreatorIdentity(creatorPlayerId, out long _, out string accountId)) { text = IceboxSubsystem.NormalizeAccountId(accountId); zdo.Set("sighsorry.FineDining.IceboxOwnerAccountId", text); ZDOMan instance = ZDOMan.instance; if (instance != null) { instance.ForceSendZDO(zdo.m_uid); } } AddIndexed(new IndexedIcebox(zdo.m_uid, creatorPlayerId, text, zdo.GetPosition()), updateRevision: false); } private static void AddIndexed(IndexedIcebox entry, bool updateRevision) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (!Indexed.ContainsKey(entry.ZdoId)) { Indexed[entry.ZdoId] = entry; IncrementAccountCount(entry.OwnerAccountId); if (updateRevision) { BumpRevision(); } } } private static void RemoveIndexed(ZDOID zdoId) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (!Indexed.TryGetValue(zdoId, out IndexedIcebox value)) { Pending.Remove(zdoId); return; } Indexed.Remove(zdoId); DecrementAccountCount(value.OwnerAccountId); Pending.Remove(zdoId); BumpRevision(); } private static void BeginReconcileScan() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) ScanBuffer.Clear(); ReconcileBaselineIds.Clear(); foreach (ZDOID key in Indexed.Keys) { ReconcileBaselineIds.Add(key); } _reconcileScanIndex = 0; _reconcileScanInProgress = true; } private static void AdvanceReconcileScan(ZDOMan zdoMan, DateTime nowUtc) { if (zdoMan.GetAllZDOsWithPrefabIterative("FineDining_Icebox", ScanBuffer, ref _reconcileScanIndex)) { _reconcileScanInProgress = false; _reconcileScanIndex = 0; _nextScanUtc = nowUtc.Add(ScanInterval); ReconcileAuthoritativeIndex(nowUtc); } } private static void ReconcileAuthoritativeIndex(DateTime nowUtc) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) ScanSeenIds.Clear(); for (int i = 0; i < ScanBuffer.Count; i++) { ZDO val = ScanBuffer[i]; if (!IceboxSubsystem.IsIcebox(val)) { continue; } ScanSeenIds.Add(val.m_uid); if (Indexed.TryGetValue(val.m_uid, out IndexedIcebox value)) { RepairIndexedMetadata(val, value); Vector3 position = val.GetPosition(); if (value.Position != position) { value.Position = position; BumpRevision(); } } else if (!RejectedPlacements.Contains(val.m_uid) && !Pending.ContainsKey(val.m_uid)) { long creatorPlayerId = val.GetLong(ZDOVars.s_creator, 0L); long senderUid = 0L; TryResolveOnlineCreatorIdentity(creatorPlayerId, out senderUid, out string _); Pending[val.m_uid] = new PendingPlacement(senderUid, nowUtc, refundEligible: false, hasPlacementNotice: false); } } ScratchIds.Clear(); foreach (ZDOID reconcileBaselineId in ReconcileBaselineIds) { if (!ScanSeenIds.Contains(reconcileBaselineId)) { ScratchIds.Add(reconcileBaselineId); } } for (int j = 0; j < ScratchIds.Count; j++) { RemoveIndexed(ScratchIds[j]); } ReconcileBaselineIds.Clear(); PruneRejectedPlacements(); } private static void RepairIndexedMetadata(ZDO zdo, IndexedIcebox indexed) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) if (indexed.OwnerAccountId.Length > 0) { if (!AccountIdsEqual(IceboxSubsystem.NormalizeAccountId(zdo.GetString("sighsorry.FineDining.IceboxOwnerAccountId", string.Empty)), indexed.OwnerAccountId)) { zdo.Set("sighsorry.FineDining.IceboxOwnerAccountId", indexed.OwnerAccountId); ZDOMan instance = ZDOMan.instance; if (instance != null) { instance.ForceSendZDO(zdo.m_uid); } } return; } long creatorPlayerId = zdo.GetLong(ZDOVars.s_creator, indexed.CreatorPlayerId); if (!TryResolveOnlineCreatorIdentity(creatorPlayerId, out long _, out string accountId)) { return; } string text = IceboxSubsystem.NormalizeAccountId(accountId); if (text.Length != 0) { indexed.CreatorPlayerId = creatorPlayerId; indexed.OwnerAccountId = text; IncrementAccountCount(text); zdo.Set("sighsorry.FineDining.IceboxOwnerAccountId", text); ZDOMan instance2 = ZDOMan.instance; if (instance2 != null) { instance2.ForceSendZDO(zdo.m_uid); } BumpRevision(); } } private static void AttributeUnresolvedGrandfatheredIceboxes(long creatorPlayerId, string accountId) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) string text = IceboxSubsystem.NormalizeAccountId(accountId); if (creatorPlayerId == 0L || text.Length == 0) { return; } foreach (IndexedIcebox value in Indexed.Values) { if (value.CreatorPlayerId != creatorPlayerId || value.OwnerAccountId.Length != 0) { continue; } ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(value.ZdoId) : null); if (val != null && val.IsValid() && IceboxSubsystem.IsIcebox(val)) { value.OwnerAccountId = text; IncrementAccountCount(text); val.Set("sighsorry.FineDining.IceboxOwnerAccountId", text); ZDOMan instance2 = ZDOMan.instance; if (instance2 != null) { instance2.ForceSendZDO(val.m_uid); } BumpRevision(); } } } private static void RejectPlacement(ZDO zdo, long receiverUid, int limit, bool showMessage, bool refundEligible) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) ZDOID uid = zdo.m_uid; Pending.Remove(uid); RemoveIndexed(uid); if (!RejectedPlacements.Add(uid)) { return; } SendPlacementRejected(receiverUid, limit, showMessage); if (refundEligible && !IsGlobalFreeBuildEnabled()) { RefundPlacementOnce(zdo); } if (!zdo.IsValid()) { return; } zdo.SetOwner(ZDOMan.GetSessionID()); ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.FindInstance(uid) : null); if ((Object)(object)val != (Object)null && (Object)(object)ZNetScene.instance != (Object)null) { ZNetScene.instance.Destroy(val); return; } ZDOMan instance2 = ZDOMan.instance; if (instance2 != null) { instance2.DestroyZDO(zdo); } } private static void RefundPlacementOnce(ZDO zdo) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) if (!zdo.IsValid() || zdo.GetBool("sighsorry.FineDining.IceboxLimitRefundProcessed", false)) { return; } zdo.Set("sighsorry.FineDining.IceboxLimitRefundProcessed", true); Requirement[] array = ResolveCurrentRequirements(zdo); Vector3 val = zdo.GetPosition() + Vector3.up; foreach (Requirement val2 in array) { ItemDrop resItem = val2.m_resItem; if (!((Object)(object)resItem == (Object)null) && val2.m_recover) { int num = val2.GetAmount(1); int val3 = Math.Max(1, resItem.m_itemData.m_shared.m_maxStackSize); while (num > 0) { int num2 = Math.Min(num, val3); num -= num2; ItemData obj = resItem.m_itemData.Clone(); obj.m_dropPrefab = ((Component)resItem).gameObject; obj.m_stack = num2; ItemDrop.DropItem(obj, num2, val, Quaternion.Euler(0f, Random.Range(0f, 360f), 0f)); } } } } private static Requirement[] ResolveCurrentRequirements(ZDO zdo) { string text = zdo.GetString("sighsorry.FineDining.IceboxPlacedRecipe", string.Empty); ObjectDB instance = ObjectDB.instance; if (text.Length == 0 || (Object)(object)instance == (Object)null) { return Array.Empty(); } if (!IceboxSubsystem.TryCreateRequirements(instance, text, out Requirement[] requirements) || !IceboxSubsystem.TryCreateRequirements(instance, IceboxSubsystem.Recipe, out Requirement[] requirements2)) { return Array.Empty(); } string text2 = IceboxSubsystem.SerializeRequirements(requirements); string text3 = IceboxSubsystem.SerializeRequirements(requirements2); if (!string.Equals(text2, text3, StringComparison.Ordinal)) { FineDiningPlugin.Log.LogWarning((object)("Skipped an Icebox placement refund because its stored recipe '" + text2 + "' did not match the server recipe '" + text3 + "'.")); return Array.Empty(); } return requirements; } private static void SendPlacementRejected(long receiverUid, int limit, bool showMessage) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown if (!showMessage || receiverUid == 0L) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && ((Character)localPlayer).GetOwner() == receiverUid) { ((Character)localPlayer).Message((MessageType)2, FormatPlacementLimitMessage(limit), 0, (Sprite)null); return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { ZPackage val = new ZPackage(); val.Write(limit); instance.InvokeRoutedRPC(receiverUid, "FineDining_IceboxPlacementRejected", new object[1] { val }); } } private static void EnsureTrackedZdoMan(ZDOMan zdoMan) { if (_trackedZdoMan != zdoMan) { if (_trackedZdoMan != null) { ZDOMan? trackedZdoMan = _trackedZdoMan; trackedZdoMan.m_onZDODestroyed = (Action)Delegate.Remove(trackedZdoMan.m_onZDODestroyed, new Action(HandleZdoDestroyed)); } _trackedZdoMan = zdoMan; ZDOMan? trackedZdoMan2 = _trackedZdoMan; trackedZdoMan2.m_onZDODestroyed = (Action)Delegate.Combine(trackedZdoMan2.m_onZDODestroyed, new Action(HandleZdoDestroyed)); } } private static void HandleZdoDestroyed(ZDO zdo) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (zdo != null) { RemoveIndexed(zdo.m_uid); Pending.Remove(zdo.m_uid); LocallyNotifiedPlacements.Remove(zdo.m_uid); RejectedPlacements.Remove(zdo.m_uid); } } private static void PruneRejectedPlacements() { //IL_0026: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if (RejectedPlacements.Count == 0) { return; } ScratchIds.Clear(); foreach (ZDOID rejectedPlacement in RejectedPlacements) { ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(rejectedPlacement) : null); if (val == null || !val.IsValid() || !IceboxSubsystem.IsIcebox(val)) { ScratchIds.Add(rejectedPlacement); } } for (int i = 0; i < ScratchIds.Count; i++) { RejectedPlacements.Remove(ScratchIds[i]); } } private static void PrepareScan(ZDOMan zdoMan) { ScanBuffer.Clear(); int num = 0; while (!zdoMan.GetAllZDOsWithPrefabIterative("FineDining_Icebox", ScanBuffer, ref num)) { } } private static string ResolveAccountIdForCharacter(ZDOID characterId) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_0035: Unknown result type (might be due to invalid IL or missing references) List list = ZNet.instance?.m_players; if (list == null) { return string.Empty; } for (int i = 0; i < list.Count; i++) { if (list[i].m_characterID == characterId) { return NormalizePlayerInfoAccount(list[i]); } } return string.Empty; } private static string NormalizePlayerInfoAccount(PlayerInfo playerInfo) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { object obj = playerInfo; object obj2 = PlayerInfoUserInfoField?.GetValue(obj); if (obj2 == null) { return string.Empty; } return IceboxSubsystem.NormalizeAccountId((obj2.GetType().GetField("m_id", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2))?.ToString()); } catch { return string.Empty; } } private static ZNetPeer? FindPeerByCharacter(ZDOID characterId) { //IL_0022: 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) ZNet instance = ZNet.instance; List list = ((instance != null) ? instance.GetPeers() : null); if (list == null) { return null; } for (int i = 0; i < list.Count; i++) { if (list[i].m_characterID == characterId) { return list[i]; } } return null; } private static void IncrementAccountCount(string accountId) { string text = IceboxSubsystem.NormalizeAccountId(accountId); if (text.Length != 0) { CountsByAccount[text] = ((!CountsByAccount.TryGetValue(text, out var value)) ? 1 : (value + 1)); } } private static void DecrementAccountCount(string accountId) { string text = IceboxSubsystem.NormalizeAccountId(accountId); if (text.Length != 0 && CountsByAccount.TryGetValue(text, out var value)) { if (value <= 1) { CountsByAccount.Remove(text); } else { CountsByAccount[text] = value - 1; } } } private static bool AccountIdsEqual(string left, string right) { return string.Equals(IceboxSubsystem.NormalizeAccountId(left), IceboxSubsystem.NormalizeAccountId(right), StringComparison.Ordinal); } private static void BumpRevision() { _revision = ((_revision == int.MaxValue) ? 1 : (_revision + 1)); } private static bool IsGlobalFreeBuildEnabled() { ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { return false; } return instance.GetGlobalKey((GlobalKeys)19); } internal static string FormatPlacementLimitMessage(int limit) { string text = ((limit < 0) ? "-1" : limit.ToString()); string text2 = ((Localization.instance != null) ? Localization.instance.Localize("$finedining_icebox_limit_reached") : "$finedining_icebox_limit_reached"); if (string.IsNullOrWhiteSpace(text2) || string.Equals(text2, "$finedining_icebox_limit_reached", StringComparison.Ordinal)) { return "Icebox placement limit reached (maximum " + text + ")."; } return text2.Replace("{0}", text); } } internal static class IceboxSubsystem { private sealed class PlacementLimitAcceptableValue : AcceptableValueBase { internal PlacementLimitAcceptableValue() : base(typeof(int)) { } public override object Clamp(object value) { return (value is int num && num >= -1) ? num : 2; } public override bool IsValid(object value) { if (value is int num) { return num >= -1; } return false; } public override string ToDescriptionString() { return "# Acceptable values: -1 or greater"; } } internal const string PrefabName = "FineDining_Icebox"; internal const string OwnerAccountIdKey = "sighsorry.FineDining.IceboxOwnerAccountId"; internal const string LimitRefundProcessedKey = "sighsorry.FineDining.IceboxLimitRefundProcessed"; internal const string PlacedRecipeKey = "sighsorry.FineDining.IceboxPlacedRecipe"; internal const int StorageColumns = 8; internal const int DefaultStorageRows = 4; internal const int MinimumStorageRows = 4; internal const int MaximumStorageRows = 20; internal const int DefaultPlacementLimit = 2; internal const string DefaultRecipe = "TrophySGolem:1,Obsidian:8,Crystal:16,Silver:32"; internal const bool DefaultShowMapPins = true; private static readonly FieldRef InventoryWidth = AccessTools.FieldRefAccess("m_width"); private static readonly FieldRef InventoryHeight = AccessTools.FieldRefAccess("m_height"); private static FineDiningPlugin? _owner; private static ConfigEntry? _defaultPlacementLimit; private static ConfigEntry? _storageRows; private static ConfigEntry? _recipe; private static ConfigEntry? _showMapPins; internal static int PrefabHash => GeneratedPrefabRegistry.IceboxPrefabHash; internal static bool IsInitialized => (Object)(object)_owner != (Object)null; internal static int PlacementLimit => _defaultPlacementLimit?.Value ?? 2; internal static int StorageRows => Mathf.Clamp(_storageRows?.Value ?? 4, 4, 20); internal static string Recipe => _recipe?.Value ?? "TrophySGolem:1,Obsidian:8,Crystal:16,Silver:32"; internal static bool ShowMapPins => _showMapPins?.Value ?? false; internal static void Initialize(FineDiningPlugin owner) { if ((Object)(object)owner == (Object)null) { throw new ArgumentNullException("owner"); } if (_owner != owner) { Shutdown(); _owner = owner; _defaultPlacementLimit = BindDefaultPlacementLimit(((BaseUnityPlugin)owner).Config); _storageRows = BindStorageRows(((BaseUnityPlugin)owner).Config); _recipe = BindRecipe(((BaseUnityPlugin)owner).Config); _showMapPins = ((BaseUnityPlugin)owner).Config.Bind(ConfigPresentation.Spoilage.Name, "Icebox Map Pins", true, ConfigPresentation.Client("Show the positions of Iceboxes owned by this account on the world map and minimap.", ConfigPresentation.Spoilage, 200)); FineDiningPlugin.ConfigSync.AddConfigEntry(_defaultPlacementLimit).SynchronizedConfig = true; FineDiningPlugin.ConfigSync.AddConfigEntry(_storageRows).SynchronizedConfig = true; FineDiningPlugin.ConfigSync.AddConfigEntry(_recipe).SynchronizedConfig = true; _storageRows.SettingChanged += OnGameplayConfigChanged; _recipe.SettingChanged += OnGameplayConfigChanged; _showMapPins.SettingChanged += OnShowMapPinsChanged; IceboxLimitPolicy.Initialize(); IceboxQuotaService.Initialize(); IceboxMapPins.Initialize(); } } internal static void Tick() { if (!((Object)(object)_owner == (Object)null)) { IceboxLimitPolicy.Tick(); IceboxQuotaService.Tick(); IceboxMapPins.Tick(); } } internal static void ResetSession() { IceboxQuotaService.ResetSession(); IceboxMapPins.ResetSession(); } internal static void Shutdown() { _defaultPlacementLimit = null; if (_storageRows != null) { _storageRows.SettingChanged -= OnGameplayConfigChanged; _storageRows = null; } if (_recipe != null) { _recipe.SettingChanged -= OnGameplayConfigChanged; _recipe = null; } if (_showMapPins != null) { _showMapPins.SettingChanged -= OnShowMapPinsChanged; _showMapPins = null; } IceboxMapPins.Shutdown(); IceboxQuotaService.Shutdown(); IceboxLimitPolicy.Shutdown(); _owner = null; } internal static ConfigEntry BindDefaultPlacementLimit(ConfigFile config) { return config.Bind(ConfigPresentation.Spoilage.Name, "Icebox Default Placement Limit", 2, ConfigPresentation.Synced("Default number of Iceboxes each Steam account may place. -1 allows unlimited placement, 0 denies placement, and positive values set the maximum. Exact Steam64 overrides in Icebox.yml take priority.", ConfigPresentation.Spoilage, 450, (AcceptableValueBase?)(object)new PlacementLimitAcceptableValue())); } internal static ConfigEntry BindStorageRows(ConfigFile config) { return config.Bind(ConfigPresentation.Spoilage.Name, "Icebox Storage Rows", 4, ConfigPresentation.Synced("Number of Icebox inventory rows. Iceboxes always have eight columns.", ConfigPresentation.Spoilage, 400, (AcceptableValueBase?)(object)new AcceptableValueRange(4, 20))); } internal static ConfigEntry BindRecipe(ConfigFile config) { return config.Bind(ConfigPresentation.Spoilage.Name, "Icebox Build Recipe", "TrophySGolem:1,Obsidian:8,Crystal:16,Silver:32", ConfigPresentation.Synced("Comma-separated ItemPrefab:Amount entries used to build the Icebox, for example TrophySGolem:1,Obsidian:8,Crystal:16,Silver:32. Every amount must be a positive integer and every prefab must be a registered item.", ConfigPresentation.Spoilage, 300)); } internal static bool TryCreateRequirements(ObjectDB objectDb, string? recipe, out Requirement[] requirements) { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Expected O, but got Unknown requirements = Array.Empty(); if (!TryParseRecipe(recipe, out List> ingredients, out string error)) { GeneratedPrefabRegistry.LogProblemOnce("icebox-recipe-format:" + (recipe ?? string.Empty), "Could not apply the Icebox recipe '" + (recipe ?? string.Empty) + "': " + error + " Expected comma-separated ItemPrefab:Amount entries such as 'TrophySGolem:1,Obsidian:8,Crystal:16,Silver:32'."); return false; } Requirement[] array = (Requirement[])(object)new Requirement[ingredients.Count]; for (int i = 0; i < ingredients.Count; i++) { KeyValuePair keyValuePair = ingredients[i]; GameObject? obj = GeneratedPrefabRegistry.FindItemPrefab(objectDb, keyValuePair.Key); ItemDrop val = ((obj != null) ? obj.GetComponent() : null); if (val == null) { GeneratedPrefabRegistry.LogDebugOnce("icebox-recipe-item:" + keyValuePair.Key, "Could not apply the Icebox recipe yet: item prefab '" + keyValuePair.Key + "' is not ready."); return false; } array[i] = new Requirement { m_resItem = val, m_amount = keyValuePair.Value, m_amountPerLevel = 0, m_recover = true }; } requirements = array; return true; } internal static string SerializeRequirements(Requirement[]? requirements) { if (requirements == null || requirements.Length == 0) { return string.Empty; } List list = new List(requirements.Length); HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (Requirement obj in requirements) { ItemDrop val = obj?.m_resItem; string text = ((val != null) ? FoodIdentity.NormalizePrefabName(Utils.GetPrefabName(((Component)val).gameObject)) : string.Empty); int num = obj?.m_amount ?? 0; if (text.Length == 0 || num <= 0 || !hashSet.Add(text)) { return string.Empty; } list.Add(text + ":" + num.ToString(CultureInfo.InvariantCulture)); } return string.Join(",", list); } private static bool TryParseRecipe(string? recipe, out List> ingredients, out string error) { ingredients = new List>(); error = string.Empty; string text = recipe?.Trim() ?? string.Empty; if (text.Length == 0) { error = "the recipe is empty."; return false; } HashSet hashSet = new HashSet(StringComparer.Ordinal); string[] array = text.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); string[] array2 = text2.Split(new char[1] { ':' }); if (array2.Length != 2) { error = "entry '" + text2 + "' does not contain exactly one ':' separator."; return false; } string text3 = array2[0].Trim(); string s = array2[1].Trim(); if (text3.Length == 0) { error = "entry '" + text2 + "' has an empty prefab name."; return false; } if (!int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result <= 0) { error = "entry '" + text2 + "' must use a positive integer amount."; return false; } if (!hashSet.Add(text3)) { error = "item prefab '" + text3 + "' is listed more than once."; return false; } ingredients.Add(new KeyValuePair(text3, result)); } return true; } internal static void ApplyConfiguredStorageSize(Container? container) { if (!((Object)(object)container == (Object)null) && GeneratedPrefabRegistry.IsIcebox(container)) { Inventory inventory = container.GetInventory(); int num = ResolveSafeStorageRows(inventory); container.m_width = 8; container.m_height = num; if (inventory != null) { InventoryWidth.Invoke(inventory) = 8; InventoryHeight.Invoke(inventory) = num; } } } internal static bool PrepareStorageLoad(Container? container) { if ((Object)(object)container == (Object)null || !GeneratedPrefabRegistry.IsIcebox(container)) { return false; } container.m_width = 8; container.m_height = 20; Inventory inventory = container.GetInventory(); if (inventory != null) { InventoryWidth.Invoke(inventory) = 8; InventoryHeight.Invoke(inventory) = 20; } return true; } internal static void CapturePlacedRecipe(Piece? piece) { if ((Object)(object)piece == (Object)null || !IsIcebox(piece)) { return; } ZNetView val = piece.m_nview ?? ((Component)piece).GetComponent(); if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner()) { return; } ZDO zDO = val.GetZDO(); if (string.IsNullOrEmpty(zDO.GetString("sighsorry.FineDining.IceboxPlacedRecipe", string.Empty))) { string text = SerializeRequirements(piece.m_resources); if (text.Length == 0) { FineDiningPlugin.Log.LogWarning((object)"Could not persist the construction recipe for a newly placed Icebox; its recovery requirements cannot be protected across a recipe change."); } else { zDO.Set("sighsorry.FineDining.IceboxPlacedRecipe", text); } } } internal static void ApplyStoredRecipe(Container? container) { if ((Object)(object)container == (Object)null || !GeneratedPrefabRegistry.IsIcebox(container)) { return; } Piece component = ((Component)container).GetComponent(); ZNetView val = component?.m_nview ?? ((Component)container).GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)val == (Object)null || !val.IsValid()) { return; } string text = val.GetZDO().GetString("sighsorry.FineDining.IceboxPlacedRecipe", string.Empty); if (text.Length != 0) { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance != (Object)null && TryCreateRequirements(instance, text, out Requirement[] requirements)) { component.m_resources = requirements; } else { component.m_resources = Array.Empty(); } } } internal static void ApplyStoredRecipesToLoadedIceboxes() { Container[] array = Object.FindObjectsByType((FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { ApplyStoredRecipe(array[i]); } } internal static void HandleInventoryChanged(Inventory? inventory) { if (inventory != null && !DecayRuntime.IsContainerLoading(inventory) && DecayRuntime.TryGetContainer(inventory, out Container container) && !((Object)(object)container == (Object)null) && GeneratedPrefabRegistry.IsIcebox(container)) { ApplyConfiguredStorageSize(container); } } private static int ResolveSafeStorageRows(Inventory? inventory) { int num = StorageRows; if (inventory == null) { return num; } foreach (ItemData allItem in inventory.GetAllItems()) { num = Math.Max(num, allItem.m_gridPos.y + 1); } return Mathf.Clamp(num, 4, 20); } private static void ApplyConfiguredStorageSizeToLoadedIceboxes() { Container[] array = Object.FindObjectsByType((FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { ApplyConfiguredStorageSize(array[i]); } } internal static bool IsIcebox(Piece? piece) { if ((Object)(object)piece == (Object)null) { return false; } ZNetView nview = piece.m_nview; if ((Object)(object)nview != (Object)null && nview.IsValid()) { return IsIcebox(nview.GetZDO()); } return IsIcebox(((Component)piece).gameObject); } internal static bool IsIcebox(GameObject? gameObject) { if ((Object)(object)gameObject == (Object)null) { return false; } return GeneratedPrefabRegistry.IsIcebox(gameObject); } internal static bool IsIcebox(ZDO? zdo) { if (zdo != null && zdo.IsValid()) { return zdo.GetPrefab() == PrefabHash; } return false; } internal static string NormalizeAccountId(string? rawAccountId) { string text = rawAccountId?.Trim() ?? string.Empty; if (text.StartsWith("Steam_", StringComparison.OrdinalIgnoreCase)) { text = text.Substring("Steam_".Length); } return text; } private static void OnShowMapPinsChanged(object sender, EventArgs args) { IceboxMapPins.HandleConfigChanged(); } private static void OnGameplayConfigChanged(object sender, EventArgs args) { ApplyConfiguredStorageSizeToLoadedIceboxes(); GeneratedPrefabRegistry.RefreshIceboxConfiguredContent(); } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class ZNetAwakeIceboxSubsystemPatch { [HarmonyPriority(800)] private static void Prefix() { IceboxSubsystem.ResetSession(); } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class ZNetSceneAwakeIceboxSubsystemPatch { private static void Postfix() { IceboxQuotaService.EnsureRpcBindings(); IceboxMapPins.EnsureRpcBindings(); } } [HarmonyPatch(typeof(ZDOMan), "Load")] internal static class ZdoManLoadIceboxSubsystemPatch { private static void Postfix(ZDOMan __instance) { IceboxQuotaService.OnAuthoritativeWorldLoaded(__instance); } } [HarmonyPatch(typeof(Piece), "SetCreator")] internal static class PieceSetCreatorIceboxSubsystemPatch { private static void Postfix(Piece __instance) { IceboxSubsystem.CapturePlacedRecipe(__instance); IceboxQuotaService.NotifyLocallyPlacedIcebox(__instance); } } [HarmonyPatch(typeof(Minimap), "OnDestroy")] internal static class MinimapDestroyIceboxSubsystemPatch { private static void Prefix(Minimap __instance) { IceboxMapPins.HandleMinimapDestroyed(__instance); } } internal static class InventorySlotsCompatibility { internal const string PluginGuid = "sighsorry.InventorySlots"; internal static readonly System.Version MinimumSupportedVersion = new System.Version(1, 3, 7); private const string ApiTypeName = "InventorySlots.InventorySlotsApi"; internal static void TryInstall() { if (!Chainloader.PluginInfos.TryGetValue("sighsorry.InventorySlots", out var value)) { return; } try { if (value.Metadata.Version.CompareTo(MinimumSupportedVersion) < 0) { FineDiningPlugin.Log.LogWarning((object)("InventorySlots " + value.Metadata.Version?.ToString() + " predates FineDining stack-metadata compatibility; update it to " + MinimumSupportedVersion?.ToString() + " or newer.")); } Type type = ((object)value.Instance)?.GetType().Assembly.GetType("InventorySlots.InventorySlotsApi", throwOnError: false); MethodInfo methodInfo = type?.GetMethod("RegisterStackMetadataPolicy", BindingFlags.Static | BindingFlags.Public, null, new Type[3] { typeof(string), typeof(Func), typeof(Func) }, null); MethodInfo methodInfo2 = type?.GetMethod("RegisterStackMetadataPolicy", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { typeof(string), typeof(Func) }, null); MethodInfo methodInfo3 = methodInfo ?? methodInfo2; if (methodInfo3 == null) { FineDiningPlugin.Log.LogWarning((object)"InventorySlots is installed, but a compatible stack metadata API was not found. FineDining spoilage metadata was not registered."); return; } bool flag = methodInfo != null; if (!flag) { FineDiningPlugin.Log.LogWarning((object)"InventorySlots exposes only the legacy 2-argument stack metadata API. FineDining will register its mergers, but InventorySlots cannot ask them to reject malformed or future metadata before stacking."); } Func merger = DecayRuntime.ComposeStackClockValues; Func func = DecayRuntime.CanMergeStackClockValues; Func merger2 = FreshnessRuntime.ComposeAssignedLifetimeValues; Func func2 = FreshnessRuntime.CanMergeAssignedLifetimeValues; RegisterAndLog(methodInfo3, "sighsorry.FineDining.ExpiryWorldTicks", "spoilage-clock", merger, flag ? func : null); RegisterAndLog(methodInfo3, "sighsorry.FineDining.AssignedLifetimeTicks", "assigned-lifetime", merger2, flag ? func2 : null); } catch (Exception ex) { FineDiningPlugin.Log.LogWarning((object)("Could not inspect InventorySlots' stack metadata API. FineDining spoilage metadata may remain unregistered. " + ex)); } } private static void RegisterAndLog(MethodInfo register, string key, string label, Func merger, Func? canMerge) { try { if (Register(register, key, merger, canMerge)) { FineDiningPlugin.Log.LogInfo((object)("Registered FineDining " + label + " metadata merger with InventorySlots" + ((canMerge != null) ? " with fail-closed validation." : " through its legacy API."))); return; } FineDiningPlugin.Log.LogWarning((object)("InventorySlots did not accept FineDining's " + label + " metadata merger for '" + key + "'. Another first-wins policy may already own that key; FineDining cannot verify the active policy.")); } catch (Exception ex) { FineDiningPlugin.Log.LogWarning((object)("Could not register FineDining's " + label + " metadata merger for '" + key + "' with InventorySlots. " + ex)); } } private static bool Register(MethodInfo register, string key, Func merger, Func? canMerge) { object[] parameters = ((canMerge == null) ? new object[2] { key, merger } : new object[3] { key, merger, canMerge }); object obj = register.Invoke(null, parameters); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } } internal sealed class PlacementSpoilageState { internal string TargetPrefabName = ""; internal long PlacementTicks; internal long InheritedRemainingTicks = -1L; internal bool Consumed; } internal static class PlacementSpoilageTracker { [ThreadStatic] private static Stack? _scopes; private static bool _loggedCaptureFailure; internal static PlacementSpoilageState Begin(Player player, Piece piece) { PlacementSpoilageState placementSpoilageState = new PlacementSpoilageState(); try { if ((Object)(object)player != (Object)null && (Object)(object)piece != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer) { placementSpoilageState.TargetPrefabName = FoodIdentity.NormalizePrefabName(((Object)((Component)piece).gameObject).name); DecayRuntime.TryGetWorldTicks(out placementSpoilageState.PlacementTicks); if (!IsNoCostPlacement(player, piece)) { placementSpoilageState.InheritedRemainingTicks = CaptureConsumedRemaining(((Humanoid)player).GetInventory(), piece); } } } catch (Exception ex) { if (!_loggedCaptureFailure) { _loggedCaptureFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not capture a placed food source deadline; the placed item will receive a fresh timer: " + ex)); } } (_scopes ?? (_scopes = new Stack())).Push(placementSpoilageState); return placementSpoilageState; } internal static void End(PlacementSpoilageState? state) { if (state != null && _scopes != null && _scopes.Count != 0) { if (_scopes.Peek() == state) { _scopes.Pop(); } else { _scopes.Clear(); } } } internal static bool TryConsumeFor(ItemDrop placedDrop, bool sendRpc, out long inheritedRemainingTicks, out long placementTicks) { inheritedRemainingTicks = -1L; placementTicks = 0L; if (!sendRpc || (Object)(object)placedDrop == (Object)null || _scopes == null || _scopes.Count == 0) { return false; } PlacementSpoilageState placementSpoilageState = _scopes.Peek(); string b = FoodIdentity.NormalizePrefabName(((Object)((Component)placedDrop).gameObject).name); if (placementSpoilageState.Consumed || placementSpoilageState.TargetPrefabName.Length == 0 || !string.Equals(placementSpoilageState.TargetPrefabName, b, StringComparison.OrdinalIgnoreCase)) { return false; } placementSpoilageState.Consumed = true; inheritedRemainingTicks = placementSpoilageState.InheritedRemainingTicks; placementTicks = placementSpoilageState.PlacementTicks; return true; } private static long CaptureConsumedRemaining(Inventory? inventory, Piece piece) { if (inventory == null || piece.m_resources == null) { return -1L; } Dictionary dictionary = new Dictionary(); foreach (ItemData item in inventory.m_inventory) { if (item != null) { dictionary[item] = Math.Max(0, item.m_stack); } } bool flag = false; long num = -1L; if (!DecayRuntime.TryGetWorldTicks(out var ticks)) { return num; } Requirement[] resources = piece.m_resources; foreach (Requirement val in resources) { if (val?.m_resItem?.m_itemData?.m_shared == null) { continue; } int num2 = Math.Max(0, val.GetAmount(0)); string name = val.m_resItem.m_itemData.m_shared.m_name; if (num2 <= 0 || string.IsNullOrEmpty(name)) { continue; } foreach (ItemData item2 in inventory.m_inventory) { if (num2 <= 0) { break; } if (item2?.m_shared == null || item2.m_shared.m_name != name || item2.m_worldLevel < Game.m_worldLevel || !dictionary.TryGetValue(item2, out var value) || value <= 0) { continue; } int num3 = Math.Min(value, num2); dictionary[item2] = value - num3; num2 -= num3; if (num3 > 0) { flag |= DecayRuntime.PrepareItemForAdd(inventory, item2); if (DecayRuntime.TryGetSpoilageClock(item2, ticks, out var remainingTicks, out var _)) { num = ((num < 0) ? remainingTicks : Math.Min(num, remainingTicks)); } } } } if (flag) { inventory.Changed(); } return num; } private static bool IsNoCostPlacement(Player player, Piece piece) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) try { return player.NoCostCheat() || ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey())); } catch { return false; } } } internal sealed class PieceRecoverySpoilageState { internal long RemainingTicks = -1L; internal readonly HashSet RecoverySourcePrefabs = new HashSet(StringComparer.OrdinalIgnoreCase); internal readonly HashSet RecoverablePrefabs = new HashSet(StringComparer.OrdinalIgnoreCase); } internal static class PieceRecoverySpoilageTracker { [ThreadStatic] private static Stack? _scopes; private static bool _loggedRecoveryFailure; private static PieceRecoverySpoilageState? Current { get { if (_scopes == null || _scopes.Count <= 0) { return null; } return _scopes.Peek(); } } internal static PieceRecoverySpoilageState Begin(Piece piece) { PieceRecoverySpoilageState pieceRecoverySpoilageState = new PieceRecoverySpoilageState(); try { ItemDrop val = (((Object)(object)piece != (Object)null) ? ((Component)piece).GetComponent() : null); if ((Object)(object)val != (Object)null) { try { val.Load(); } catch { } if (!DecayRuntime.IsCreatorlessPlacedDrop(val) && DecayRuntime.TryGetWorldTicks(out var ticks) && DecayRuntime.TryGetSpoilageClock(val.m_itemData, ticks, out var remainingTicks, out var _)) { pieceRecoverySpoilageState.RemainingTicks = remainingTicks; } } if (piece?.m_resources != null) { Requirement[] resources = piece.m_resources; foreach (Requirement val2 in resources) { if (val2 != null && val2.m_recover && !((Object)(object)val2.m_resItem == (Object)null)) { string text = FoodIdentity.NormalizePrefabName(((Object)((Component)val2.m_resItem).gameObject).name); if (text.Length > 0) { pieceRecoverySpoilageState.RecoverySourcePrefabs.Add(text); pieceRecoverySpoilageState.RecoverablePrefabs.Add(text); } } } } } catch (Exception ex) { if (!_loggedRecoveryFailure) { _loggedRecoveryFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not capture a placed food deadline before resource recovery: " + ex)); } } (_scopes ?? (_scopes = new Stack())).Push(pieceRecoverySpoilageState); return pieceRecoverySpoilageState; } internal static void End(PieceRecoverySpoilageState? state) { if (state != null && _scopes != null && _scopes.Count != 0) { if (_scopes.Peek() == state) { _scopes.Pop(); } else { _scopes.Clear(); } } } internal static void ApplyToGroundDrop(ItemDrop? recoveredDrop) { PieceRecoverySpoilageState current = Current; if (current != null && !((Object)(object)recoveredDrop == (Object)null) && Matches(current, recoveredDrop.m_itemData)) { DecayRuntime.InitializeRecoveredDrop(recoveredDrop, current.RemainingTicks); } } internal static bool ApplyToInventoryItem(Inventory inventory, ItemData? item) { try { PieceRecoverySpoilageState current = Current; return current != null && item != null && Matches(current, item) && DecayRuntime.PrepareInheritedItemForAdd(inventory, item, current.RemainingTicks); } catch (Exception ex) { if (!_loggedRecoveryFailure) { _loggedRecoveryFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not transfer a placed food deadline to recovered inventory data: " + ex)); } return false; } } internal static void IncludeConvertedResult(ItemDrop? source, GameObject? resultPrefab) { try { PieceRecoverySpoilageState current = Current; if (current == null || current.RemainingTicks < 0 || (Object)(object)source == (Object)null || (Object)(object)resultPrefab == (Object)null) { return; } string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(source.m_itemData); if (canonicalPrefabName.Length != 0 && current.RecoverySourcePrefabs.Contains(canonicalPrefabName) && !((Object)(object)resultPrefab.GetComponent() == (Object)null)) { string text = FoodIdentity.NormalizePrefabName(((Object)resultPrefab).name); if (text.Length > 0) { current.RecoverablePrefabs.Add(text); } } } catch (Exception ex) { if (!_loggedRecoveryFailure) { _loggedRecoveryFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not include a converted Piece recovery item in deadline inheritance: " + ex)); } } } private static bool Matches(PieceRecoverySpoilageState state, ItemData item) { if (state.RemainingTicks < 0 || state.RecoverablePrefabs.Count == 0) { return false; } string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(item); if (canonicalPrefabName.Length > 0) { return state.RecoverablePrefabs.Contains(canonicalPrefabName); } return false; } } [HarmonyPatch] internal static class GameCheckDropConversionSpoilagePatch { private static MethodBase TargetMethod() { return AccessTools.DeclaredMethod(typeof(Game), "CheckDropConversion", new Type[4] { typeof(HitData), typeof(ItemDrop), typeof(GameObject), typeof(int).MakeByRefType() }, (Type[])null); } [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(ItemDrop itemDrop, GameObject __result) { PieceRecoverySpoilageTracker.IncludeConvertedResult(itemDrop, __result); } } [HarmonyPatch(typeof(Player), "PlacePiece")] internal static class PlayerPlacePieceSpoilagePatch { private static void Prefix(Player __instance, Piece piece, out PlacementSpoilageState __state) { __state = PlacementSpoilageTracker.Begin(__instance, piece); } private static Exception? Finalizer(PlacementSpoilageState __state, Exception? __exception) { PlacementSpoilageTracker.End(__state); return __exception; } } [HarmonyPatch(typeof(ItemDrop), "MakePiece")] internal static class ItemDropMakePieceSpoilagePatch { private static bool _loggedFailure; private static void Postfix(ItemDrop __instance, bool sendRPC) { try { PlacementSpoilageTracker.TryConsumeFor(__instance, sendRPC, out var inheritedRemainingTicks, out var placementTicks); DecayRuntime.InitializePlacedDrop(__instance, inheritedRemainingTicks, placementTicks); } catch (Exception ex) { if (!_loggedFailure) { _loggedFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not initialize spoilage for an ItemDrop Piece: " + ex)); } } } } [HarmonyPatch(typeof(Piece), "DropResources")] internal static class PieceDropResourcesSpoilagePatch { private static void Prefix(Piece __instance, out PieceRecoverySpoilageState __state) { __state = PieceRecoverySpoilageTracker.Begin(__instance); } private static Exception? Finalizer(PieceRecoverySpoilageState __state, Exception? __exception) { PieceRecoverySpoilageTracker.End(__state); return __exception; } } [HarmonyPatch(typeof(ItemDrop), "OnCreateNew", new Type[] { typeof(ItemDrop) })] internal static class ItemDropCreateRecoveredSpoilagePatch { private static bool _loggedFailure; private static void Postfix(ItemDrop item) { try { PieceRecoverySpoilageTracker.ApplyToGroundDrop(item); } catch (Exception ex) { if (!_loggedFailure) { _loggedFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not transfer a placed food deadline to a recovered ground item: " + ex)); } } } } [BepInPlugin("sighsorry.FineDining", "FineDining", "1.0.0")] [BepInDependency("com.jotunn.jotunn", "2.29.2")] [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.*/)] [BepInIncompatibility("sighsorry.BeingSpoiled")] [BepInIncompatibility("blizz.GourmetsDiet")] [BepInIncompatibility("sighsorry.InputHoverHints")] public sealed class FineDiningPlugin : BaseUnityPlugin { internal const string ModName = "FineDining"; internal const string ModVersion = "1.0.0"; internal const string Author = "sighsorry"; internal const string ModGUID = "sighsorry.FineDining"; internal const bool DefaultConfigurationLock = true; internal static ManualLogSource Log = null; internal static readonly ConfigSync ConfigSync = new ConfigSync("sighsorry.FineDining") { DisplayName = "FineDining", CurrentVersion = "1.0.0", MinimumRequiredVersion = "1.0.0", ModRequired = true }; private readonly Harmony _harmony = new Harmony("sighsorry.FineDining"); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; FineDiningLocalization.Initialize((BaseUnityPlugin)(object)this); ConfigSync.AddLockingConfigEntry(BindConfigurationLock(((BaseUnityPlugin)this).Config)); PreservationConfig.Initialize(((BaseUnityPlugin)this).Config, ConfigSync); FreshnessRuntime.Initialize(((BaseUnityPlugin)this).Config, ConfigSync); SpoilagePolicy.Initialize(ConfigSync); ChefResourceMapPolicy.Initialize(ConfigSync); IceboxSubsystem.Initialize(this); GeneratedPrefabRegistry.Initialize(); DietModule.Initialize(((BaseUnityPlugin)this).Config, ConfigSync); StationModule.Initialize(((BaseUnityPlugin)this).Config, ConfigSync); _harmony.PatchAll(Assembly.GetExecutingAssembly()); AzuExtendedPlayerInventoryCompatibility.TryInstall(_harmony); InventorySlotsCompatibility.TryInstall(); } internal static ConfigEntry BindConfigurationLock(ConfigFile config) { return config.Bind(ConfigPresentation.General.Name, "Lock Server Configuration", true, ConfigPresentation.Synced("Lock synchronized gameplay settings to the server. Server administrators remain exempt.", ConfigPresentation.General, 500)); } private void Update() { PreservationConfig.Tick(); SpoilagePolicy.RefreshAuthority(); ChefResourceMapPolicy.RefreshAuthority(); ChefFoodTierCatalog.Tick(); SpoilageReferenceGenerator.Tick(); ChefTierReferenceGenerator.Tick(); DecayRuntime.Tick(); IceboxSubsystem.Tick(); DietModule.Tick(); } private void OnDestroy() { StationModule.Shutdown(); DietModule.Shutdown(); ChefResourceMapPolicy.Shutdown(); FreshnessRuntime.Shutdown(); PreservationConfig.Shutdown(); SpoilagePolicy.Shutdown(); GeneratedPrefabRegistry.Shutdown(); IceboxSubsystem.Shutdown(); AzuExtendedPlayerInventoryCompatibility.Shutdown(); FineDiningLocalization.Shutdown(); _harmony.UnpatchSelf(); DecayRuntime.Reset(); FoodClassifier.Invalidate(); SpoilageReferenceGenerator.Reset(); ChefFoodTierCatalog.Reset(); ChefTierReferenceGenerator.Reset(); } } internal static class PreservationConfig { private delegate bool TryGetBiomeDelegate(string name, out Biome biome); private delegate Biome GetNatureDelegate(Biome biome); private const string DefaultBiomeList = "Mountain, DeepNorth"; private static readonly HashSet NoSpoilBiomeNames = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet WarnedUnknownBiomeNames = new HashSet(StringComparer.OrdinalIgnoreCase); private static ConfigEntry? _noSpoilBiomes; private static TryGetBiomeDelegate? _expandWorldDataTryGetBiome; private static GetNatureDelegate? _expandWorldDataGetNature; private static Biome _resolvedBiomeMask; private static float _nextValidationAt; private static float _nextBridgeAttemptAt; private static bool _expandWorldDataBridgeWarningLogged; internal static void Initialize(ConfigFile config, ConfigSync configSync) { Shutdown(); _noSpoilBiomes = config.Bind(ConfigPresentation.Spoilage.Name, "No-Spoil Biomes", "Mountain, DeepNorth", ConfigPresentation.Synced("Comma-separated biome identifiers where spoilage pauses. Expand World Data custom biome names are supported when that mod is installed.", ConfigPresentation.Spoilage, 600)); configSync.AddConfigEntry(_noSpoilBiomes).SynchronizedConfig = true; _noSpoilBiomes.SettingChanged += OnSettingChanged; Rebuild(); } internal static void Shutdown() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (_noSpoilBiomes != null) { _noSpoilBiomes.SettingChanged -= OnSettingChanged; _noSpoilBiomes = null; } NoSpoilBiomeNames.Clear(); WarnedUnknownBiomeNames.Clear(); _resolvedBiomeMask = (Biome)0; _expandWorldDataTryGetBiome = null; _expandWorldDataGetNature = null; _nextValidationAt = 0f; _nextBridgeAttemptAt = 0f; _expandWorldDataBridgeWarningLogged = false; } internal static void Tick() { if (!(Time.unscaledTime < _nextValidationAt) && !((Object)(object)ZoneSystem.instance == (Object)null) && WorldGenerator.instance != null) { _nextValidationAt = Time.unscaledTime + 10f; ResolveConfiguredBiomeMask(warnUnknown: true); } } internal static bool IsNoSpoilBiome(Biome biome) { //IL_0000: 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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 if ((int)biome == 0 || NoSpoilBiomeNames.Count == 0) { return false; } Biome val = ResolveEffectiveNature(biome); if ((int)_resolvedBiomeMask != 0) { return (val & _resolvedBiomeMask) > 0; } return false; } private static void OnSettingChanged(object sender, EventArgs eventArgs) { Rebuild(); } private static void Rebuild() { NoSpoilBiomeNames.Clear(); WarnedUnknownBiomeNames.Clear(); _nextValidationAt = 0f; string[] array = (_noSpoilBiomes?.Value ?? "Mountain, DeepNorth").Trim().Trim('[', ']').Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim().Trim('\'', '"'); if (text.Length != 0) { if (text.Equals("None", StringComparison.OrdinalIgnoreCase)) { FineDiningPlugin.Log.LogWarning((object)"No-Spoil Biomes ignores 'None' because it represents an unavailable biome sample."); } else { NoSpoilBiomeNames.Add(text); } } } ResolveConfiguredBiomeMask(warnUnknown: false); } private static void ResolveConfiguredBiomeMask(bool warnUnknown) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) Biome val = (Biome)0; foreach (string noSpoilBiomeName in NoSpoilBiomeNames) { if (!TryResolveBiomeName(noSpoilBiomeName, out var biome) || (int)biome == 0) { if (warnUnknown && WarnedUnknownBiomeNames.Add(noSpoilBiomeName)) { FineDiningPlugin.Log.LogWarning((object)("No-Spoil Biomes contains unresolved biome identifier '" + noSpoilBiomeName + "'. It remains configured and will be retried for late-loaded Expand World Data content.")); } continue; } Biome val2 = ResolveEffectiveNature(biome); if ((int)val2 != 0) { val |= val2; WarnedUnknownBiomeNames.Remove(noSpoilBiomeName); } } if (_resolvedBiomeMask != val) { _resolvedBiomeMask = val; DecayRuntime.InvalidateAll(); } } private static bool TryResolveBiomeName(string name, out Biome biome) { EnsureExpandWorldDataBridge(); if (_expandWorldDataTryGetBiome != null) { try { if (_expandWorldDataTryGetBiome(name, out biome)) { return true; } } catch (Exception exception) { LogExpandWorldDataBridgeWarning(exception); } } return Enum.TryParse(name, ignoreCase: true, out biome); } private static Biome ResolveEffectiveNature(Biome biome) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) EnsureExpandWorldDataBridge(); if (_expandWorldDataGetNature == null) { return biome; } try { return _expandWorldDataGetNature(biome); } catch (Exception exception) { LogExpandWorldDataBridgeWarning(exception); return biome; } } private static void EnsureExpandWorldDataBridge() { if (_expandWorldDataTryGetBiome != null && _expandWorldDataGetNature != null) { return; } float unscaledTime = Time.unscaledTime; if (unscaledTime < _nextBridgeAttemptAt) { return; } _nextBridgeAttemptAt = unscaledTime + 10f; Type type = Type.GetType("ExpandWorldData.BiomeManager, ExpandWorldData", throwOnError: false); if (type == null) { return; } try { MethodInfo method = type.GetMethod("TryGetBiome", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { typeof(string), typeof(Biome).MakeByRefType() }, null); MethodInfo method2 = type.GetMethod("GetNature", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Biome) }, null); if (method == null || method2 == null) { throw new MissingMethodException("ExpandWorldData.BiomeManager.TryGetBiome/GetNature was not found."); } _expandWorldDataTryGetBiome = (TryGetBiomeDelegate)method.CreateDelegate(typeof(TryGetBiomeDelegate)); _expandWorldDataGetNature = (GetNatureDelegate)method2.CreateDelegate(typeof(GetNatureDelegate)); } catch (Exception exception) { LogExpandWorldDataBridgeWarning(exception); } } private static void LogExpandWorldDataBridgeWarning(Exception exception) { if (!_expandWorldDataBridgeWarningLogged) { _expandWorldDataBridgeWarningLogged = true; FineDiningPlugin.Log.LogWarning((object)("Expand World Data biome compatibility is unavailable; custom no-spoil biome names will be retried. " + exception.GetBaseException().Message)); } } } internal static class SpoilageClock { internal static bool TryParseClockValue(string? value, out long clockValue) { clockValue = 0L; if (value != null && long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out clockValue) && IsValidClockValue(clockValue)) { return string.Equals(value, clockValue.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal); } return false; } internal static bool IsValidClockValue(long clockValue) { if (clockValue != 0L) { return clockValue != long.MinValue; } return false; } internal static bool TryDecodeClockValue(long clockValue, long nowTicks, out long remainingTicks, out bool paused) { remainingTicks = 0L; paused = false; if (!IsValidClockValue(clockValue)) { return false; } if (clockValue < 0) { paused = true; remainingTicks = -clockValue; return true; } remainingTicks = ((nowTicks > 0) ? Math.Max(0L, clockValue - Math.Min(clockValue, nowTicks)) : clockValue); return true; } internal static long EncodeClockValue(long nowTicks, long remainingTicks, bool paused) { long num = Math.Max(0L, Math.Min(long.MaxValue, remainingTicks)); if (paused && num > 0) { return -num; } if (num <= 0) { return Math.Max(1L, nowTicks); } return AddTicksSaturating(Math.Max(0L, nowTicks), num); } internal static long AddTicksSaturating(long left, long right) { long num = Math.Max(0L, left); long num2 = Math.Max(0L, right); if (num2 < long.MaxValue - num) { return num + num2; } return long.MaxValue; } internal static long ComposeClockValues(long destinationClock, long sourceClock, long nowTicks, bool destinationPaused) { if (!TryDecodeClockValue(destinationClock, nowTicks, out var remainingTicks, out var paused) || !TryDecodeClockValue(sourceClock, nowTicks, out var remainingTicks2, out paused)) { return destinationClock; } long num = Math.Min(remainingTicks, remainingTicks2); return EncodeClockValue(nowTicks, num, destinationPaused && num > 0); } } internal enum SpoilageGroup { FarmingHarvest, CookingStationInput, CookingStationOutput, FermentedFood, UnfermentedFood, FeastMaterial, FeastResult, Fish, OtherEdible } internal static class SpoilageDefaults { internal const string RottenMeatPrefabName = "RottenMeat"; internal const string RottenProducePrefabName = "FineDining_RottenProduce"; internal const string RottenFoodPrefabName = "FineDining_RottenFood"; internal static string GetReplacementPrefab(SpoilageGroup group) { return group switch { SpoilageGroup.FarmingHarvest => "FineDining_RottenProduce", SpoilageGroup.CookingStationInput => "RottenMeat", SpoilageGroup.CookingStationOutput => "RottenMeat", SpoilageGroup.Fish => "RottenMeat", SpoilageGroup.FermentedFood => "FineDining_RottenFood", SpoilageGroup.UnfermentedFood => "FineDining_RottenFood", SpoilageGroup.FeastMaterial => "FineDining_RottenFood", SpoilageGroup.FeastResult => "FineDining_RottenFood", SpoilageGroup.OtherEdible => "FineDining_RottenFood", _ => throw new ArgumentOutOfRangeException("group", group, "Unknown spoilage group."), }; } } internal enum SpoilageRuleState { NotReady, NotTracked, Disabled, Enabled } internal readonly struct ResolvedSpoilageRule { internal SpoilageRuleState State { get; } internal long LifetimeTicks { get; } internal string ReplacementPrefab { get; } internal SpoilageGroup Group { get; } internal bool IsOverride { get; } internal ResolvedSpoilageRule(SpoilageRuleState state, long lifetimeTicks = 0L, string replacementPrefab = "", SpoilageGroup group = SpoilageGroup.OtherEdible, bool isOverride = false) { State = state; LifetimeTicks = lifetimeTicks; ReplacementPrefab = replacementPrefab; Group = group; IsOverride = isOverride; } } internal static class SpoilagePolicy { private enum AuthorityMode { Unknown, LocalFiles, SyncedOnly } private sealed class NormalizedPolicy { internal Dictionary Lifetimes { get; } internal Dictionary Overrides { get; } internal HashSet ReplacementPrefabs { get; } internal HashSet ChefChoiceBlacklist { get; } internal NormalizedPolicy(Dictionary lifetimes, Dictionary overrides, HashSet replacementPrefabs, HashSet chefChoiceBlacklist) { Lifetimes = lifetimes; Overrides = overrides; ReplacementPrefabs = replacementPrefabs; ChefChoiceBlacklist = chefChoiceBlacklist; } internal long GetLifetimeTicks(SpoilageGroup group) { return Lifetimes[group].Ticks; } internal double GetLifetimeHours(SpoilageGroup group) { return Lifetimes[group].Hours; } } private sealed class NormalizedItemOverride { internal string PrefabName { get; } internal double Hours { get; } internal long LifetimeTicks { get; } internal string ReplacementPrefab { get; } internal bool HasReplacementOverride { get; } internal NormalizedItemOverride(string prefabName, double hours, long lifetimeTicks, string replacementPrefab, bool hasReplacementOverride) { PrefabName = prefabName; Hours = hours; LifetimeTicks = lifetimeTicks; ReplacementPrefab = replacementPrefab; HasReplacementOverride = hasReplacementOverride; } } private const string PolicyFileName = "Spoilage.yml"; private const string DefaultPolicyResourceName = "FineDining.Resources.Defaults.Spoilage.yml"; private const string SyncedYamlIdentifier = "finedining_spoilage_yaml"; private const int SupportedVersion = 1; private const double MaximumLifetimeHours = 720.0; private const double ReloadDebounceMilliseconds = 350.0; private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).WithDuplicateKeyChecking().Build(); private static readonly ISerializer Serializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull).DisableAliases() .Build(); private static ConfigSync? _configSync; private static CustomSyncedValue? _syncedYaml; private static FileSystemWatcher? _watcher; private static System.Timers.Timer? _reloadTimer; private static AuthorityMode _authorityMode; private static NormalizedPolicy? _policy; private static string? _lastAppliedNormalizedYaml; private static bool _isReady; internal static string ConfigDirectoryPath => Path.Combine(Paths.ConfigPath, "FineDining"); private static string PolicyFilePath => Path.Combine(ConfigDirectoryPath, "Spoilage.yml"); internal static bool IsReady { get { if (_isReady) { return _policy != null; } return false; } } internal static bool IsRuntimeReferenceAuthority { get { ConfigSync? configSync = _configSync; if (configSync != null && configSync.IsSourceOfTruth && (Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } internal static bool IsChefChoiceBlacklisted(string? prefabName) { if (string.IsNullOrWhiteSpace(prefabName)) { return false; } return _policy?.ChefChoiceBlacklist.Contains(prefabName.Trim()) ?? false; } internal static void Initialize(ConfigSync sync) { if (sync == null) { throw new ArgumentNullException("sync"); } Shutdown(); _configSync = sync; _configSync.SourceOfTruthChanged += OnSourceOfTruthChanged; _syncedYaml = new CustomSyncedValue(sync, "finedining_spoilage_yaml", ""); _syncedYaml.ValueChanged += OnSyncedYamlChanged; RefreshAuthority(force: true); } internal static void Shutdown() { DisposeWatcher(); if (_syncedYaml != null) { _syncedYaml.ValueChanged -= OnSyncedYamlChanged; _syncedYaml = null; } if (_configSync != null) { _configSync.SourceOfTruthChanged -= OnSourceOfTruthChanged; _configSync = null; } _authorityMode = AuthorityMode.Unknown; _policy = null; _lastAppliedNormalizedYaml = null; _isReady = false; } internal static void RefreshAuthority(bool force = false) { if (_configSync == null) { return; } AuthorityMode authorityMode = (UsesLocalAuthorityFiles() ? AuthorityMode.LocalFiles : AuthorityMode.SyncedOnly); bool flag = authorityMode != _authorityMode; if (force || flag) { if (flag) { _policy = null; _lastAppliedNormalizedYaml = null; _isReady = false; DecayRuntime.InvalidateAll(); } _authorityMode = authorityMode; switch (authorityMode) { case AuthorityMode.LocalFiles: SetupWatcher(); ReloadFromDiskAndSync(); break; case AuthorityMode.SyncedOnly: DisposeWatcher(); break; } } } internal static ResolvedSpoilageRule Resolve(ItemData? item) { NormalizedPolicy policy = _policy; if (!_isReady || policy == null) { return new ResolvedSpoilageRule(SpoilageRuleState.NotReady, 0L); } if (item?.m_shared == null) { return new ResolvedSpoilageRule(SpoilageRuleState.NotTracked, 0L); } string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(item); if (canonicalPrefabName.Length > 0 && policy.ReplacementPrefabs.Contains(canonicalPrefabName)) { return new ResolvedSpoilageRule(SpoilageRuleState.NotTracked, 0L); } if (canonicalPrefabName.Length > 0 && policy.Overrides.TryGetValue(canonicalPrefabName, out NormalizedItemOverride value)) { if (value.LifetimeTicks == 0L) { return new ResolvedSpoilageRule(SpoilageRuleState.Disabled, 0L, "", SpoilageGroup.OtherEdible, isOverride: true); } SpoilageGroup group = SpoilageGroup.OtherEdible; string replacementPrefab = value.ReplacementPrefab; if (!value.HasReplacementOverride) { if (!FoodClassifier.IsReady) { return new ResolvedSpoilageRule(SpoilageRuleState.NotReady, 0L); } replacementPrefab = (FoodClassifier.TryClassify(item, out group) ? SpoilageDefaults.GetReplacementPrefab(group) : "RottenMeat"); } else if (FoodClassifier.IsReady) { FoodClassifier.TryClassify(item, out group); } return new ResolvedSpoilageRule(SpoilageRuleState.Enabled, value.LifetimeTicks, replacementPrefab, group, isOverride: true); } if (!FoodClassifier.IsReady) { return new ResolvedSpoilageRule(SpoilageRuleState.NotReady, 0L); } if (!FoodClassifier.TryClassify(item, out var group2)) { return new ResolvedSpoilageRule(SpoilageRuleState.NotTracked, 0L); } long lifetimeTicks = policy.GetLifetimeTicks(group2); if (lifetimeTicks == 0L) { return new ResolvedSpoilageRule(SpoilageRuleState.Disabled, 0L, SpoilageDefaults.GetReplacementPrefab(group2), group2); } return new ResolvedSpoilageRule(SpoilageRuleState.Enabled, lifetimeTicks, SpoilageDefaults.GetReplacementPrefab(group2), group2); } internal static bool TryGetReferenceOverrides(out List overrides) { overrides = new List(); NormalizedPolicy policy = _policy; if (!_isReady || policy == null) { return false; } overrides.AddRange(from itemOverride in policy.Overrides.Values.OrderBy((NormalizedItemOverride itemOverride) => itemOverride.PrefabName, StringComparer.OrdinalIgnoreCase).ThenBy((NormalizedItemOverride itemOverride) => itemOverride.PrefabName, StringComparer.Ordinal) select new SpoilagePolicyReferenceOverride(itemOverride.PrefabName, itemOverride.Hours, itemOverride.LifetimeTicks, itemOverride.ReplacementPrefab)); return true; } internal static bool TryGetReferenceLifetimeHours(string prefabName, SpoilageGroup group, bool isOverride, out double hours) { hours = 0.0; NormalizedPolicy policy = _policy; if (!_isReady || policy == null) { return false; } if (isOverride) { if (!policy.Overrides.TryGetValue(prefabName, out NormalizedItemOverride value)) { return false; } hours = value.Hours; return true; } hours = policy.GetLifetimeHours(group); return true; } private static bool UsesLocalAuthorityFiles() { ConfigSync? configSync = _configSync; if (configSync == null || !configSync.IsSourceOfTruth) { return false; } if (ZNet.HasServerHost()) { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } return true; } private static void OnSourceOfTruthChanged(bool _) { RefreshAuthority(force: true); } private static void SetupWatcher() { EnsureLocalPolicyFileExists(); if (_watcher == null) { _reloadTimer = new System.Timers.Timer(350.0) { AutoReset = false, SynchronizingObject = ThreadingHelper.SynchronizingObject }; _reloadTimer.Elapsed += OnReloadTimerElapsed; _watcher = new FileSystemWatcher(ConfigDirectoryPath, "*.yml") { IncludeSubdirectories = false, SynchronizingObject = ThreadingHelper.SynchronizingObject, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime) }; _watcher.Changed += OnPolicyFileChanged; _watcher.Created += OnPolicyFileChanged; _watcher.Deleted += OnPolicyFileChanged; _watcher.Renamed += OnPolicyFileChanged; _watcher.EnableRaisingEvents = true; } } private static void DisposeWatcher() { if (_watcher != null) { _watcher.EnableRaisingEvents = false; _watcher.Dispose(); _watcher = null; } if (_reloadTimer != null) { _reloadTimer.Stop(); _reloadTimer.Elapsed -= OnReloadTimerElapsed; _reloadTimer.Dispose(); _reloadTimer = null; } } private static void OnPolicyFileChanged(object sender, FileSystemEventArgs args) { if (_authorityMode == AuthorityMode.LocalFiles && _reloadTimer != null && IsPolicyFileChange(args)) { _reloadTimer.Stop(); _reloadTimer.Start(); } } private static bool IsPolicyFileChange(FileSystemEventArgs args) { if (IsPolicyFilePath(args.FullPath)) { return true; } if (args is RenamedEventArgs e) { return IsPolicyFilePath(e.OldFullPath); } return false; } private static bool IsPolicyFilePath(string path) { return Path.GetFileName(path).Equals("Spoilage.yml", StringComparison.OrdinalIgnoreCase); } private static void OnReloadTimerElapsed(object sender, ElapsedEventArgs args) { if (_authorityMode == AuthorityMode.LocalFiles) { ReloadFromDiskAndSync(); } } private static void ReloadFromDiskAndSync() { if (_authorityMode != AuthorityMode.LocalFiles) { return; } try { EnsureLocalPolicyFileExists(); ApplyYamlText(File.ReadAllText(PolicyFilePath), publish: true, PolicyFilePath); } catch (Exception ex) { FineDiningPlugin.Log.LogError((object)("Could not reload " + PolicyFilePath + "; keeping the last-known-good spoilage policy. " + ex.GetBaseException().Message)); } } private static void ApplyCurrentSyncedYaml() { string text = _syncedYaml?.Value ?? ""; if (!string.IsNullOrWhiteSpace(text)) { ApplyYamlText(text, publish: false, "server-synced spoilage policy"); } } private static void OnSyncedYamlChanged() { if (_authorityMode == AuthorityMode.SyncedOnly) { ApplyCurrentSyncedYaml(); } } private static void ApplyYamlText(string yamlText, bool publish, string source) { if (!TryParseAndNormalize(yamlText, out NormalizedPolicy policy, out string normalizedYaml, out string error)) { FineDiningPlugin.Log.LogError((object)("Could not parse " + source + "; keeping the last-known-good spoilage policy. " + error)); return; } CommitPolicy(policy, normalizedYaml); if (publish && _syncedYaml != null && !string.Equals(_syncedYaml.Value ?? "", normalizedYaml, StringComparison.Ordinal)) { _syncedYaml.AssignLocalValue(normalizedYaml); } } private static void CommitPolicy(NormalizedPolicy policy, string normalizedYaml) { if (string.Equals(normalizedYaml, _lastAppliedNormalizedYaml, StringComparison.Ordinal) && _policy != null) { _isReady = true; return; } _policy = policy; _lastAppliedNormalizedYaml = normalizedYaml; _isReady = true; DecayRuntime.InvalidateAll(); SpoilageReferenceGenerator.Invalidate(); DietModule.RequestChefCollectionReconcile(); FineDiningPlugin.Log.LogInfo((object)($"Applied FineDining policy with {policy.Overrides.Count} spoilage override(s) and " + $"{policy.ChefChoiceBlacklist.Count} Chef Choice blacklist entry/entries.")); } private static bool TryParseAndNormalize(string yamlText, out NormalizedPolicy? policy, out string normalizedYaml, out string error) { policy = null; normalizedYaml = ""; error = ""; try { if (string.IsNullOrWhiteSpace(yamlText)) { throw new InvalidDataException("The policy document cannot be empty."); } SpoilageYamlDocument spoilageYamlDocument = Deserializer.Deserialize(yamlText) ?? throw new InvalidDataException("The policy document cannot be null."); if (spoilageYamlDocument.Version != 1) { throw new InvalidDataException($"version must be {1}; found {spoilageYamlDocument.Version}."); } SpoilageYamlLifetimes? obj = spoilageYamlDocument.Lifetimes ?? throw new InvalidDataException("lifetimes is required."); double num = RequireHours(obj.FarmingHarvest, "lifetimes.farmingHarvest"); double num2 = RequireHours(obj.CookingStationInput, "lifetimes.cookingStationInput"); double num3 = RequireHours(obj.CookingStationOutput, "lifetimes.cookingStationOutput"); double num4 = RequireHours(obj.UnfermentedFood, "lifetimes.unfermentedFood"); double num5 = RequireHours(obj.FermentedFood, "lifetimes.fermentedFood"); double num6 = RequireHours(obj.FeastMaterial, "lifetimes.feastMaterial"); double num7 = RequireHours(obj.FeastResult, "lifetimes.feastResult"); double num8 = RequireHours(obj.Fish, "lifetimes.fish"); double num9 = RequireHours(obj.OtherEdible, "lifetimes.otherEdible"); Dictionary lifetimes = new Dictionary { [SpoilageGroup.FarmingHarvest] = (num, HoursToTicks(num)), [SpoilageGroup.CookingStationInput] = (num2, HoursToTicks(num2)), [SpoilageGroup.CookingStationOutput] = (num3, HoursToTicks(num3)), [SpoilageGroup.UnfermentedFood] = (num4, HoursToTicks(num4)), [SpoilageGroup.FermentedFood] = (num5, HoursToTicks(num5)), [SpoilageGroup.FeastMaterial] = (num6, HoursToTicks(num6)), [SpoilageGroup.FeastResult] = (num7, HoursToTicks(num7)), [SpoilageGroup.Fish] = (num8, HoursToTicks(num8)), [SpoilageGroup.OtherEdible] = (num9, HoursToTicks(num9)) }; if (spoilageYamlDocument.Overrides == null) { throw new InvalidDataException("overrides is required and must be a sequence."); } Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (string @override in spoilageYamlDocument.Overrides) { NormalizedItemOverride normalizedItemOverride = NormalizeOverride(@override); if (dictionary.ContainsKey(normalizedItemOverride.PrefabName)) { throw new InvalidDataException("Duplicate override entry '" + normalizedItemOverride.PrefabName + "'."); } dictionary.Add(normalizedItemOverride.PrefabName, normalizedItemOverride); } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string item in spoilageYamlDocument.ChefChoiceBlacklist ?? new List()) { string text = RequirePrefab(item, "chefChoiceBlacklist entry"); if (!hashSet.Add(text)) { throw new InvalidDataException("Duplicate Chef Choice blacklist entry '" + text + "'."); } } HashSet hashSet2 = new HashSet(StringComparer.OrdinalIgnoreCase) { "RottenMeat", "FineDining_RottenProduce", "FineDining_RottenFood" }; foreach (NormalizedItemOverride value in dictionary.Values) { if (value.LifetimeTicks > 0) { hashSet2.Add(value.ReplacementPrefab); } } foreach (NormalizedItemOverride value2 in dictionary.Values) { if (value2.LifetimeTicks > 0 && hashSet2.Contains(value2.PrefabName)) { throw new InvalidDataException("Positive override source '" + value2.PrefabName + "' is also configured as a replacement terminal."); } } policy = new NormalizedPolicy(lifetimes, dictionary, hashSet2, hashSet); SpoilageYamlDocument graph = new SpoilageYamlDocument { Version = 1, Lifetimes = new SpoilageYamlLifetimes { FarmingHarvest = num, CookingStationInput = num2, CookingStationOutput = num3, UnfermentedFood = num4, FermentedFood = num5, FeastMaterial = num6, FeastResult = num7, Fish = num8, OtherEdible = num9 }, ChefChoiceBlacklist = hashSet.OrderBy((string prefab) => prefab, StringComparer.OrdinalIgnoreCase).ThenBy((string prefab) => prefab, StringComparer.Ordinal).ToList(), Overrides = dictionary.Values.OrderBy((NormalizedItemOverride entry) => entry.PrefabName, StringComparer.OrdinalIgnoreCase).ThenBy((NormalizedItemOverride entry) => entry.PrefabName, StringComparer.Ordinal).Select(FormatOverride) .ToList() }; normalizedYaml = CanonicalizeYaml(Serializer.Serialize(graph)); return true; } catch (Exception ex) { error = ex.GetBaseException().Message; return false; } } private static NormalizedItemOverride NormalizeOverride(string? rawEntry) { if (string.IsNullOrWhiteSpace(rawEntry)) { throw new InvalidDataException("Override entries cannot be null or empty."); } string[] array = rawEntry.Split(new char[1] { ',' }, StringSplitOptions.None); int num = array.Length; if ((num < 2 || num > 3) ? true : false) { throw new InvalidDataException("Override '" + rawEntry + "' must be ', [, ]'."); } string text = RequirePrefab(array[0], "override prefab"); double num2 = ParseHours(array[1], "Override '" + text + "' hours"); bool flag = array.Length == 3; if (num2 == 0.0 && flag) { throw new InvalidDataException("Disabled override '" + text + "' cannot specify a replacement prefab."); } string text2 = (flag ? RequirePrefab(array[2], "Override '" + text + "' replacement") : "RottenMeat"); if (num2 > 0.0 && text.Equals(text2, StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException("Override '" + text + "' cannot replace an item with itself."); } return new NormalizedItemOverride(text, num2, HoursToTicks(num2), text2, flag); } private static string FormatOverride(NormalizedItemOverride itemOverride) { string text = itemOverride.PrefabName + ", " + FormatHours(itemOverride.Hours); if (!itemOverride.HasReplacementOverride) { return text; } return text + ", " + itemOverride.ReplacementPrefab; } private static double RequireHours(double? value, string context) { if (!value.HasValue) { throw new InvalidDataException(context + " is required."); } return ValidateHours(value.Value, context); } private static double ParseHours(string? value, string context) { if (!double.TryParse(value?.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { throw new InvalidDataException(context + " must be a number from 0 through " + FormatHours(720.0) + "."); } return ValidateHours(result, context); } private static double ValidateHours(double value, string context) { if (double.IsNaN(value) || value < 0.0 || value > 720.0) { throw new InvalidDataException(context + " must be a finite value from 0 through " + FormatHours(720.0) + "."); } if (value != 0.0) { return value; } return 0.0; } private static long HoursToTicks(double hours) { if (hours <= 0.0) { return 0L; } long val = checked((long)Math.Ceiling(hours * 36000000000.0)); return Math.Max(10000000L, val); } private static string RequirePrefab(string? value, string context) { if (string.IsNullOrWhiteSpace(value)) { throw new InvalidDataException(context + " requires a prefab name."); } string text = value.Trim(); if (text.Any(char.IsControl)) { throw new InvalidDataException(context + " cannot contain control characters."); } if (text.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase) >= 0) { throw new InvalidDataException(context + " must use the registered prefab name without Unity's '(Clone)' marker."); } return text; } private static string FormatHours(double hours) { return hours.ToString("R", CultureInfo.InvariantCulture); } private static string CanonicalizeYaml(string yaml) { return yaml.Replace("\r\n", "\n").Replace('\r', '\n').TrimEnd(new char[1] { '\n' }) + "\n"; } private static void EnsureLocalPolicyFileExists() { Directory.CreateDirectory(ConfigDirectoryPath); if (File.Exists(PolicyFilePath)) { return; } using Stream stream = typeof(SpoilagePolicy).Assembly.GetManifestResourceStream("FineDining.Resources.Defaults.Spoilage.yml") ?? throw new InvalidOperationException("Embedded default YAML resource 'FineDining.Resources.Defaults.Spoilage.yml' was not found."); using StreamReader streamReader = new StreamReader(stream); File.WriteAllText(PolicyFilePath, streamReader.ReadToEnd()); FineDiningPlugin.Log.LogInfo((object)("Created default spoilage policy: " + PolicyFilePath)); } } internal readonly struct SpoilagePolicyReferenceOverride { internal string PrefabName { get; } internal double Hours { get; } internal long LifetimeTicks { get; } internal string ReplacementPrefab { get; } internal SpoilagePolicyReferenceOverride(string prefabName, double hours, long lifetimeTicks, string replacementPrefab) { PrefabName = prefabName; Hours = hours; LifetimeTicks = lifetimeTicks; ReplacementPrefab = replacementPrefab; } } internal sealed class SpoilageYamlDocument { public int Version { get; set; } public SpoilageYamlLifetimes? Lifetimes { get; set; } public List? ChefChoiceBlacklist { get; set; } public List? Overrides { get; set; } } internal sealed class SpoilageYamlLifetimes { public double? FarmingHarvest { get; set; } public double? CookingStationInput { get; set; } public double? CookingStationOutput { get; set; } public double? UnfermentedFood { get; set; } public double? FermentedFood { get; set; } public double? FeastMaterial { get; set; } public double? FeastResult { get; set; } public double? Fish { get; set; } public double? OtherEdible { get; set; } } internal sealed class SpoilageReferenceEntry { internal string PrefabName { get; } internal string OwnerName { get; } internal SpoilageGroup? Group { get; } internal bool? OverrideEnabled { get; } internal double LifetimeHours { get; } internal string ReplacementPrefab { get; } internal SpoilageReferenceEntry(string prefabName, string ownerName, SpoilageGroup? group, bool? overrideEnabled, double lifetimeHours, string replacementPrefab) { PrefabName = FoodIdentity.NormalizePrefabName(prefabName); OwnerName = FoodPrefabOwnerResolver.NormalizeOwnerName(ownerName); Group = group; OverrideEnabled = overrideEnabled; LifetimeHours = ((lifetimeHours <= 0.0) ? 0.0 : lifetimeHours); ReplacementPrefab = FoodIdentity.NormalizePrefabName(replacementPrefab); } } internal static class SpoilageReferenceGenerator { private readonly struct ReferenceGeneration { internal bool Changed { get; } internal bool HasUnknownOwner { get; } internal int EntryCount { get; } internal ReferenceGeneration(bool changed, bool hasUnknownOwner, int entryCount) { Changed = changed; HasUnknownOwner = hasUnknownOwner; EntryCount = entryCount; } } internal const string ReferenceFileName = "Spoilage.reference.yml"; private const float ReadyRetrySeconds = 1f; private const float FailureRetrySeconds = 5f; private const float ExistenceCheckSeconds = 5f; private const int OwnerResolutionRetryCount = 3; private static readonly (SpoilageGroup? Group, bool? OverrideEnabled, string Label)[] SectionOrder = new(SpoilageGroup?, bool?, string)[11] { (SpoilageGroup.FarmingHarvest, null, "automatic: farmingHarvest"), (SpoilageGroup.FeastMaterial, null, "automatic: feastMaterial"), (SpoilageGroup.FeastResult, null, "automatic: feastResult"), (SpoilageGroup.FermentedFood, null, "automatic: fermentedFood"), (SpoilageGroup.CookingStationOutput, null, "automatic: cookingStationOutput"), (SpoilageGroup.CookingStationInput, null, "automatic: cookingStationInput"), (SpoilageGroup.Fish, null, "automatic: fish"), (SpoilageGroup.UnfermentedFood, null, "automatic: unfermentedFood"), (SpoilageGroup.OtherEdible, null, "automatic: otherEdible"), (null, true, "exact overrides: enabled"), (null, false, "exact overrides: disabled") }; private static bool _dirty = true; private static bool _failureLogged; private static float _nextAttemptAt; private static float _nextExistenceCheckAt; private static int _ownerResolutionRetriesRemaining = 3; private static string ReferenceFilePath => Path.Combine(SpoilagePolicy.ConfigDirectoryPath, "Spoilage.reference.yml"); internal static void Tick() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!_dirty) { if (realtimeSinceStartup < _nextExistenceCheckAt) { return; } _nextExistenceCheckAt = realtimeSinceStartup + 5f; if (!SpoilagePolicy.IsRuntimeReferenceAuthority || File.Exists(ReferenceFilePath)) { return; } _dirty = true; _ownerResolutionRetriesRemaining = 3; } if (realtimeSinceStartup < _nextAttemptAt) { return; } _nextAttemptAt = realtimeSinceStartup + 1f; if (!SpoilagePolicy.IsRuntimeReferenceAuthority || !SpoilagePolicy.IsReady || !FoodClassifier.IsReady || !_dirty) { return; } if (!TryGenerateCurrentReference(out ReferenceGeneration result, out string error)) { _nextAttemptAt = Time.realtimeSinceStartup + 5f; if (!_failureLogged) { _failureLogged = true; FineDiningPlugin.Log.LogWarning((object)("Could not generate " + ReferenceFilePath + "; FineDining will retry: " + error)); } return; } if (result.HasUnknownOwner && _ownerResolutionRetriesRemaining > 0) { _ownerResolutionRetriesRemaining--; _dirty = true; _nextAttemptAt = realtimeSinceStartup + 5f; } else { _dirty = false; _ownerResolutionRetriesRemaining = 0; _nextExistenceCheckAt = realtimeSinceStartup + 5f; } _failureLogged = false; if (result.Changed) { FineDiningPlugin.Log.LogInfo((object)("Updated generated spoilage reference with " + result.EntryCount + " classified prefab(s): " + ReferenceFilePath)); } } internal static void Invalidate() { ResetGenerationState(resetFailureLog: false); } internal static void Reset() { ResetGenerationState(resetFailureLog: true); } private static bool TryGenerateCurrentReference(out ReferenceGeneration result, out string error) { result = default(ReferenceGeneration); if (!SpoilagePolicy.IsReady) { error = "The synchronized spoilage policy is not ready yet."; return false; } if (!FoodClassifier.IsReady) { error = "The food classifier is not ready yet. Wait until world loading finishes."; return false; } try { List list = CaptureReferenceEntries(); bool changed = WriteTextIfChanged(ReferenceFilePath, BuildReferenceContent(list)); result = new ReferenceGeneration(changed, list.Any((SpoilageReferenceEntry entry) => entry.OwnerName.Equals("Unknown / Untracked", StringComparison.OrdinalIgnoreCase)), list.Count); error = string.Empty; return true; } catch (Exception ex) { error = ex.GetBaseException().Message; return false; } } private static void ResetGenerationState(bool resetFailureLog) { _dirty = true; if (resetFailureLog) { _failureLogged = false; } _nextAttemptAt = 0f; _nextExistenceCheckAt = 0f; _ownerResolutionRetriesRemaining = 3; } internal static string BuildReferenceContent(IEnumerable sourceEntries) { List list = NormalizeFinalEntries(sourceEntries).ToList(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("# Generated by FineDining ").Append("1.0.0").AppendLine(". This file is overwritten automatically."); stringBuilder.AppendLine("# It is a local lookup only; it is not loaded as configuration or synchronized to clients."); stringBuilder.AppendLine("# Copy selected '- Prefab, hours[, replacement]' rows under 'overrides:' in Spoilage.yml."); stringBuilder.AppendLine("# Classification is primary; prefab owner is the secondary comment section."); (SpoilageGroup?, bool?, string)[] sectionOrder = SectionOrder; for (int i = 0; i < sectionOrder.Length; i++) { var (group, overrideEnabled, value) = sectionOrder[i]; stringBuilder.AppendLine(); stringBuilder.Append("# ===== ").Append(value).AppendLine(" ====="); List list2 = (from entry in list where IsInSection(entry, @group, overrideEnabled) orderby FoodPrefabOwnerResolver.GetOwnerSortBucket(entry.OwnerName) select entry).ThenBy((SpoilageReferenceEntry entry) => entry.OwnerName, StringComparer.OrdinalIgnoreCase).ThenBy((SpoilageReferenceEntry entry) => entry.OwnerName, StringComparer.Ordinal).ThenBy((SpoilageReferenceEntry entry) => entry.PrefabName, StringComparer.OrdinalIgnoreCase) .ThenBy((SpoilageReferenceEntry entry) => entry.PrefabName, StringComparer.Ordinal) .ToList(); if (list2.Count == 0) { stringBuilder.AppendLine("# (none)"); continue; } foreach (IGrouping item in list2.GroupBy((SpoilageReferenceEntry entry) => entry.OwnerName, StringComparer.OrdinalIgnoreCase)) { stringBuilder.Append("# ----- ").Append(FoodPrefabOwnerResolver.NormalizeOwnerName(item.Key)).AppendLine(" -----"); foreach (SpoilageReferenceEntry item2 in item) { stringBuilder.Append("- ").Append(FormatYamlScalar(FormatCompactOverride(item2))).AppendLine(); } } } if (list.Count == 0) { stringBuilder.AppendLine(); stringBuilder.AppendLine("[]"); } return Canonicalize(stringBuilder.ToString()); } internal static bool WriteTextIfChanged(string path, string content) { string directoryName = Path.GetDirectoryName(path); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } string text = Canonicalize(content ?? ""); if (File.Exists(path) && string.Equals(File.ReadAllText(path), text, StringComparison.Ordinal)) { return false; } File.WriteAllText(path, text, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); return true; } private static List CaptureReferenceEntries() { ObjectDB instance = ObjectDB.instance; if (instance?.m_items == null) { throw new InvalidOperationException("ObjectDB item prefabs are not ready."); } Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (GameObject item in instance.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent(); if (component?.m_itemData?.m_shared != null) { string text = FoodIdentity.NormalizePrefabName(((Object)item).name); if (text.Length > 0 && !dictionary.ContainsKey(text)) { dictionary.Add(text, component); } } } List<(string, SpoilageGroup?, bool?, double, string)> list = new List<(string, SpoilageGroup?, bool?, double, string)>(); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair item2 in dictionary) { ResolvedSpoilageRule resolvedSpoilageRule = SpoilagePolicy.Resolve(item2.Value.m_itemData); SpoilageRuleState state = resolvedSpoilageRule.State; if ((uint)(state - 2) <= 1u) { if (!SpoilagePolicy.TryGetReferenceLifetimeHours(item2.Key, resolvedSpoilageRule.Group, resolvedSpoilageRule.IsOverride, out var hours)) { throw new InvalidOperationException("Could not resolve normalized lifetime hours for '" + item2.Key + "'."); } list.Add((FoodIdentity.NormalizePrefabName(item2.Key), resolvedSpoilageRule.IsOverride ? ((SpoilageGroup?)null) : new SpoilageGroup?(resolvedSpoilageRule.Group), resolvedSpoilageRule.IsOverride ? new bool?(resolvedSpoilageRule.State == SpoilageRuleState.Enabled) : ((bool?)null), (hours <= 0.0) ? 0.0 : hours, FoodIdentity.NormalizePrefabName((resolvedSpoilageRule.State == SpoilageRuleState.Enabled) ? resolvedSpoilageRule.ReplacementPrefab : ""))); hashSet.Add(item2.Key); } } if (!SpoilagePolicy.TryGetReferenceOverrides(out List overrides)) { throw new InvalidOperationException("The normalized spoilage policy is not ready."); } foreach (SpoilagePolicyReferenceOverride item3 in overrides) { if (!hashSet.Contains(item3.PrefabName)) { list.Add((FoodIdentity.NormalizePrefabName(item3.PrefabName), null, item3.LifetimeTicks > 0, (item3.Hours <= 0.0) ? 0.0 : item3.Hours, FoodIdentity.NormalizePrefabName((item3.LifetimeTicks > 0) ? item3.ReplacementPrefab : ""))); } } FoodPrefabOwnerSnapshot ownerSnapshot = FoodPrefabOwnerResolver.GetSnapshot(list.Select<(string, SpoilageGroup?, bool?, double, string), string>(((string PrefabName, SpoilageGroup? Group, bool? OverrideEnabled, double LifetimeHours, string ReplacementPrefab) candidate) => candidate.PrefabName)); return list.Select<(string, SpoilageGroup?, bool?, double, string), SpoilageReferenceEntry>(((string PrefabName, SpoilageGroup? Group, bool? OverrideEnabled, double LifetimeHours, string ReplacementPrefab) candidate) => new SpoilageReferenceEntry(candidate.PrefabName, ownerSnapshot.GetOwnerName(candidate.PrefabName), candidate.Group, candidate.OverrideEnabled, candidate.LifetimeHours, candidate.ReplacementPrefab)).ToList(); } private static IEnumerable NormalizeFinalEntries(IEnumerable sourceEntries) { return from @group in (sourceEntries ?? Enumerable.Empty()).Where((SpoilageReferenceEntry entry) => entry != null && entry.PrefabName.Length > 0).GroupBy((SpoilageReferenceEntry entry) => entry.PrefabName, StringComparer.OrdinalIgnoreCase) select @group.OrderByDescending(GetOverridePriority).ThenBy(GetSectionIndex).ThenBy((SpoilageReferenceEntry entry) => entry.OwnerName, StringComparer.OrdinalIgnoreCase) .ThenBy((SpoilageReferenceEntry entry) => entry.OwnerName, StringComparer.Ordinal) .ThenBy((SpoilageReferenceEntry entry) => entry.LifetimeHours) .ThenBy((SpoilageReferenceEntry entry) => entry.ReplacementPrefab, StringComparer.OrdinalIgnoreCase) .ThenBy((SpoilageReferenceEntry entry) => entry.ReplacementPrefab, StringComparer.Ordinal) .ThenBy((SpoilageReferenceEntry entry) => entry.PrefabName, StringComparer.OrdinalIgnoreCase) .ThenBy((SpoilageReferenceEntry entry) => entry.PrefabName, StringComparer.Ordinal) .First(); } private static int GetOverridePriority(SpoilageReferenceEntry entry) { if (!entry.OverrideEnabled.HasValue) { return 0; } if (!entry.OverrideEnabled.Value) { return 2; } return 1; } private static int GetSectionIndex(SpoilageReferenceEntry entry) { for (int i = 0; i < SectionOrder.Length; i++) { var (spoilageGroup, overrideEnabled, _) = SectionOrder[i]; if (IsInSection(entry, spoilageGroup, overrideEnabled)) { return i; } } return int.MaxValue; } private static bool IsInSection(SpoilageReferenceEntry entry, SpoilageGroup? group, bool? overrideEnabled) { if (!group.HasValue) { if (overrideEnabled.HasValue) { return entry.OverrideEnabled == overrideEnabled; } return false; } if (!entry.OverrideEnabled.HasValue) { return entry.Group == group; } return false; } private static string FormatCompactOverride(SpoilageReferenceEntry entry) { string text = entry.PrefabName + ", " + FormatHours(entry.LifetimeHours); if (!(entry.LifetimeHours > 0.0) || entry.ReplacementPrefab.Length <= 0) { return text; } return text + ", " + entry.ReplacementPrefab; } private static string FormatHours(double lifetimeHours) { if (lifetimeHours <= 0.0) { return "0"; } return lifetimeHours.ToString("R", CultureInfo.InvariantCulture); } private static string FormatYamlScalar(string value) { if (value.Length > 0 && value.All(delegate(char character) { bool flag = char.IsLetterOrDigit(character); if (!flag) { bool flag2; switch (character) { case ' ': case ',': case '-': case '.': case '_': flag2 = true; break; default: flag2 = false; break; } flag = flag2; } return flag; })) { return value; } return "\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; } private static string Canonicalize(string value) { return (value ?? "").Replace("\r\n", "\n").Replace('\r', '\n').TrimEnd(new char[1] { '\n' }) + "\n"; } } internal static class AzuCraftyBoxesCompatibility { internal readonly struct ContainerReference { internal object Container { get; } internal string PrefabName { get; } internal ContainerReference(object container, string prefabName) { Container = container; PrefabName = prefabName; } } internal sealed class NearbyContainerQuery { private readonly IReadOnlyList _containers; private readonly MethodInfo _countItemInContainer; private readonly MethodInfo _canItemBePulled; private readonly Dictionary _counts = new Dictionary(StringComparer.Ordinal); private bool _failed; internal NearbyContainerQuery(IReadOnlyList containers, MethodInfo countItemInContainer, MethodInfo canItemBePulled) { _containers = containers; _countItemInContainer = countItemInContainer; _canItemBePulled = canItemBePulled; } internal int CountAvailable(string itemPrefab, string sharedName) { if (_failed || string.IsNullOrEmpty(itemPrefab) || string.IsNullOrEmpty(sharedName)) { return 0; } string key = itemPrefab + "\n" + sharedName; if (_counts.TryGetValue(key, out var value)) { return value; } try { long num = 0L; foreach (ContainerReference container in _containers) { if ((string.IsNullOrEmpty(container.PrefabName) || InvokeCanItemBePulled(_canItemBePulled, container.PrefabName, itemPrefab)) && _countItemInContainer.Invoke(null, new object[2] { container.Container, sharedName }) is int num2 && num2 > 0) { num = Math.Min(2147483647L, num + num2); } } int num3 = (int)num; _counts[key] = num3; return num3; } catch (Exception exception) { _failed = true; Disable("AzuCraftyBoxes nearby count failed.", exception); return 0; } } } internal const string PluginGuid = "Azumatt.AzuCraftyBoxes"; internal const string ContainerRangeSection = "2 - CraftyBoxes"; internal const string ContainerRangeKey = "Container Range"; private const float MissingPluginRetrySeconds = 5f; private static MethodInfo? _getNearbyContainersDefinition; private static MethodInfo? _countItemInContainer; private static MethodInfo? _canItemBePulled; private static MethodInfo? _getContainerPrefabName; private static ConfigEntry? _containerRange; private static bool _initialized; private static bool _isReady; private static bool _broken; private static float _nextLookupTime; internal static void Initialize() { _nextLookupTime = 0f; TryInitialize(); } internal static void Shutdown() { _getNearbyContainersDefinition = null; _countItemInContainer = null; _canItemBePulled = null; _getContainerPrefabName = null; _containerRange = null; _initialized = false; _isReady = false; _broken = false; _nextLookupTime = 0f; } internal static NearbyContainerQuery? CreateNearbyQuery(Component source) { if ((Object)(object)source == (Object)null || !EnsureReady()) { return null; } MethodInfo getNearbyContainersDefinition = _getNearbyContainersDefinition; MethodInfo countItemInContainer = _countItemInContainer; MethodInfo canItemBePulled = _canItemBePulled; MethodInfo getContainerPrefabName = _getContainerPrefabName; ConfigEntry containerRange = _containerRange; if (getNearbyContainersDefinition == null || countItemInContainer == null || canItemBePulled == null || getContainerPrefabName == null || containerRange == null) { return null; } try { if (!(getNearbyContainersDefinition.MakeGenericMethod(((object)source).GetType()).Invoke(null, new object[2] { source, containerRange.Value }) is IEnumerable enumerable)) { return null; } List list = new List(); foreach (object item in enumerable) { if (item != null) { string prefabName = (getContainerPrefabName.Invoke(item, Array.Empty()) as string) ?? string.Empty; list.Add(new ContainerReference(item, prefabName)); } } return new NearbyContainerQuery(list, countItemInContainer, canItemBePulled); } catch (Exception exception) { Disable("AzuCraftyBoxes nearby-container query failed.", exception); return null; } } internal static bool CanItemBePulled(string ownerPrefab, string itemPrefab) { if (string.IsNullOrEmpty(ownerPrefab) || string.IsNullOrEmpty(itemPrefab) || !EnsureReady()) { return true; } MethodInfo canItemBePulled = _canItemBePulled; if (canItemBePulled == null) { return true; } try { return InvokeCanItemBePulled(canItemBePulled, ownerPrefab, itemPrefab); } catch (Exception exception) { Disable("AzuCraftyBoxes item filter failed.", exception); return true; } } private static bool EnsureReady() { if (_isReady) { return true; } TryInitialize(); return _isReady; } private static void TryInitialize() { if (_initialized || _broken || Time.unscaledTime < _nextLookupTime) { return; } try { Type type = Type.GetType("AzuCraftyBoxes.API, AzuCraftyBoxes"); if (type == null) { _nextLookupTime = Time.unscaledTime + 5f; return; } _initialized = true; Type type2 = type.Assembly.GetType("AzuCraftyBoxes.IContainers.IContainer", throwOnError: false); if (type2 == null) { Disable("AzuCraftyBoxes compatibility disabled because IContainer was not found."); return; } MethodInfo methodInfo = FindGetNearbyContainers(type, type2); MethodInfo method = type.GetMethod("CountItemInContainer", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { type2, typeof(string) }, null); MethodInfo method2 = type.GetMethod("CanItemBePulled", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { typeof(string), typeof(string) }, null); MethodInfo method3 = type2.GetMethod("GetPrefabName", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); ConfigEntry val = TryGetContainerRangeEntry(); if (methodInfo == null || method == null || method.IsGenericMethod || method.ReturnType != typeof(int) || method2 == null || method2.IsGenericMethod || method2.ReturnType != typeof(bool) || method3 == null || method3.IsGenericMethod || method3.ReturnType != typeof(string) || val == null) { Disable("AzuCraftyBoxes compatibility disabled because the expected API or synchronized Container Range config was not found."); return; } _getNearbyContainersDefinition = methodInfo; _countItemInContainer = method; _canItemBePulled = method2; _getContainerPrefabName = method3; _containerRange = val; _isReady = true; FineDiningPlugin.Log.LogInfo((object)"AzuCraftyBoxes station-hint compatibility enabled."); } catch (Exception exception) { Disable("AzuCraftyBoxes compatibility initialization failed.", exception); } } private static ConfigEntry? TryGetContainerRangeEntry() { if (!Chainloader.PluginInfos.TryGetValue("Azumatt.AzuCraftyBoxes", out var value) || (Object)(object)value.Instance == (Object)null) { return null; } ConfigEntry result = default(ConfigEntry); if (!value.Instance.Config.TryGetEntry("2 - CraftyBoxes", "Container Range", ref result)) { return null; } return result; } private static MethodInfo? FindGetNearbyContainers(Type apiType, Type containerType) { MethodInfo methodInfo = null; MethodInfo[] methods = apiType.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo2 in methods) { if (methodInfo2.Name != "GetNearbyContainers" || !methodInfo2.IsGenericMethodDefinition || methodInfo2.GetGenericArguments().Length != 1) { continue; } Type type = methodInfo2.GetGenericArguments()[0]; Type[] genericParameterConstraints = type.GetGenericParameterConstraints(); ParameterInfo[] parameters = methodInfo2.GetParameters(); Type returnType = methodInfo2.ReturnType; if (genericParameterConstraints.Length == 1 && !(genericParameterConstraints[0] != typeof(Component)) && parameters.Length == 2 && !(parameters[0].ParameterType != type) && !(parameters[1].ParameterType != typeof(float)) && returnType.IsGenericType && !(returnType.GetGenericTypeDefinition() != typeof(List<>)) && !(returnType.GetGenericArguments()[0] != containerType)) { if (methodInfo != null) { return null; } methodInfo = methodInfo2; } } return methodInfo; } private static bool InvokeCanItemBePulled(MethodInfo method, string ownerPrefab, string itemPrefab) { object obj = method.Invoke(null, new object[2] { ownerPrefab, itemPrefab }); if (obj is bool) { return (bool)obj; } throw new InvalidOperationException("AzuCraftyBoxes CanItemBePulled returned an unexpected value."); } private static void Disable(string message, Exception? exception = null) { _isReady = false; _broken = true; _getNearbyContainersDefinition = null; _countItemInContainer = null; _canItemBePulled = null; _getContainerPrefabName = null; _containerRange = null; FineDiningPlugin.Log.LogWarning((object)((exception == null) ? message : (message + " " + exception.GetBaseException().Message))); } } internal static class CookingProgressResolver { private enum CookingStatus { NotDone, Done, Burnt } internal static IReadOnlyList GetCandidates(CookingStation station, int maxCandidates) { if (station.m_slots == null || station.m_slots.Length == 0 || maxCandidates <= 0 || !TryGetZdo((Component)(object)station, out ZDO zdo)) { return Array.Empty(); } List list = new List(Math.Min(station.m_slots.Length, maxCandidates)); for (int i = 0; i < station.m_slots.Length; i++) { string text = zdo.GetString("slot" + i, ""); if (string.IsNullOrEmpty(text)) { continue; } CookingStatus cookingStatus = (CookingStatus)zdo.GetInt("slotstatus" + i, 0); if (cookingStatus == CookingStatus.Burnt && text == "Coal") { continue; } ItemConversion val = FindConversion(station, text); float num = zdo.GetFloat("slot" + i, 0f); ItemDrop item; string text2; switch (cookingStatus) { case CookingStatus.NotDone: item = FindItemPrefab(text) ?? val?.m_from; text2 = ((val != null && val.m_cookTime > 0f) ? StationText.FormatSeconds(Math.Max(0.0, val.m_cookTime - num), keepAtLeastOneSecond: true) : string.Empty); break; case CookingStatus.Done: item = FindItemPrefab(text) ?? val?.m_to; text2 = ((val != null && val.m_cookTime > 0f && (Object)(object)station.m_overCookedItem != (Object)null) ? StationText.FormatSeconds(Math.Max(0.0, (double)val.m_cookTime * 2.0 - (double)num), keepAtLeastOneSecond: true) : StationText.FormatSeconds(0.0)); break; default: item = FindItemPrefab(text) ?? station.m_overCookedItem; text2 = StationText.FormatSeconds(0.0); break; } bool flag = !string.IsNullOrEmpty(text2) && cookingStatus != CookingStatus.Burnt && CookingStationAutoPopSystem.ShouldShowAutoEject(station, i, text, ((Object)(object)val?.m_to != (Object)null) ? Utils.GetPrefabName(((Component)val.m_to).gameObject) : string.Empty, cookingStatus == CookingStatus.Done); text2 = StationText.ColorizeTimer(text2); StationHintCandidate stationHintCandidate = CreateCandidate(item, text2, flag ? StationText.AutoEjectLabel : string.Empty); if (stationHintCandidate != null) { list.Add(stationHintCandidate); if (list.Count >= maxCandidates) { break; } } } return list; } private static ItemConversion? FindConversion(CookingStation station, string itemName) { if (station.m_conversion == null) { return null; } foreach (ItemConversion item in station.m_conversion) { if (item != null && (MatchesPrefab(item.m_from, itemName) || MatchesPrefab(item.m_to, itemName))) { return item; } } return null; } private static bool MatchesPrefab(ItemDrop? item, string prefabName) { if ((Object)(object)item != (Object)null) { return Utils.GetPrefabName(((Component)item).gameObject) == prefabName; } return false; } private static ItemDrop? FindItemPrefab(string prefabName) { GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(prefabName) : null); if ((Object)(object)val == (Object)null && (Object)(object)ZNetScene.instance != (Object)null) { val = ZNetScene.instance.GetPrefab(prefabName); } if (!((Object)(object)val != (Object)null)) { return null; } return val.GetComponent(); } private static StationHintCandidate? CreateCandidate(ItemDrop? item, string timerText, string secondaryStatusText) { if ((Object)(object)item == (Object)null || item.m_itemData == null || item.m_itemData.m_shared == null) { return null; } string name = item.m_itemData.m_shared.m_name; if (string.IsNullOrWhiteSpace(name)) { return null; } Sprite icon = null; Sprite[] icons = item.m_itemData.m_shared.m_icons; if (icons != null && icons.Length != 0) { icon = icons[0]; } return new StationHintCandidate((Localization.instance != null) ? Localization.instance.Localize(name) : name, icon, timerText, secondaryStatusText); } private static bool TryGetZdo(Component component, out ZDO? zdo) { ZNetView val = component.GetComponent() ?? component.GetComponentInParent(); zdo = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); return zdo != null; } } internal static class FermenterEnvironmentSpeedSystem { private sealed class RuntimeState { internal bool CoverKnown; internal float Cover; internal bool UnderRoof; internal bool Tracked; internal bool DepthCached; internal Vector3 DepthPosition; internal float DepthMeters; } internal readonly struct EnvironmentStatus { internal float Cover { get; } internal bool CanApplyBonus { get; } internal float CoverMultiplier { get; } internal bool DepthKnown { get; } internal float DepthMeters { get; } internal float DepthMultiplier { get; } internal EnvironmentStatus(float cover, bool canApplyBonus, float coverMultiplier, bool depthKnown, float depthMeters, float depthMultiplier) { Cover = cover; CanApplyBonus = canApplyBonus; CoverMultiplier = coverMultiplier; DepthKnown = depthKnown; DepthMeters = depthMeters; DepthMultiplier = depthMultiplier; } } private readonly struct StateSnapshot { internal long BatchToken { get; } internal long AccumulatedBonusTicks { get; } internal long LastCheckpointTicks { get; } internal float BonusRate { get; } internal StateSnapshot(long batchToken, long accumulatedBonusTicks, long lastCheckpointTicks, float bonusRate) { BatchToken = batchToken; AccumulatedBonusTicks = accumulatedBonusTicks; LastCheckpointTicks = lastCheckpointTicks; BonusRate = bonusRate; } } private const float VanillaMinimumCover = 0.7f; internal const float MaximumDepthMeters = 8f; private const float RateEpsilon = 0.001f; private const float HeightmapEdgeEpsilon = 0.001f; private const double CheckpointSeconds = 10.0; private const string BatchTokenKey = "FineDining_FermenterEnv_BatchTokenV1"; private const string AccumulatedBonusTicksKey = "FineDining_FermenterEnv_AccumulatedBonusTicksV1"; private const string LastCheckpointTicksKey = "FineDining_FermenterEnv_LastCheckpointTicksV1"; private const string BonusRateKey = "FineDining_FermenterEnv_BonusRateV1"; private static readonly FieldInfo? FermenterHasRoofField = AccessTools.Field(typeof(Fermenter), "m_hasRoof"); private static readonly FieldInfo? FermenterExposedField = AccessTools.Field(typeof(Fermenter), "m_exposed"); private static readonly FieldInfo? HeightmapBuildDataField = AccessTools.Field(typeof(Heightmap), "m_buildData"); private static ConditionalWeakTable _runtimeStates = new ConditionalWeakTable(); private static readonly List TrackedFermenters = new List(); [ThreadStatic] private static Fermenter? _coverUpdateTarget; internal static void RegisterLoaded(Fermenter fermenter) { if (StationModule.IsInitialized) { if (TrackedFermenters.Count > 0 && TrackedFermenters.Count % 64 == 0) { PruneDeadTrackedFermenters(); } RuntimeState runtimeState = GetRuntimeState(fermenter); if (!runtimeState.Tracked) { runtimeState.Tracked = true; TrackedFermenters.Add(new WeakReference(fermenter)); } } } internal static Fermenter? BeginCoverCapture(Fermenter fermenter) { Fermenter? coverUpdateTarget = _coverUpdateTarget; _coverUpdateTarget = ((StationModule.IsInitialized && !StationModule.IsFermenterBonusExcluded(fermenter)) ? fermenter : null); return coverUpdateTarget; } internal static void EndCoverCapture(Fermenter? previous) { _coverUpdateTarget = previous; } internal static void CaptureCover(float cover, bool underRoof) { Fermenter coverUpdateTarget = _coverUpdateTarget; if (!((Object)(object)coverUpdateTarget == (Object)null) && IsFinite(cover)) { RuntimeState runtimeState = GetRuntimeState(coverUpdateTarget); runtimeState.CoverKnown = true; runtimeState.Cover = Mathf.Clamp01(cover); runtimeState.UnderRoof = underRoof; } } internal static void CheckpointOwner(Fermenter fermenter, bool force) { if (!StationModule.IsInitialized || !TryGetZdo(fermenter, requireOwner: true, out ZDO zdo)) { return; } string value = zdo.GetString(ZDOVars.s_content, ""); long num = zdo.GetLong(ZDOVars.s_startTime, 0L); if (string.IsNullOrEmpty(value) || num <= 0) { if (HasState(zdo)) { ClearState(zdo); } return; } long currentTicks = GetCurrentTicks(); if (currentTicks <= 0) { return; } StateSnapshot snapshot; bool flag = TryReadValidState(zdo, num, out snapshot); bool flag2 = StationModule.IsFermenterBonusExcluded(fermenter); RuntimeState runtimeState = GetRuntimeState(fermenter); float num2 = (flag2 ? 0f : ((flag && !runtimeState.CoverKnown) ? snapshot.BonusRate : GetCurrentBonusRate(fermenter))); if (!flag) { if (flag2) { if (HasState(zdo)) { ClearState(zdo); } } else { WriteState(zdo, num, 0L, currentTicks, num2); } return; } long right = ProjectPendingBonusTicks(fermenter, snapshot, currentTicks); long num3 = SafeAddAndClamp(snapshot.AccumulatedBonusTicks, right, GetMaximumTotalBonusTicks(fermenter, snapshot.AccumulatedBonusTicks)); bool flag3 = Math.Abs(snapshot.BonusRate - num2) > 0.001f; bool flag4 = SecondsBetween(snapshot.LastCheckpointTicks, currentTicks) >= 10.0; bool flag5 = IsFinite(fermenter.m_fermentationDuration) && fermenter.m_fermentationDuration >= 0f && GetVanillaElapsed(zdo) + TimeSpan.FromTicks(num3).TotalSeconds > (double)fermenter.m_fermentationDuration; if (force || flag3 || flag4 || flag5) { WriteState(zdo, num, num3, Math.Max(snapshot.LastCheckpointTicks, currentTicks), num2); } } internal static void CheckpointAllOwners() { for (int num = TrackedFermenters.Count - 1; num >= 0; num--) { WeakReference weakReference = TrackedFermenters[num]; if (weakReference.IsAlive) { object? target = weakReference.Target; Fermenter val = (Fermenter)((target is Fermenter) ? target : null); if (val != null && !((Object)(object)val == (Object)null)) { CheckpointOwner(val, force: true); continue; } } TrackedFermenters.RemoveAt(num); } } internal static void ResetRuntime() { _coverUpdateTarget = null; TrackedFermenters.Clear(); _runtimeStates = new ConditionalWeakTable(); } internal static void CheckpointForView(ZNetView view) { if (StationModule.IsInitialized && !((Object)(object)view == (Object)null)) { Fermenter val = ((Component)view).GetComponent() ?? ((Component)view).GetComponentInChildren(); if ((Object)(object)val != (Object)null) { CheckpointOwner(val, force: true); } } } internal static long GetBatchToken(Fermenter fermenter) { if (!TryGetZdo(fermenter, requireOwner: false, out ZDO zdo)) { return 0L; } return zdo.GetLong(ZDOVars.s_startTime, 0L); } internal static void NotifyBatchStartedOrReset(Fermenter fermenter) { if (!StationModule.IsInitialized || !TryGetZdo(fermenter, requireOwner: true, out ZDO zdo)) { return; } string value = zdo.GetString(ZDOVars.s_content, ""); long num = zdo.GetLong(ZDOVars.s_startTime, 0L); if (string.IsNullOrEmpty(value) || num <= 0) { ClearState(zdo); return; } if (TryReadValidState(zdo, num, out var _)) { CheckpointOwner(fermenter, force: true); return; } if (StationModule.IsFermenterBonusExcluded(fermenter)) { if (HasState(zdo)) { ClearState(zdo); } return; } long currentTicks = GetCurrentTicks(); if (currentTicks > 0) { WriteState(zdo, num, 0L, currentTicks, GetCurrentBonusRate(fermenter)); } } internal static void NotifyBatchCleared(Fermenter fermenter) { if (StationModule.IsInitialized && TryGetZdo(fermenter, requireOwner: true, out ZDO zdo)) { ClearState(zdo); } } internal static bool HasContent(Fermenter fermenter) { if (TryGetZdo(fermenter, requireOwner: false, out ZDO zdo)) { return !string.IsNullOrEmpty(zdo.GetString(ZDOVars.s_content, "")); } return false; } internal static double ProjectEffectiveElapsed(Fermenter fermenter, double vanillaElapsed) { if (!StationModule.IsInitialized || vanillaElapsed < 0.0 || !TryGetZdo(fermenter, requireOwner: false, out ZDO zdo) || (Object)(object)ZNet.instance == (Object)null) { return vanillaElapsed; } long num = zdo.GetLong(ZDOVars.s_startTime, 0L); if (num <= 0 || string.IsNullOrEmpty(zdo.GetString(ZDOVars.s_content, "")) || !TryReadValidState(zdo, num, out var snapshot)) { return vanillaElapsed; } long right = ProjectPendingBonusTicks(fermenter, snapshot, GetCurrentTicks()); long value = SafeAddAndClamp(snapshot.AccumulatedBonusTicks, right, GetMaximumTotalBonusTicks(fermenter, snapshot.AccumulatedBonusTicks)); double num2 = vanillaElapsed + TimeSpan.FromTicks(value).TotalSeconds; if (!double.IsNaN(num2) && !double.IsInfinity(num2)) { return Math.Max(vanillaElapsed, num2); } return vanillaElapsed; } internal static bool TryGetRemainingSeconds(Fermenter fermenter, out double remainingSeconds, out float speedMultiplier) { remainingSeconds = 0.0; speedMultiplier = 1f; if (!StationModule.IsInitialized || fermenter.m_fermentationDuration <= 0f || !TryGetZdo(fermenter, requireOwner: false, out ZDO zdo) || string.IsNullOrEmpty(zdo.GetString(ZDOVars.s_content, ""))) { return false; } double vanillaElapsed = GetVanillaElapsed(zdo); if (vanillaElapsed < 0.0) { return false; } vanillaElapsed = ProjectEffectiveElapsed(fermenter, vanillaElapsed); bool flag = StationModule.IsFermenterBonusExcluded(fermenter); RuntimeState runtimeState = GetRuntimeState(fermenter); float num = 0f; long batchToken = zdo.GetLong(ZDOVars.s_startTime, 0L); if (TryReadValidState(zdo, batchToken, out var snapshot)) { num = (flag ? 0f : snapshot.BonusRate); if (!flag && IsOwner(fermenter) && runtimeState.CoverKnown) { num = GetCurrentBonusRate(fermenter); } } speedMultiplier = Math.Max(1f, 1f + num); if (vanillaElapsed > (double)fermenter.m_fermentationDuration) { remainingSeconds = 0.0; return true; } if (flag) { remainingSeconds = Math.Max(0.0, (double)fermenter.m_fermentationDuration - vanillaElapsed); return true; } GetCoverState(fermenter, runtimeState, out var hasRoof, out var exposed); if (!hasRoof || exposed) { return false; } remainingSeconds = Math.Max(0.0, ((double)fermenter.m_fermentationDuration - vanillaElapsed) / (double)speedMultiplier); return true; } internal static EnvironmentStatus GetEnvironmentStatus(Fermenter fermenter) { if (StationModule.IsFermenterBonusExcluded(fermenter)) { return new EnvironmentStatus(0f, canApplyBonus: false, 1f, depthKnown: false, 0f, 1f); } RuntimeState runtimeState = GetRuntimeState(fermenter); GetCoverState(fermenter, runtimeState, out var hasRoof, out var exposed); float num = (runtimeState.CoverKnown ? runtimeState.Cover : (exposed ? 0f : 0.7f)); num = Mathf.Clamp01(num); float num2 = (StationModule.IsInitialized ? SanitizeMultiplier(StationModule.FermenterCoverMaxSpeedMultiplier.Value) : 1f); float num3 = Mathf.InverseLerp(0.7f, 1f, num); float coverMultiplier = Mathf.Lerp(1f, num2, num3); float depthMeters; bool flag = TryGetDepthMeters(fermenter, runtimeState, out depthMeters); float depthMultiplier = 1f; if (flag) { float num4 = Mathf.Clamp01(depthMeters / 8f); float num5 = (StationModule.IsInitialized ? SanitizeMultiplier(StationModule.FermenterDepthMaxSpeedMultiplier.Value) : 1f); depthMultiplier = Mathf.Lerp(1f, num5, num4); } return new EnvironmentStatus(num, hasRoof && num >= 0.7f, coverMultiplier, flag, depthMeters, depthMultiplier); } internal static bool IsOwner(Fermenter fermenter) { ZNetView view = GetView(fermenter); if ((Object)(object)view != (Object)null && view.IsValid()) { return view.IsOwner(); } return false; } private static float GetCurrentBonusRate(Fermenter fermenter) { if (StationModule.IsFermenterBonusExcluded(fermenter)) { return 0f; } EnvironmentStatus environmentStatus = GetEnvironmentStatus(fermenter); if (!environmentStatus.CanApplyBonus) { return 0f; } float num = environmentStatus.CoverMultiplier * environmentStatus.DepthMultiplier; if (!IsFinite(num)) { return 0f; } return Math.Max(0f, num - 1f); } private static bool TryGetDepthMeters(Fermenter fermenter, RuntimeState state, out float depthMeters) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)fermenter).transform.position; if (state.DepthCached) { Vector3 val = state.DepthPosition - position; if (((Vector3)(ref val)).sqrMagnitude <= 0.0001f) { depthMeters = state.DepthMeters; return true; } } if (!TryGetOriginalTerrainBaselineY(position, out var baselineY)) { depthMeters = 0f; return false; } depthMeters = baselineY - position.y; if (!IsFinite(depthMeters)) { depthMeters = 0f; return false; } depthMeters = Mathf.Max(0f, depthMeters); state.DepthCached = true; state.DepthPosition = position; state.DepthMeters = depthMeters; return true; } private static bool TryGetOriginalTerrainBaselineY(Vector3 worldPosition, out float baselineY) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) baselineY = 0f; if (!IsFinite(worldPosition.x) || !IsFinite(worldPosition.y) || !IsFinite(worldPosition.z)) { return false; } Heightmap val = Heightmap.FindHeightmap(worldPosition); if ((Object)(object)val == (Object)null || val.IsDistantLod) { return false; } int width = val.m_width; float scale = val.m_scale; HMBuildData val2 = ReadField(HeightmapBuildDataField, val); if (width <= 0 || !IsFinite(scale) || scale <= 0f || val2 == null || val2.m_baseHeights == null || val2.m_width != width || val2.m_center != ((Component)val).transform.position || !Mathf.Approximately(val2.m_scale, scale)) { return false; } long num = (long)width + 1L; long num2 = num * num; if (num2 > int.MaxValue || val2.m_baseHeights.Count != (int)num2) { return false; } Vector3 val3 = ((Component)val).transform.InverseTransformPoint(worldPosition); float num3 = (float)width * scale * 0.5f; float num4 = (val3.x + num3) / scale; float num5 = (val3.z + num3) / scale; if (!IsFinite(num3) || !IsFinite(num4) || !IsFinite(num5) || num4 < -0.001f || num5 < -0.001f || num4 > (float)width + 0.001f || num5 > (float)width + 0.001f) { return false; } num4 = Mathf.Clamp(num4, 0f, (float)width); num5 = Mathf.Clamp(num5, 0f, (float)width); int num6 = Mathf.Min(Mathf.FloorToInt(num4), width - 1); int num7 = Mathf.Min(Mathf.FloorToInt(num5), width - 1); float num8 = num4 - (float)num6; float num9 = num5 - (float)num7; int num10 = width + 1; int num11 = num7 * num10; int num12 = (num7 + 1) * num10; float num13 = val2.m_baseHeights[num11 + num6]; float num14 = val2.m_baseHeights[num11 + num6 + 1]; float num15 = val2.m_baseHeights[num12 + num6]; float num16 = val2.m_baseHeights[num12 + num6 + 1]; if (!IsFinite(num13) || !IsFinite(num14) || !IsFinite(num15) || !IsFinite(num16)) { return false; } float num17 = ((num8 + num9 <= 1f) ? (num13 + (num14 - num13) * num8 + (num15 - num13) * num9) : (num16 + (num15 - num16) * (1f - num8) + (num14 - num16) * (1f - num9))); float num18 = num4 * scale - num3; float num19 = num5 * scale - num3; float y = ((Component)val).transform.TransformPoint(new Vector3(num18, num17, num19)).y; if (!IsFinite(y)) { return false; } baselineY = y; return true; } private static long ProjectPendingBonusTicks(Fermenter fermenter, StateSnapshot snapshot, long nowTicks) { if (StationModule.IsFermenterBonusExcluded(fermenter) || nowTicks <= snapshot.LastCheckpointTicks || snapshot.BonusRate <= 0f) { return 0L; } double num = (double)(nowTicks - snapshot.LastCheckpointTicks) * (double)snapshot.BonusRate; long maximumTotalBonusTicks = GetMaximumTotalBonusTicks(fermenter, snapshot.AccumulatedBonusTicks); long num2 = Math.Max(0L, maximumTotalBonusTicks - snapshot.AccumulatedBonusTicks); if (double.IsNaN(num) || num <= 0.0 || num2 <= 0) { return 0L; } if (!(num >= (double)num2)) { return Math.Max(0L, (long)Math.Round(num)); } return num2; } private static long GetMaximumTotalBonusTicks(Fermenter fermenter, long accumulatedBonusTicks) { double num = (IsFinite(fermenter.m_fermentationDuration) ? Math.Max(0.0, (double)fermenter.m_fermentationDuration + 1.0) : 0.0) * 10000000.0; long val = ((num >= 9.223372036854776E+18) ? long.MaxValue : ((long)Math.Ceiling(num))); return Math.Max(Math.Max(0L, accumulatedBonusTicks), val); } private static long SafeAddAndClamp(long left, long right, long maximum) { left = Math.Max(0L, left); maximum = Math.Max(left, maximum); right = Math.Max(0L, right); if (right <= maximum - left) { return left + right; } return maximum; } private static bool TryReadValidState(ZDO zdo, long batchToken, out StateSnapshot snapshot) { snapshot = new StateSnapshot(zdo.GetLong("FineDining_FermenterEnv_BatchTokenV1", 0L), zdo.GetLong("FineDining_FermenterEnv_AccumulatedBonusTicksV1", 0L), zdo.GetLong("FineDining_FermenterEnv_LastCheckpointTicksV1", 0L), ReadBonusRate(zdo)); if (batchToken > 0 && snapshot.BatchToken == batchToken && snapshot.AccumulatedBonusTicks >= 0) { return snapshot.LastCheckpointTicks > 0; } return false; } private static void WriteState(ZDO zdo, long batchToken, long accumulatedBonusTicks, long lastCheckpointTicks, float bonusRate) { zdo.Set("FineDining_FermenterEnv_BatchTokenV1", Math.Max(0L, batchToken)); zdo.Set("FineDining_FermenterEnv_AccumulatedBonusTicksV1", Math.Max(0L, accumulatedBonusTicks)); zdo.Set("FineDining_FermenterEnv_LastCheckpointTicksV1", Math.Max(0L, lastCheckpointTicks)); zdo.Set("FineDining_FermenterEnv_BonusRateV1", IsFinite(bonusRate) ? Math.Max(0f, bonusRate) : 0f); } private static void ClearState(ZDO zdo) { WriteState(zdo, 0L, 0L, 0L, 0f); } private static bool HasState(ZDO zdo) { if (zdo.GetLong("FineDining_FermenterEnv_BatchTokenV1", 0L) <= 0 && zdo.GetLong("FineDining_FermenterEnv_AccumulatedBonusTicksV1", 0L) <= 0 && zdo.GetLong("FineDining_FermenterEnv_LastCheckpointTicksV1", 0L) <= 0) { return ReadBonusRate(zdo) > 0f; } return true; } private static bool TryGetZdo(Fermenter fermenter, bool requireOwner, out ZDO? zdo) { ZNetView view = GetView(fermenter); zdo = (((Object)(object)view != (Object)null && view.IsValid() && (!requireOwner || view.IsOwner())) ? view.GetZDO() : null); return zdo != null; } private static ZNetView? GetView(Fermenter fermenter) { if (!((Object)(object)fermenter != (Object)null)) { return null; } return ((Component)fermenter).GetComponent(); } private static double GetVanillaElapsed(ZDO zdo) { long num = zdo.GetLong(ZDOVars.s_startTime, 0L); if (num > 0) { DateTime maxValue = DateTime.MaxValue; if (num <= maxValue.Ticks && !((Object)(object)ZNet.instance == (Object)null)) { return (ZNet.instance.GetTime() - new DateTime(num)).TotalSeconds; } } return -1.0; } private static void GetCoverState(Fermenter fermenter, RuntimeState state, out bool hasRoof, out bool exposed) { if (state.CoverKnown) { hasRoof = state.UnderRoof; exposed = state.Cover < 0.7f; } else { hasRoof = ReadField(FermenterHasRoofField, fermenter, fallback: false); exposed = ReadField(FermenterExposedField, fermenter, fallback: true); } } private static bool ReadField(FieldInfo? field, object instance, bool fallback) { try { return (field?.GetValue(instance) is bool flag) ? flag : fallback; } catch (Exception ex) { FineDiningPlugin.Log.LogDebug((object)("Could not read " + field?.DeclaringType?.Name + "." + field?.Name + ": " + ex.Message)); return fallback; } } private static T? ReadField(FieldInfo? field, object instance) where T : class { try { return field?.GetValue(instance) as T; } catch (Exception ex) { FineDiningPlugin.Log.LogDebug((object)("Could not read " + field?.DeclaringType?.Name + "." + field?.Name + ": " + ex.Message)); return null; } } private static RuntimeState GetRuntimeState(Fermenter fermenter) { return _runtimeStates.GetValue(fermenter, (Fermenter _) => new RuntimeState()); } private static float ReadBonusRate(ZDO zdo) { float num = zdo.GetFloat("FineDining_FermenterEnv_BonusRateV1", 0f); if (!IsFinite(num)) { return 0f; } return Math.Max(0f, num); } private static float SanitizeMultiplier(float value) { if (!IsFinite(value)) { return 1f; } return Math.Max(1f, value); } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static long GetCurrentTicks() { if (!((Object)(object)ZNet.instance != (Object)null)) { return 0L; } return ZNet.instance.GetTime().Ticks; } private static double SecondsBetween(long startTicks, long endTicks) { if (startTicks <= 0 || endTicks <= startTicks) { return 0.0; } return TimeSpan.FromTicks(endTicks - startTicks).TotalSeconds; } private static void PruneDeadTrackedFermenters() { for (int num = TrackedFermenters.Count - 1; num >= 0; num--) { WeakReference weakReference = TrackedFermenters[num]; if (weakReference.IsAlive) { object? target = weakReference.Target; Fermenter val = (Fermenter)((target is Fermenter) ? target : null); if (val != null && !((Object)(object)val == (Object)null)) { continue; } } TrackedFermenters.RemoveAt(num); } } } [HarmonyPatch(typeof(Fermenter), "Awake")] internal static class StationFermenterEnvironmentAwakePatch { private static void Prefix(Fermenter __instance) { FermenterEnvironmentSpeedSystem.RegisterLoaded(__instance); } private static Exception? Finalizer(Exception? __exception, Fermenter __instance) { if (__exception == null && FermenterEnvironmentSpeedSystem.IsOwner(__instance)) { try { FermenterEnvironmentSpeedSystem.CheckpointOwner(__instance, force: true); } catch (Exception ex) { FineDiningPlugin.Log.LogWarning((object)("Could not initialize fermenter environment state: " + ex.Message)); } } return __exception; } } [HarmonyPatch(typeof(Fermenter), "SlowUpdate")] internal static class StationFermenterEnvironmentSlowUpdatePatch { private static void Postfix(Fermenter __instance) { if (FermenterEnvironmentSpeedSystem.IsOwner(__instance)) { FermenterEnvironmentSpeedSystem.CheckpointOwner(__instance, force: false); } } } [HarmonyPatch(typeof(Fermenter), "UpdateCover")] internal static class StationFermenterEnvironmentCoverScopePatch { private static void Prefix(Fermenter __instance, out Fermenter? __state) { __state = FermenterEnvironmentSpeedSystem.BeginCoverCapture(__instance); } private static Exception? Finalizer(Exception? __exception, Fermenter? __state) { FermenterEnvironmentSpeedSystem.EndCoverCapture(__state); return __exception; } } [HarmonyPatch(typeof(Cover), "GetCoverForPoint")] internal static class StationFermenterEnvironmentCoverResultPatch { [HarmonyPriority(0)] private static void Postfix(ref float coverPercentage, ref bool underRoof) { FermenterEnvironmentSpeedSystem.CaptureCover(coverPercentage, underRoof); } } [HarmonyPatch(typeof(Fermenter), "GetFermentationTime")] internal static class StationFermenterEnvironmentElapsedPatch { private static void Postfix(Fermenter __instance, ref double __result) { __result = FermenterEnvironmentSpeedSystem.ProjectEffectiveElapsed(__instance, __result); } } [HarmonyPatch(typeof(Fermenter), "RPC_AddItem")] internal static class StationFermenterEnvironmentAddPatch { private static void Postfix(Fermenter __instance) { FermenterEnvironmentSpeedSystem.NotifyBatchStartedOrReset(__instance); } } [HarmonyPatch(typeof(Fermenter), "ResetFermentationTimer")] internal static class StationFermenterEnvironmentResetPatch { private static void Postfix(Fermenter __instance) { FermenterEnvironmentSpeedSystem.NotifyBatchStartedOrReset(__instance); } } [HarmonyPatch(typeof(Fermenter), "RPC_Tap")] internal static class StationFermenterEnvironmentTapPatch { private static void Prefix(Fermenter __instance, out bool __state) { __state = FermenterEnvironmentSpeedSystem.HasContent(__instance); if (__state) { FermenterEnvironmentSpeedSystem.CheckpointOwner(__instance, force: true); } } private static void Postfix(Fermenter __instance, bool __state) { if (__state && !FermenterEnvironmentSpeedSystem.HasContent(__instance)) { FermenterEnvironmentSpeedSystem.NotifyBatchCleared(__instance); } } } [HarmonyPatch(typeof(Fermenter), "DropAllItems")] internal static class StationFermenterEnvironmentDropPatch { private static void Prefix(Fermenter __instance, out bool __state) { __state = FermenterEnvironmentSpeedSystem.HasContent(__instance); if (__state) { FermenterEnvironmentSpeedSystem.CheckpointOwner(__instance, force: true); } } private static void Postfix(Fermenter __instance, bool __state) { if (__state && !FermenterEnvironmentSpeedSystem.HasContent(__instance)) { FermenterEnvironmentSpeedSystem.NotifyBatchCleared(__instance); } } } [HarmonyPatch(typeof(ZNetView), "ResetZDO")] internal static class StationFermenterEnvironmentResetZdoPatch { private static void Prefix(ZNetView __instance) { FermenterEnvironmentSpeedSystem.CheckpointForView(__instance); } } [HarmonyPatch(typeof(ZNetView), "OnDestroy")] internal static class StationFermenterEnvironmentViewDestroyedPatch { private static void Prefix(ZNetView __instance) { FermenterEnvironmentSpeedSystem.CheckpointForView(__instance); } } internal sealed class StationHintCandidate { internal string DisplayName { get; } internal Sprite? Icon { get; } internal string StatusText { get; } internal string SecondaryStatusText { get; } internal StationHintCandidate(string displayName, Sprite? icon, string statusText = "", string secondaryStatusText = "") { DisplayName = displayName; Icon = icon; StatusText = statusText; SecondaryStatusText = secondaryStatusText; } } internal sealed class StationHintUi : MonoBehaviour { private sealed class Element { private readonly GameObject _go; private readonly Image _icon; private readonly TMP_Text _label; private readonly TMP_Text _status; private readonly TMP_Text _secondaryStatus; internal Element(string name, Transform root, GameObject elementPrefab, bool emphasizeStatusText) { //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Expected O, but got Unknown //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_033d: Unknown result type (might be due to invalid IL or missing references) //IL_0352: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_037f: Unknown result type (might be due to invalid IL or missing references) //IL_0415: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_043a: Unknown result type (might be due to invalid IL or missing references) //IL_044f: Unknown result type (might be due to invalid IL or missing references) //IL_0464: Unknown result type (might be due to invalid IL or missing references) //IL_047b: Unknown result type (might be due to invalid IL or missing references) //IL_0491: Unknown result type (might be due to invalid IL or missing references) //IL_0528: Unknown result type (might be due to invalid IL or missing references) //IL_052d: Unknown result type (might be due to invalid IL or missing references) _go = Object.Instantiate(elementPrefab, root); ((Object)_go).name = name; float num = (emphasizeStatusText ? 1.5f : 1f); MonoBehaviour[] components = _go.GetComponents(); foreach (MonoBehaviour obj in components) { Graphic val = (Graphic)(object)((obj is Graphic) ? obj : null); if (val != null) { ((Behaviour)val).enabled = false; } Object.Destroy((Object)(object)obj); } Transform val2 = _go.transform.Find("icon"); Transform val3 = _go.transform.Find("amount"); Image val4 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent() : null); TMP_Text val5 = (((Object)(object)val3 != (Object)null) ? ((Component)val3).GetComponent() : null); if ((Object)(object)val4 == (Object)null || (Object)(object)val5 == (Object)null) { throw new InvalidOperationException("Inventory element prefab is missing its icon or amount UI."); } _icon = val4; _label = val5; GameObject obj2 = Object.Instantiate(((Component)val3).gameObject, _go.transform); ((Object)obj2).name = "FineDining_StationStatus"; TMP_Text component = obj2.GetComponent(); if ((Object)(object)component == (Object)null) { throw new InvalidOperationException("Inventory element prefab is missing a usable amount UI."); } _status = component; GameObject obj3 = Object.Instantiate(((Component)val3).gameObject, _go.transform); ((Object)obj3).name = "FineDining_StationSecondaryStatus"; TMP_Text component2 = obj3.GetComponent(); if ((Object)(object)component2 == (Object)null) { throw new InvalidOperationException("Inventory element prefab is missing a usable secondary status UI."); } _secondaryStatus = component2; foreach (Transform item in _go.transform) { Transform val6 = item; if (((Object)val6).name != "icon" && ((Object)val6).name != "amount" && ((Object)val6).name != "FineDining_StationStatus" && ((Object)val6).name != "FineDining_StationSecondaryStatus") { ((Component)val6).gameObject.SetActive(false); Object.Destroy((Object)(object)((Component)val6).gameObject); } } RectTransform rectTransform = _label.rectTransform; rectTransform.anchorMin = new Vector2(0f, 0f); rectTransform.anchorMax = new Vector2(1f, 0f); rectTransform.pivot = new Vector2(0.5f, 0f); rectTransform.anchoredPosition = new Vector2(0f, 1f); rectTransform.sizeDelta = new Vector2(-4f, 30f); _label.textWrappingMode = (TextWrappingModes)1; _label.overflowMode = (TextOverflowModes)1; _label.fontSize = 11f; _label.enableAutoSizing = true; _label.fontSizeMin = 8f; _label.fontSizeMax = 11f; _label.maxVisibleLines = 2; _label.alignment = (TextAlignmentOptions)514; ((Graphic)_label).color = Color.white; RectTransform rectTransform2 = _status.rectTransform; rectTransform2.anchorMin = new Vector2(0f, 1f); rectTransform2.anchorMax = new Vector2(1f, 1f); rectTransform2.pivot = new Vector2(0.5f, 1f); rectTransform2.anchoredPosition = new Vector2(0f, -1f * num); rectTransform2.sizeDelta = new Vector2(-4f, 24f * num); _status.textWrappingMode = (TextWrappingModes)0; _status.overflowMode = (TextOverflowModes)1; _status.fontSize = 12f * num; _status.enableAutoSizing = true; _status.fontSizeMin = 7f * num; _status.fontSizeMax = 12f * num; _status.maxVisibleLines = 1; _status.alignment = (TextAlignmentOptions)514; ((Graphic)_status).color = Color32.op_Implicit(new Color32(byte.MaxValue, (byte)165, (byte)0, byte.MaxValue)); RectTransform rectTransform3 = _secondaryStatus.rectTransform; rectTransform3.anchorMin = new Vector2(0f, 1f); rectTransform3.anchorMax = new Vector2(1f, 1f); rectTransform3.pivot = new Vector2(0.5f, 1f); rectTransform3.anchoredPosition = new Vector2(0f, -19f * num); rectTransform3.sizeDelta = new Vector2(-4f, 20f * num); _secondaryStatus.textWrappingMode = (TextWrappingModes)0; _secondaryStatus.overflowMode = (TextOverflowModes)1; _secondaryStatus.fontSize = 10f * num; _secondaryStatus.enableAutoSizing = true; _secondaryStatus.fontSizeMin = 7f * num; _secondaryStatus.fontSizeMax = 10f * num; _secondaryStatus.maxVisibleLines = 1; _secondaryStatus.alignment = (TextAlignmentOptions)514; ((Graphic)_secondaryStatus).color = Color32.op_Implicit(new Color32((byte)159, (byte)232, (byte)112, byte.MaxValue)); Graphic[] componentsInChildren = _go.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].raycastTarget = false; } Hide(); } internal void Set(StationHintCandidate candidate) { _go.SetActive(true); ((Behaviour)_icon).enabled = (Object)(object)candidate.Icon != (Object)null; _icon.sprite = candidate.Icon; ((Behaviour)_label).enabled = true; _label.text = candidate.DisplayName; ((Behaviour)_status).enabled = !string.IsNullOrEmpty(candidate.StatusText); _status.text = candidate.StatusText; ((Behaviour)_secondaryStatus).enabled = !string.IsNullOrEmpty(candidate.SecondaryStatusText); _secondaryStatus.text = candidate.SecondaryStatusText; } internal void Hide() { _go.SetActive(false); } } private const float CellWidth = 82f; private const float CellHeight = 96f; private const float ColumnSpacing = 5f; private const float RowSpacing = 5f; private const float HoverGap = 6f; private const float ProgressInputGap = 12f; private const float CookingProgressTextScale = 1.5f; private static StationHintUi? _instance; private static bool _loggedCreationFailure; private static float _nextCreationAttemptTime; private readonly List _inputElements = new List(20); private readonly List _progressElements = new List(20); private GameObject? _root; private GameObject? _inputRoot; private GameObject? _progressRoot; private TMP_Text? _hoverText; private int _visibleInputCount; private int _visibleProgressCount; private int _lastShowFrame = -100; internal static void EnsureCreated() { if (!StationModule.IsInitialized || (Object)(object)_instance != (Object)null) { return; } Hud instance = Hud.instance; InventoryGui instance2 = InventoryGui.instance; InventoryGrid val = instance2?.m_playerGrid; GameObject val2 = val?.m_elementPrefab; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || (Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || Time.unscaledTime < _nextCreationAttemptTime) { return; } StationHintUi stationHintUi = null; try { stationHintUi = ((Component)instance).gameObject.AddComponent(); stationHintUi.Create(instance, val2); _instance = stationHintUi; _loggedCreationFailure = false; _nextCreationAttemptTime = 0f; } catch (Exception ex) { _nextCreationAttemptTime = Time.unscaledTime + 1f; if ((Object)(object)stationHintUi != (Object)null) { stationHintUi.DestroyRoot(); Object.Destroy((Object)(object)stationHintUi); } if (!_loggedCreationFailure) { _loggedCreationFailure = true; FineDiningPlugin.Log.LogWarning((object)("Could not create station hover UI: " + ex.Message)); } } } internal static void Shutdown() { StationHintUi instance = _instance; _instance = null; _nextCreationAttemptTime = 0f; _loggedCreationFailure = false; if ((Object)(object)instance != (Object)null) { instance.DestroyRoot(); Object.Destroy((Object)(object)instance); } } internal static void Show(IReadOnlyList candidates) { if (candidates.Count != 0) { EnsureCreated(); _instance?.ShowGroups(Array.Empty(), candidates); } } internal static void ShowCookingStation(IReadOnlyList progressCandidates, IReadOnlyList inputCandidates) { if (progressCandidates.Count != 0 || inputCandidates.Count != 0) { EnsureCreated(); _instance?.ShowGroups(progressCandidates, inputCandidates); } } private void Create(Hud hud, GameObject elementPrefab) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown _hoverText = (TMP_Text?)(object)hud.m_hoverName; if ((Object)(object)_hoverText == (Object)null) { throw new InvalidOperationException("HUD hover text is unavailable."); } Transform parent = ((Transform)_hoverText.rectTransform).parent; _root = new GameObject("FineDining_StationHints", new Type[1] { typeof(RectTransform) }); _root.SetActive(false); _root.transform.SetParent(parent, false); ConfigureTopLeftRect((RectTransform)_root.transform, Vector2.zero); _inputRoot = CreateGrid("FineDining_AvailableStationInputs", _root.transform); _progressRoot = CreateGrid("FineDining_CookingProgress", _root.transform); for (int i = 0; i < 20; i++) { _inputElements.Add(new Element("FineDining_StationInputHint_" + i, _inputRoot.transform, elementPrefab, emphasizeStatusText: false)); _progressElements.Add(new Element("FineDining_CookingProgress_" + i, _progressRoot.transform, elementPrefab, emphasizeStatusText: true)); } ApplyConfiguration(); } private static GameObject CreateGrid(string name, Transform parent) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown //IL_00be: Expected O, but got Unknown GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.SetActive(false); val.transform.SetParent(parent, false); int num = 4; ConfigureTopLeftRect((RectTransform)val.transform, new Vector2(430f, (float)num * 96f + (float)(num - 1) * 5f)); GridLayoutGroup obj = val.AddComponent(); obj.startCorner = (Corner)0; obj.startAxis = (Axis)0; obj.constraint = (Constraint)1; obj.constraintCount = 5; obj.cellSize = new Vector2(82f, 96f); obj.spacing = new Vector2(5f, 5f); ((LayoutGroup)obj).padding = new RectOffset(0, 0, 0, 0); ((LayoutGroup)obj).childAlignment = (TextAnchor)0; return val; } private static void ConfigureTopLeftRect(RectTransform rect, Vector2 size) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = new Vector2(0.5f, 0.5f); rect.anchorMax = rect.anchorMin; rect.pivot = new Vector2(0f, 1f); rect.sizeDelta = size; ((Transform)rect).localPosition = Vector3.zero; } private void ShowGroups(IReadOnlyList progressCandidates, IReadOnlyList inputCandidates) { _visibleProgressCount = SetElements(_progressElements, progressCandidates); _visibleInputCount = SetElements(_inputElements, inputCandidates); ApplyConfiguration(); GameObject? progressRoot = _progressRoot; if (progressRoot != null) { progressRoot.SetActive(_visibleProgressCount > 0); } GameObject? inputRoot = _inputRoot; if (inputRoot != null) { inputRoot.SetActive(_visibleInputCount > 0); } GameObject? root = _root; if (root != null) { root.SetActive(_visibleProgressCount > 0 || _visibleInputCount > 0); } _lastShowFrame = Time.frameCount; } private static int SetElements(List elements, IReadOnlyList candidates) { int num = Math.Min(elements.Count, candidates.Count); for (int i = 0; i < elements.Count; i++) { if (i < num) { elements[i].Set(candidates[i]); } else { elements[i].Hide(); } } return num; } private void ApplyConfiguration() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null) && !((Object)(object)_inputRoot == (Object)null) && !((Object)(object)_progressRoot == (Object)null)) { _root.transform.localScale = Vector3.one * StationModule.IconGroupScale.Value; _progressRoot.transform.localPosition = Vector3.zero; _inputRoot.transform.localPosition = Vector3.zero; if (_visibleProgressCount > 0 && _visibleInputCount > 0) { int num = (_visibleProgressCount + 5 - 1) / 5; float num2 = (float)num * 96f + (float)(num - 1) * 5f; _inputRoot.transform.localPosition = Vector3.down * (num2 + 12f); } } } private void LateUpdate() { if (_lastShowFrame == Time.frameCount) { LayoutBelowHoverText(); return; } GameObject? root = _root; if (root != null) { root.SetActive(false); } } private void LayoutBelowHoverText() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null) && !((Object)(object)_hoverText == (Object)null)) { _hoverText.ForceMeshUpdate(false, false); RectTransform rectTransform = _hoverText.rectTransform; Rect rect; ? val; if (_hoverText.textInfo.characterCount <= 0) { rect = rectTransform.rect; float xMin = ((Rect)(ref rect)).xMin; rect = rectTransform.rect; val = new Vector3(xMin, ((Rect)(ref rect)).yMin, 0f); } else { Bounds textBounds = _hoverText.textBounds; val = ((Bounds)(ref textBounds)).min; } Vector3 val2 = (Vector3)val; if (!IsFinite(val2.x) || !IsFinite(val2.y)) { rect = rectTransform.rect; float xMin2 = ((Rect)(ref rect)).xMin; rect = rectTransform.rect; ((Vector3)(ref val2))..ctor(xMin2, ((Rect)(ref rect)).yMin, 0f); } Vector3 val3 = ((Transform)rectTransform).TransformPoint(val2); Transform parent = _root.transform.parent; Vector3 val4 = (((Object)(object)parent != (Object)null) ? parent.InverseTransformPoint(val3) : val3); _root.transform.localPosition = new Vector3(val4.x, val4.y - 6f, 0f); } } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private void OnDestroy() { if (_instance == this) { _instance = null; } DestroyRoot(); } private void DestroyRoot() { GameObject root = _root; _root = null; _inputRoot = null; _progressRoot = null; _hoverText = null; _inputElements.Clear(); _progressElements.Clear(); _visibleInputCount = 0; _visibleProgressCount = 0; if ((Object)(object)root != (Object)null) { root.SetActive(false); Object.Destroy((Object)(object)root); } } } [HarmonyPatch(typeof(Hud), "Awake")] internal static class StationHintHudAwakePatch { private static void Postfix() { if (StationModule.IsInitialized) { StationHintUi.EnsureCreated(); } } } [HarmonyPatch(typeof(Hud), "UpdateCrosshair")] internal static class StationHintHudCrosshairPatch { private static void Postfix(Hud __instance, Player player) { if (!StationModule.IsInitialized || (Object)(object)player == (Object)null || !__instance.IsVisible() || (Object)(object)__instance.m_crosshair == (Object)null || !((Component)__instance.m_crosshair).gameObject.activeInHierarchy || ((Object)(object)TextViewer.instance != (Object)null && TextViewer.instance.IsVisible())) { return; } GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return; } Hoverable componentInParent = hoverObject.GetComponentInParent(); if (componentInParent == null || ValheimCuisineCompatibility.TryShow(__instance, hoverObject, componentInParent, player)) { return; } Switch val = (Switch)(object)((componentInParent is Switch) ? componentInParent : null); if (val != null) { object obj = ((Delegate)(object)val.m_onUse)?.Target; CookingStation val2 = (CookingStation)((obj is CookingStation) ? obj : null); if (val2 != null) { StationInputResolver.ShowCookingStation(val2, val); return; } Smelter val3 = (Smelter)((obj is Smelter) ? obj : null); if (val3 != null) { StationInputResolver.ShowSmelter(val3, val); return; } Fermenter val4 = (Fermenter)((obj is Fermenter) ? obj : null); if (val4 != null) { StationInputResolver.ShowFermenter(val4); return; } CookingStation componentInParent2 = ((Component)val).GetComponentInParent(); if (componentInParent2 != null) { StationInputResolver.ShowCookingStation(componentInParent2, val); return; } Smelter componentInParent3 = ((Component)val).GetComponentInParent(); if (componentInParent3 != null) { StationInputResolver.ShowSmelter(componentInParent3, val); return; } Fermenter componentInParent4 = ((Component)val).GetComponentInParent(); if (componentInParent4 != null) { StationInputResolver.ShowFermenter(componentInParent4); } return; } CookingStation val5 = (CookingStation)(object)((componentInParent is CookingStation) ? componentInParent : null); if (val5 != null) { StationInputResolver.ShowCookingStation(val5, null); return; } Fermenter val6 = (Fermenter)(object)((componentInParent is Fermenter) ? componentInParent : null); if (val6 != null) { StationInputResolver.ShowFermenter(val6); } } } [HarmonyPatch(typeof(Smelter), "OnHoverAddOre")] internal static class StationSmelterHoverTimePatch { private static void Postfix(Smelter __instance, ref string __result) { if (StationHoverTime.CanShow((Component)(object)__instance) && !string.IsNullOrEmpty(__result) && StationHoverTime.TryGetSmelterRemaining(__instance, out var queueSize, out var seconds)) { string marker = $"({queueSize}/{__instance.m_maxOre})"; __result = StationHoverTime.InsertAfter(__result, marker, StationText.ColorizeTimer(StationText.FormatSeconds(seconds, keepAtLeastOneSecond: true))); } } } [HarmonyPatch(typeof(Fermenter), "GetHoverText")] internal static class StationFermenterHoverTimePatch { private const string DetailColor = "orange"; private static void Postfix(Fermenter __instance, ref string __result) { if (!StationHoverTime.CanShow((Component)(object)__instance) || string.IsNullOrEmpty(__result)) { return; } if (StationModule.IsFermenterBonusExcluded(__instance)) { if (FermenterEnvironmentSpeedSystem.TryGetRemainingSeconds(__instance, out var remainingSeconds, out var _)) { string text = StationText.FormatDuration(remainingSeconds); if (!string.IsNullOrEmpty(text)) { InsertAfterFirstLine(ref __result, StationText.ColorizeTimer(text)); } } return; } double remainingSeconds2; float speedMultiplier2; bool flag = FermenterEnvironmentSpeedSystem.TryGetRemainingSeconds(__instance, out remainingSeconds2, out speedMultiplier2); FermenterEnvironmentSpeedSystem.EnvironmentStatus environmentStatus = FermenterEnvironmentSpeedSystem.GetEnvironmentStatus(__instance); float multiplier = (environmentStatus.CanApplyBonus ? environmentStatus.CoverMultiplier : 1f); float multiplier2 = (environmentStatus.CanApplyBonus ? environmentStatus.DepthMultiplier : 1f); string text2 = ColorizeDetailLine($"{StationText.CoverLabel}: {Mathf.RoundToInt(environmentStatus.Cover * 100f)}% " + "(" + StationText.RateLabel + ": " + FormatMultiplier(multiplier) + ")"); if (environmentStatus.DepthKnown) { text2 = text2 + "\n" + ColorizeDetailLine(StationText.DepthLabel + ": " + environmentStatus.DepthMeters.ToString("0.0", CultureInfo.InvariantCulture) + " m (" + StationText.RateLabel + ": " + FormatMultiplier(multiplier2) + ")"); } if (flag) { string text3 = StationText.FormatDuration(remainingSeconds2); if (!string.IsNullOrEmpty(text3)) { string text4 = StationText.ColorizeTimer(text3); text4 = text4 + " (" + StationText.FermentationSpeedLabel + " " + StationText.ColorizeTimer(FormatMultiplier(speedMultiplier2)) + ")"; text2 = text2 + "\n" + ColorizeDetailLine(text4); text2 = text2 + "\n" + ColorizeDetailLine(StationText.FermentationGuidanceLabel); } } InsertAfterFirstLine(ref __result, text2); } private static string ColorizeDetailLine(string line) { return "" + line + ""; } private static void InsertAfterFirstLine(ref string hoverText, string text) { int num = hoverText.IndexOf('\n'); hoverText = ((num >= 0) ? hoverText.Insert(num, "\n" + text) : (hoverText + "\n" + text)); } private static string FormatMultiplier(float multiplier) { return "x" + multiplier.ToString("0.##", CultureInfo.InvariantCulture); } } internal static class StationHoverTime { private const float MinimumWindPower = 0.0001f; internal static bool CanShow(Component station) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (StationModule.IsInitialized && (Object)(object)Player.m_localPlayer != (Object)null) { return PrivateArea.CheckAccess(station.transform.position, 0f, false, false); } return false; } internal static bool TryGetSmelterRemaining(Smelter smelter, out int queueSize, out double seconds) { queueSize = 0; seconds = 0.0; if (smelter.m_secPerProduct <= 0f || !TryGetZdo((Component)(object)smelter, out ZDO zdo)) { return false; } queueSize = zdo.GetInt(ZDOVars.s_queued, 0); if (queueSize <= 0) { return false; } double num = Math.Max(0.0, smelter.m_secPerProduct - zdo.GetFloat(ZDOVars.s_bakeTimer, 0f)); if ((Object)(object)smelter.m_windmill != (Object)null) { float powerOutput = smelter.m_windmill.GetPowerOutput(); if (powerOutput <= 0.0001f) { return false; } num /= (double)powerOutput; } seconds = num; return true; } internal static string InsertAfter(string hoverText, string marker, string text) { if (string.IsNullOrEmpty(text)) { return hoverText; } int num = hoverText.IndexOf(marker, StringComparison.Ordinal); if (num < 0) { return InsertAtEndOfFirstLine(hoverText, text); } int startIndex = num + marker.Length; return hoverText.Insert(startIndex, " " + text); } internal static string InsertAtEndOfFirstLine(string hoverText, string text) { if (string.IsNullOrEmpty(text)) { return hoverText; } int num = hoverText.IndexOf('\n'); int startIndex = ((num >= 0) ? num : hoverText.Length); return hoverText.Insert(startIndex, " " + text); } private static bool TryGetZdo(Component component, out ZDO? zdo) { ZNetView val = component.GetComponent() ?? component.GetComponentInParent(); zdo = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); return zdo != null; } } internal static class StationInputResolver { private enum FermenterStatus { Empty, Fermenting, Exposed, Ready } private const float CandidateCacheSeconds = 0.2f; private static readonly MethodInfo? CookingStationIsFireLitMethod = AccessTools.Method(typeof(CookingStation), "IsFireLit", (Type[])null, (Type[])null); private static readonly MethodInfo? CookingStationGetFreeSlotMethod = AccessTools.Method(typeof(CookingStation), "GetFreeSlot", (Type[])null, (Type[])null); private static readonly MethodInfo? CookingStationGetFuelMethod = AccessTools.Method(typeof(CookingStation), "GetFuel", (Type[])null, (Type[])null); private static readonly MethodInfo? SmelterGetQueueSizeMethod = AccessTools.Method(typeof(Smelter), "GetQueueSize", (Type[])null, (Type[])null); private static readonly MethodInfo? FermenterGetStatusMethod = AccessTools.Method(typeof(Fermenter), "GetStatus", (Type[])null, (Type[])null); private static readonly FieldInfo? FermenterHasRoofField = AccessTools.Field(typeof(Fermenter), "m_hasRoof"); private static readonly FieldInfo? FermenterExposedField = AccessTools.Field(typeof(Fermenter), "m_exposed"); private static Component? _cachedStation; private static string _cachedMode = string.Empty; private static int _cachedMax; private static float _cacheExpiresAt; private static IReadOnlyList _cachedCandidates = Array.Empty(); internal static void Reset() { _cachedStation = null; _cachedMode = string.Empty; _cachedMax = 0; _cacheExpiresAt = 0f; _cachedCandidates = Array.Empty(); } internal static void ShowCookingStation(CookingStation station, Switch? switchRef) { if (!CanShow() || (Object)(object)station == (Object)null) { return; } int hintLimit = StationModule.GetHintLimit(StationModule.CookingStationRows.Value); if (hintLimit <= 0) { return; } List list = new List(); Switch addFuelSwitch = station.m_addFuelSwitch; Switch addFoodSwitch = station.m_addFoodSwitch; string mode; if ((Object)(object)switchRef != (Object)null && (Object)(object)switchRef == (Object)(object)addFuelSwitch) { mode = "CookingFuel"; ItemDrop fuelItem = station.m_fuelItem; int maxFuel = station.m_maxFuel; if ((Object)(object)fuelItem == (Object)null || !station.m_useFuel || maxFuel <= 0 || InvokeFloat(CookingStationGetFuelMethod, station, maxFuel) > (float)(maxFuel - 1) || !IsValidItem(fuelItem)) { return; } list.Add(fuelItem); } else { if (!((Object)(object)switchRef == (Object)null) && !((Object)(object)switchRef == (Object)(object)addFoodSwitch)) { return; } mode = "CookingFood"; bool num = station.m_requireFire && !InvokeBool(CookingStationIsFireLitMethod, station); bool flag = InvokeInt(CookingStationGetFreeSlotMethod, station, -1) == -1; if (!num && !flag && station.m_conversion != null) { list.AddRange(from conversion in station.m_conversion where conversion != null && IsValidItem(conversion.m_from) select conversion.m_from); } } IReadOnlyList candidates = CookingProgressResolver.GetCandidates(station, hintLimit); int num2 = (candidates.Count + 5 - 1) / 5; int num3 = Math.Max(0, hintLimit - num2 * 5); IReadOnlyList readOnlyList2; if (num3 <= 0) { IReadOnlyList readOnlyList = Array.Empty(); readOnlyList2 = readOnlyList; } else { readOnlyList2 = GetCachedOrBuild(mode, (Component)(object)station, list, num3); } IReadOnlyList inputCandidates = readOnlyList2; StationHintUi.ShowCookingStation(candidates, inputCandidates); } internal static void ShowSmelter(Smelter smelter, Switch? switchRef) { if (!CanShow() || (Object)(object)smelter == (Object)null) { return; } bool num = (Object)(object)smelter.m_windmill != (Object)null; string text = (num ? "Windmill" : "Smelter"); int hintLimit = StationModule.GetHintLimit(num ? StationModule.WindmillRows.Value : StationModule.SmelterRows.Value); if (hintLimit <= 0) { return; } List list = new List(); Switch addWoodSwitch = smelter.m_addWoodSwitch; Switch addOreSwitch = smelter.m_addOreSwitch; if (((Object)(object)switchRef != (Object)null && (Object)(object)switchRef == (Object)(object)addWoodSwitch) || !((Object)(object)switchRef != (Object)null) || !((Object)(object)switchRef == (Object)(object)addOreSwitch)) { return; } string mode = text + "Input"; int maxOre = smelter.m_maxOre; if (maxOre <= 0 || InvokeInt(SmelterGetQueueSizeMethod, smelter, maxOre) >= maxOre) { return; } if (smelter.m_conversion != null) { list.AddRange(from conversion in smelter.m_conversion where conversion != null && IsValidItem(conversion.m_from) select conversion.m_from); } ShowFor(mode, (Component)(object)smelter, list, hintLimit); } internal static void ShowFermenter(Fermenter fermenter) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (!CanShow() || (Object)(object)fermenter == (Object)null) { return; } int hintLimit = StationModule.GetHintLimit(StationModule.FermenterRows.Value); if (hintLimit <= 0) { return; } FermenterStatus fermenterStatus = GetFermenterStatus(fermenter); bool flag = ReadBool(FermenterHasRoofField, fermenter); bool flag2 = ReadBool(FermenterExposedField, fermenter); if (!(fermenterStatus != FermenterStatus.Empty || !flag || flag2) && PrivateArea.CheckAccess(((Component)fermenter).transform.position, 0f, false, false)) { List inputs = ((fermenter.m_conversion == null) ? new List() : (from conversion in fermenter.m_conversion where conversion != null && IsValidItem(conversion.m_from) select conversion.m_from).ToList()); ShowFor("FermenterInput", (Component)(object)fermenter, inputs, hintLimit); } } private static bool CanShow() { if (StationModule.IsInitialized) { return (Object)(object)Player.m_localPlayer != (Object)null; } return false; } private static void ShowFor(string mode, Component station, IEnumerable inputs, int max) { IReadOnlyList cachedOrBuild = GetCachedOrBuild(mode, station, inputs, max); if (cachedOrBuild.Count > 0) { StationHintUi.Show(cachedOrBuild); } } private static IReadOnlyList GetCachedOrBuild(string mode, Component station, IEnumerable inputs, int max) { if ((Object)(object)_cachedStation == (Object)(object)station && _cachedMode == mode && _cachedMax == max && Time.unscaledTime < _cacheExpiresAt) { return _cachedCandidates; } IReadOnlyList readOnlyList = BuildCandidates(station, inputs, max); _cachedStation = station; _cachedMode = mode; _cachedMax = max; _cacheExpiresAt = Time.unscaledTime + 0.2f; _cachedCandidates = readOnlyList; return readOnlyList; } private static IReadOnlyList BuildCandidates(Component station, IEnumerable inputs, int max) { Player localPlayer = Player.m_localPlayer; Inventory inventory = ((Humanoid)localPlayer).GetInventory(); string prefabName = Utils.GetPrefabName(station.gameObject); HashSet hashSet = new HashSet(StringComparer.Ordinal); List list = new List(max); List list2 = new List(max); List result = new List(max); AzuCraftyBoxesCompatibility.NearbyContainerQuery nearbyContainerQuery = null; bool flag = false; foreach (ItemDrop input in inputs) { if (!TryReadItem(input, out string prefabName2, out string sharedName, out Sprite icon) || !hashSet.Add(prefabName2) || !IsKnown(localPlayer, prefabName2, sharedName)) { continue; } int num = inventory.CountItems(sharedName, -1, true); if (num <= 0) { if (!AzuCraftyBoxesCompatibility.CanItemBePulled(prefabName, prefabName2)) { continue; } if (!flag) { flag = true; nearbyContainerQuery = AzuCraftyBoxesCompatibility.CreateNearbyQuery(station); } if ((nearbyContainerQuery?.CountAvailable(prefabName2, sharedName) ?? 0) <= 0) { continue; } } StationHintCandidate item = new StationHintCandidate((Localization.instance != null) ? Localization.instance.Localize(sharedName) : sharedName, icon); if (num > 0) { list.Add(item); } else { list2.Add(item); } } AppendUpToMax(result, list, max); AppendUpToMax(result, list2, max); return result; } private static void AppendUpToMax(List result, List source, int max) { foreach (StationHintCandidate item in source) { if (result.Count >= max) { break; } result.Add(item); } } private static bool TryReadItem(ItemDrop item, out string prefabName, out string sharedName, out Sprite? icon) { prefabName = string.Empty; sharedName = string.Empty; icon = null; if (!IsValidItem(item)) { return false; } prefabName = Utils.GetPrefabName(((Component)item).gameObject); sharedName = item.m_itemData.m_shared.m_name; if (string.IsNullOrWhiteSpace(prefabName) || string.IsNullOrWhiteSpace(sharedName)) { return false; } Sprite[] icons = item.m_itemData.m_shared.m_icons; if (icons != null && icons.Length != 0) { icon = icons[0]; } return true; } private static bool IsKnown(Player player, string prefabName, string sharedName) { if (!player.IsKnownMaterial(sharedName) && !player.IsRecipeKnown(sharedName) && !player.IsKnownMaterial(prefabName)) { return player.IsRecipeKnown(prefabName); } return true; } private static bool IsValidItem(ItemDrop? item) { if ((Object)(object)item != (Object)null && item.m_itemData != null) { return item.m_itemData.m_shared != null; } return false; } private static FermenterStatus GetFermenterStatus(Fermenter fermenter) { return (FermenterStatus)InvokeInt(FermenterGetStatusMethod, fermenter, 1); } private static bool InvokeBool(MethodInfo? method, object instance, bool fallback = false) { object obj = Invoke(method, instance); if (obj is bool) { return (bool)obj; } return fallback; } private static int InvokeInt(MethodInfo? method, object instance, int fallback) { object obj = Invoke(method, instance); if (obj is Enum value) { return Convert.ToInt32(value); } if (obj is int) { return (int)obj; } return fallback; } private static float InvokeFloat(MethodInfo? method, object instance, float fallback) { object obj = Invoke(method, instance); if (obj is float) { return (float)obj; } return fallback; } private static object? Invoke(MethodInfo? method, object instance) { if (method == null) { return null; } try { return method.Invoke(instance, null); } catch (Exception ex) { FineDiningPlugin.Log.LogDebug((object)("Could not invoke " + method.DeclaringType?.Name + "." + method.Name + ": " + ex.Message)); return null; } } private static bool ReadBool(FieldInfo? field, object instance, bool fallback = false) { try { return (field?.GetValue(instance) is bool flag) ? flag : fallback; } catch (Exception ex) { FineDiningPlugin.Log.LogDebug((object)("Could not read " + field?.DeclaringType?.Name + "." + field?.Name + ": " + ex.Message)); return fallback; } } } internal static class StationModule { internal const int HintColumns = 5; internal const int MaxHintRows = 4; internal const int DefaultHintRows = 2; internal const int MaxHints = 20; private static bool _initialized; private static HashSet _fermenterBonusExcludedPrefabNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static ConfigEntry CookingStationRows { get; private set; } = null; internal static ConfigEntry SmelterRows { get; private set; } = null; internal static ConfigEntry WindmillRows { get; private set; } = null; internal static ConfigEntry FermenterRows { get; private set; } = null; internal static ConfigEntry GrimpyBoxRows { get; private set; } = null; internal static ConfigEntry IconGroupScale { get; private set; } = null; internal static ConfigEntry FermenterCoverMaxSpeedMultiplier { get; private set; } = null; internal static ConfigEntry FermenterDepthMaxSpeedMultiplier { get; private set; } = null; internal static ConfigEntry FermenterBonusExcludedPrefabs { get; private set; } = null; internal static bool IsInitialized => _initialized; internal static void Initialize(ConfigFile config, ConfigSync configSync) { if (!_initialized) { IconGroupScale = config.Bind(ConfigPresentation.ClientSection.Name, "Station Icon Scale", 1f, ConfigPresentation.Client("Scale of the station hover icon group.", ConfigPresentation.ClientSection, 600, (AcceptableValueBase?)(object)new AcceptableValueRange(0.25f, 2f))); CookingStationRows = config.Bind(ConfigPresentation.ClientSection.Name, "Station Icon Rows - Cooking Station", 2, ConfigPresentation.Client("Number of five-icon rows available to cooking progress and input hints. Progress rows take priority. 0 hides cooking station icons.", ConfigPresentation.ClientSection, 500, (AcceptableValueBase?)(object)new AcceptableValueRange(0, 4))); SmelterRows = config.Bind(ConfigPresentation.ClientSection.Name, "Station Icon Rows - Smelter", 2, ConfigPresentation.Client("Number of five-icon rows available to smelter input hints. 0 hides smelter icons without hiding processing-time text.", ConfigPresentation.ClientSection, 400, (AcceptableValueBase?)(object)new AcceptableValueRange(0, 4))); WindmillRows = config.Bind(ConfigPresentation.ClientSection.Name, "Station Icon Rows - Windmill", 2, ConfigPresentation.Client("Number of five-icon rows available to windmill input hints. 0 hides windmill icons without hiding processing-time text.", ConfigPresentation.ClientSection, 300, (AcceptableValueBase?)(object)new AcceptableValueRange(0, 4))); FermenterRows = config.Bind(ConfigPresentation.ClientSection.Name, "Station Icon Rows - Fermenter", 2, ConfigPresentation.Client("Number of five-icon rows available to fermenter input hints. 0 hides fermenter icons without hiding time or environment details.", ConfigPresentation.ClientSection, 200, (AcceptableValueBase?)(object)new AcceptableValueRange(0, 4))); GrimpyBoxRows = config.Bind(ConfigPresentation.ClientSection.Name, "Station Icon Rows - Grimpy Box", 2, ConfigPresentation.Client("Number of five-icon rows available to optional ValheimCuisine Grimpy Box conversion hints. 0 hides FineDining's Grimpy Box icons.", ConfigPresentation.ClientSection, 100, (AcceptableValueBase?)(object)new AcceptableValueRange(0, 4))); FermenterBonusExcludedPrefabs = BindSynced(config, configSync, ConfigPresentation.General, "Fermenter Bonus Excluded Prefabs", string.Empty, ConfigPresentation.Synced("Exact internal prefab names of Fermenters that keep native timing and output behavior. Separate names with commas, semicolons, or new lines. FineDining still shows remaining time and input icons, but does not apply or show cover/depth acceleration, Cooking output bonuses, or insertion/collection Cooking experience.", ConfigPresentation.General, 250)); RebuildFermenterBonusExcludedPrefabNames(); FermenterBonusExcludedPrefabs.SettingChanged += OnFermenterBonusExcludedPrefabsChanged; FermenterCoverMaxSpeedMultiplier = BindSynced(config, configSync, ConfigPresentation.General, "Fermenter Cover Maximum Multiplier", 2f, ConfigPresentation.Synced("Fermentation speed multiplier at 100% cover. The bonus scales linearly from x1 at the vanilla minimum required cover to this value at full cover.", ConfigPresentation.General, 200, (AcceptableValueBase?)(object)new AcceptableValueRange(1f, 10f))); FermenterDepthMaxSpeedMultiplier = BindSynced(config, configSync, ConfigPresentation.General, "Fermenter Depth Maximum Multiplier", 2f, ConfigPresentation.Synced("Fermentation speed multiplier at 8 meters or more beneath the original terrain baseline. The bonus scales linearly from x1 at the baseline to this value at 8 meters depth.", ConfigPresentation.General, 100, (AcceptableValueBase?)(object)new AcceptableValueRange(1f, 10f))); _initialized = true; AzuCraftyBoxesCompatibility.Initialize(); ValheimCuisineCompatibility.Initialize(); } } internal static int GetHintLimit(int rows) { return ((rows >= 0) ? ((rows > 4) ? 4 : rows) : 0) * 5; } internal static bool IsFermenterBonusExcluded(Fermenter? fermenter) { string fermenterPrefabName = GetFermenterPrefabName(fermenter); if (fermenterPrefabName.Length > 0) { return _fermenterBonusExcludedPrefabNames.Contains(fermenterPrefabName); } return false; } internal static string GetFermenterPrefabName(Fermenter? fermenter) { if ((Object)(object)fermenter == (Object)null) { return string.Empty; } ZNetView val = ((Component)fermenter).GetComponent() ?? ((Component)fermenter).GetComponentInParent(); if ((Object)(object)val != (Object)null) { if (val.IsValid()) { ZDO zDO = val.GetZDO(); if (zDO != null && (Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(zDO.GetPrefab()); string text = FoodIdentity.NormalizePrefabName((prefab != null) ? ((Object)prefab).name : null); if (text.Length > 0) { return text; } } } string text2 = FoodIdentity.NormalizePrefabName(((Object)((Component)val).gameObject).name); if (text2.Length > 0) { return text2; } } return FoodIdentity.NormalizePrefabName(((Object)((Component)fermenter).gameObject).name); } internal static void Shutdown() { if (_initialized) { FermenterEnvironmentSpeedSystem.CheckpointAllOwners(); FermenterBonusExcludedPrefabs.SettingChanged -= OnFermenterBonusExcludedPrefabsChanged; StationHintUi.Shutdown(); StationInputResolver.Reset(); AzuCraftyBoxesCompatibility.Shutdown(); ValheimCuisineCompatibility.Shutdown(); FermenterEnvironmentSpeedSystem.ResetRuntime(); _fermenterBonusExcludedPrefabNames = new HashSet(StringComparer.OrdinalIgnoreCase); _initialized = false; } } private static void OnFermenterBonusExcludedPrefabsChanged(object sender, EventArgs eventArgs) { if (_initialized) { FermenterEnvironmentSpeedSystem.CheckpointAllOwners(); } RebuildFermenterBonusExcludedPrefabNames(); if (_initialized) { FermenterEnvironmentSpeedSystem.CheckpointAllOwners(); } } private static void RebuildFermenterBonusExcludedPrefabNames() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); string[] array = (FermenterBonusExcludedPrefabs?.Value ?? string.Empty).Split(new char[4] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = FoodIdentity.NormalizePrefabName(array[i]); if (text.Length > 0) { hashSet.Add(text); } } _fermenterBonusExcludedPrefabNames = hashSet; } private static ConfigEntry BindSynced(ConfigFile config, ConfigSync configSync, ConfigPresentation.SectionDefinition section, string name, T value, ConfigDescription description) { ConfigEntry val = config.Bind(section.Name, name, value, description); configSync.AddConfigEntry(val).SynchronizedConfig = true; return val; } } internal static class StationText { internal const string TimerColorHex = "#FFD138"; internal const string CoverToken = "$finedining_station_cover"; internal const string DepthToken = "$finedining_station_depth"; internal const string RateToken = "$finedining_station_rate"; internal const string FermentationSpeedToken = "$finedining_station_fermentation_speed"; internal const string FermentationGuidanceToken = "$finedining_station_fermentation_guidance"; internal const string SecondsToken = "$finedining_station_seconds"; internal const string AutoEjectToken = "$finedining_station_auto_eject"; internal static string CoverLabel => Localize("$finedining_station_cover", "Cover"); internal static string DepthLabel => Localize("$finedining_station_depth", "Depth"); internal static string RateLabel => Localize("$finedining_station_rate", "Rate"); internal static string FermentationSpeedLabel => Localize("$finedining_station_fermentation_speed", "Fermentation speed"); internal static string FermentationGuidanceLabel => Localize("$finedining_station_fermentation_guidance", "More cover and greater depth make fermentation faster."); internal static string AutoEjectLabel => Localize("$finedining_station_auto_eject", "Auto eject"); internal static string ColorizeTimer(string text) { if (!string.IsNullOrEmpty(text)) { return "" + text + ""; } return text; } internal static string FormatDuration(double seconds, bool keepAtLeastOneSecond = false) { if (!TryGetTotalSeconds(seconds, keepAtLeastOneSecond, out var totalSeconds)) { return string.Empty; } long num = totalSeconds / 3600; long num2 = totalSeconds % 3600 / 60; long num3 = totalSeconds % 60; if (num <= 0) { return $"{num2:00}:{num3:00}"; } return $"{num}:{num2:00}:{num3:00}"; } internal static string FormatSeconds(double seconds, bool keepAtLeastOneSecond = false) { return FormatSecondsCore(seconds, keepAtLeastOneSecond, Localize("$finedining_station_seconds", "{0}s")); } internal static string FormatSecondsCore(double seconds, bool keepAtLeastOneSecond, string format) { if (!TryGetTotalSeconds(seconds, keepAtLeastOneSecond, out var totalSeconds)) { return string.Empty; } try { return string.Format(CultureInfo.InvariantCulture, format, totalSeconds); } catch (FormatException) { return totalSeconds.ToString(CultureInfo.InvariantCulture) + "s"; } } private static bool TryGetTotalSeconds(double seconds, bool keepAtLeastOneSecond, out long totalSeconds) { totalSeconds = 0L; if (double.IsNaN(seconds) || double.IsInfinity(seconds) || seconds < 0.0) { return false; } totalSeconds = (long)Math.Ceiling(Math.Min(seconds, 2147483647.0)); if (keepAtLeastOneSecond && totalSeconds < 1) { totalSeconds = 1L; } return true; } private static string Localize(string token, string fallback) { Localization instance = Localization.instance; if (instance == null) { return fallback; } string text = instance.Localize(token); if (!string.IsNullOrWhiteSpace(text) && !string.Equals(text, token, StringComparison.Ordinal)) { return text; } return fallback; } } internal static class ValheimCuisineCompatibility { private sealed class GrimpyRecipe { internal int RequiredAmount { get; } internal int ProducedAmount { get; } internal GrimpyRecipe(int requiredAmount, int producedAmount) { RequiredAmount = requiredAmount; ProducedAmount = producedAmount; } } internal const string PluginGuid = "XutzBR.ValheimCuisine"; internal const string GrimpyConverterTypeName = "ValheimCuisine.ValheimCuisinePlugin+GrimpyBoxConverter"; internal const string FreydisCollectorTypeName = "ValheimCuisine.ValheimCuisinePlugin+FreydisCollector"; private const string GrimpyConversionItemsFieldName = "GrimpyBoxConversionItems"; private const string FreydisSecondsPerUnitFieldName = "m_secPerUnit"; private const string FreydisMaximumLevelFieldName = "m_maxLevel"; private const float CandidateCacheSeconds = 0.2f; private static Type? _grimpyConverterType; private static Type? _freydisCollectorType; private static FieldInfo? _grimpyConversionItemsField; private static FieldInfo? _freydisSecondsPerUnitField; private static FieldInfo? _freydisMaximumLevelField; private static bool _initialized; private static bool _grimpyBroken; private static bool _freydisBroken; private static string? _grimpyRecipeSource; private static Dictionary _grimpyRecipes = new Dictionary(StringComparer.Ordinal); private static Component? _cachedGrimpyConverter; private static float _grimpyCacheExpiresAt; private static IReadOnlyList _cachedGrimpyCandidates = Array.Empty(); private static int _cachedGrimpyMaxHints; internal static void Initialize() { if (_initialized) { return; } _initialized = true; if (Chainloader.PluginInfos.TryGetValue("XutzBR.ValheimCuisine", out var value) && !((Object)(object)value.Instance == (Object)null)) { Assembly assembly = ((object)value.Instance).GetType().Assembly; Type type = ((object)value.Instance).GetType(); try { InitializeGrimpy(assembly, type); } catch (Exception exception) { DisableGrimpy("ValheimCuisine Grimpy Box compatibility could not be initialized.", exception); } try { InitializeFreydis(assembly); } catch (Exception exception2) { DisableFreydis("ValheimCuisine Freydis compatibility could not be initialized.", exception2); } if (!_grimpyBroken || !_freydisBroken) { FineDiningPlugin.Log.LogInfo((object)("Enabled optional ValheimCuisine " + value.Metadata.Version?.ToString() + " station-hover compatibility.")); } } } private static void InitializeGrimpy(Assembly assembly, Type pluginType) { _grimpyConverterType = ValidateComponentType(assembly.GetType("ValheimCuisine.ValheimCuisinePlugin+GrimpyBoxConverter", throwOnError: false)); _grimpyConversionItemsField = pluginType.GetField("GrimpyBoxConversionItems", BindingFlags.Static | BindingFlags.Public); if (!(_grimpyConverterType != null) || !(_grimpyConversionItemsField?.FieldType == typeof(ConfigEntry))) { _grimpyConverterType = null; _grimpyConversionItemsField = null; _grimpyBroken = true; FineDiningPlugin.Log.LogWarning((object)"ValheimCuisine is installed, but its Grimpy Box conversion contract was not found. Grimpy Box hover icons are disabled."); } } private static void InitializeFreydis(Assembly assembly) { _freydisCollectorType = ValidateComponentType(assembly.GetType("ValheimCuisine.ValheimCuisinePlugin+FreydisCollector", throwOnError: false)); _freydisSecondsPerUnitField = _freydisCollectorType?.GetField("m_secPerUnit", BindingFlags.Instance | BindingFlags.Public); _freydisMaximumLevelField = _freydisCollectorType?.GetField("m_maxLevel", BindingFlags.Instance | BindingFlags.Public); if (!(_freydisCollectorType != null) || !(_freydisSecondsPerUnitField?.FieldType == typeof(float)) || !(_freydisMaximumLevelField?.FieldType == typeof(int))) { _freydisCollectorType = null; _freydisSecondsPerUnitField = null; _freydisMaximumLevelField = null; _freydisBroken = true; FineDiningPlugin.Log.LogWarning((object)"ValheimCuisine is installed, but its Freydis collector contract was not found. Freydis hover timing is disabled."); } } internal static void Shutdown() { _grimpyConverterType = null; _freydisCollectorType = null; _grimpyConversionItemsField = null; _freydisSecondsPerUnitField = null; _freydisMaximumLevelField = null; _initialized = false; _grimpyBroken = false; _freydisBroken = false; _grimpyRecipeSource = null; _grimpyRecipes.Clear(); ResetGrimpyCandidateCache(); } internal static bool TryShow(Hud hud, GameObject hoverObject, Hoverable hoverable, Player player) { if (!_initialized) { Initialize(); } Component val = FindComponentInParents(hoverObject, hoverable, _freydisCollectorType); if ((Object)(object)val != (Object)null) { ShowFreydis(hud, val); return true; } Container val2 = (Container)(object)((hoverable is Container) ? hoverable : null); if (val2 != null && _grimpyConverterType != null) { Component component = ((Component)val2).GetComponent(_grimpyConverterType); if ((Object)(object)component != (Object)null) { ShowGrimpy(component, val2, player); return true; } } return false; } internal static double CalculateFreydisRemainingSeconds(double secondsPerUnit, double accumulatedSeconds, long lastUpdateTicks, long serverNowTicks) { if (double.IsNaN(secondsPerUnit) || double.IsInfinity(secondsPerUnit) || secondsPerUnit <= 0.0) { return double.NaN; } if (double.IsNaN(accumulatedSeconds) || double.IsInfinity(accumulatedSeconds) || accumulatedSeconds < 0.0) { accumulatedSeconds = 0.0; } double num = 0.0; if (lastUpdateTicks > 0 && serverNowTicks > lastUpdateTicks) { num = (double)(serverNowTicks - lastUpdateTicks) / 10000000.0; } double num2 = accumulatedSeconds + num; if (!(num2 >= secondsPerUnit)) { return secondsPerUnit - num2; } return 0.0; } internal static bool TryParseGrimpyRecipe(string? text, out string prefabName, out int requiredAmount, out int producedAmount) { prefabName = string.Empty; requiredAmount = 0; producedAmount = 0; if (string.IsNullOrWhiteSpace(text)) { return false; } string[] array = text.Split(new char[1] { ':' }); if (array.Length != 3) { return false; } prefabName = array[0].Trim(); if (prefabName.Length > 0 && int.TryParse(array[1].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out requiredAmount) && int.TryParse(array[2].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out producedAmount) && requiredAmount > 0) { return producedAmount > 0; } return false; } private static Type? ValidateComponentType(Type? type) { if (!(type != null) || !typeof(Component).IsAssignableFrom(type)) { return null; } return type; } private static Component? FindComponentInParents(GameObject hoverObject, Hoverable hoverable, Type? componentType) { if (componentType == null) { return null; } Component val = (Component)(object)((hoverable is Component) ? hoverable : null); if (val != null && componentType.IsInstanceOfType(val)) { return val; } Transform val2 = hoverObject.transform; while ((Object)(object)val2 != (Object)null) { Component component = ((Component)val2).GetComponent(componentType); if ((Object)(object)component != (Object)null) { return component; } val2 = val2.parent; } return null; } private static void ShowGrimpy(Component converter, Container container, Player player) { if (_grimpyBroken) { return; } try { int hintLimit = StationModule.GetHintLimit(StationModule.GrimpyBoxRows.Value); if (hintLimit > 0) { IReadOnlyList grimpyCandidates = GetGrimpyCandidates(converter, container, player, hintLimit); if (grimpyCandidates.Count > 0) { StationHintUi.Show(grimpyCandidates); } } } catch (Exception exception) { DisableGrimpy("Grimpy Box hover icons failed.", exception); } } private static IReadOnlyList GetGrimpyCandidates(Component converter, Container container, Player player, int maxHints) { if ((Object)(object)_cachedGrimpyConverter == (Object)(object)converter && _cachedGrimpyMaxHints == maxHints && Time.unscaledTime < _grimpyCacheExpiresAt) { return _cachedGrimpyCandidates; } RefreshGrimpyRecipes(); List list = new List(maxHints); if (_grimpyRecipes.Count > 0) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); Dictionary dictionary2 = new Dictionary(StringComparer.Ordinal); List list2 = new List(); HashSet seen = new HashSet(StringComparer.Ordinal); Inventory inventory = container.GetInventory(); AddGrimpyItems((inventory != null) ? inventory.GetAllItems() : null, dictionary, dictionary2, list2, seen, countAsContained: true); Inventory inventory2 = ((Humanoid)player).GetInventory(); AddGrimpyItems((inventory2 != null) ? inventory2.GetAllItems() : null, dictionary, dictionary2, list2, seen, countAsContained: false); string localizedItemName = GetLocalizedItemName("Ectoplasm", "Ectoplasm"); foreach (string item in list2) { if (list.Count < maxHints && _grimpyRecipes.TryGetValue(item, out GrimpyRecipe value) && dictionary2.TryGetValue(item, out var value2)) { int value3; int num = (dictionary.TryGetValue(item, out value3) ? value3 : 0); list.Add(new StationHintCandidate(GetLocalizedItemName(value2), GetItemIcon(value2), num.ToString(CultureInfo.InvariantCulture) + "/" + value.RequiredAmount.ToString(CultureInfo.InvariantCulture), Localize("$finedining_station_grimpy_output", "→ $1 ×$2", localizedItemName, value.ProducedAmount.ToString(CultureInfo.InvariantCulture)))); } } } _cachedGrimpyConverter = converter; _cachedGrimpyMaxHints = maxHints; _grimpyCacheExpiresAt = Time.unscaledTime + 0.2f; _cachedGrimpyCandidates = list; return list; } private static void AddGrimpyItems(IReadOnlyList? items, IDictionary containedAmounts, IDictionary sampleItems, ICollection orderedPrefabs, ISet seen, bool countAsContained) { if (items == null) { return; } foreach (ItemData item in items) { string canonicalPrefabName = FoodIdentity.GetCanonicalPrefabName(item); if (_grimpyRecipes.ContainsKey(canonicalPrefabName)) { sampleItems[canonicalPrefabName] = item; if (seen.Add(canonicalPrefabName)) { orderedPrefabs.Add(canonicalPrefabName); } if (countAsContained && item.m_stack > 0) { int value; long val = (containedAmounts.TryGetValue(canonicalPrefabName, out value) ? ((long)value + (long)item.m_stack) : item.m_stack); containedAmounts[canonicalPrefabName] = (int)Math.Min(2147483647L, val); } } } } private static void RefreshGrimpyRecipes() { string text = ((_grimpyConversionItemsField?.GetValue(null) as ConfigEntry) ?? throw new InvalidOperationException("ValheimCuisine GrimpyBoxConversionItems is unavailable.")).Value ?? string.Empty; if (string.Equals(text, _grimpyRecipeSource, StringComparison.Ordinal)) { return; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); string[] array = text.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { if (TryParseGrimpyRecipe(array[i], out string prefabName, out int requiredAmount, out int producedAmount)) { dictionary[prefabName] = new GrimpyRecipe(requiredAmount, producedAmount); } } _grimpyRecipeSource = text; _grimpyRecipes = dictionary; ResetGrimpyCandidateCache(); } private static void ShowFreydis(Hud hud, Component collector) { if (_freydisBroken || (Object)(object)hud.m_hoverName == (Object)null) { return; } try { object obj = _freydisSecondsPerUnitField?.GetValue(collector); object obj2 = _freydisMaximumLevelField?.GetValue(collector); if (!(obj is float num) || !(obj2 is int num2) || float.IsNaN(num) || float.IsInfinity(num) || num <= 0f || num2 <= 0) { return; } ZNetView component = collector.GetComponent(); ZDO val = ((component != null) ? component.GetZDO() : null); if (val == null || (Object)(object)ZNet.instance == (Object)null) { return; } int num3 = Mathf.Clamp(val.GetInt(ZDOVars.s_level, 0), 0, num2); string line; if (num3 >= num2) { line = Localize("$finedining_station_freydis_full", "Collection storage full $1/$2", num3.ToString(CultureInfo.InvariantCulture), num2.ToString(CultureInfo.InvariantCulture)); } else { long ticks = ZNet.instance.GetTime().Ticks; string text = StationText.FormatDuration(CalculateFreydisRemainingSeconds(num, val.GetFloat(ZDOVars.s_product, 0f), val.GetLong(ZDOVars.s_lastTime, ticks), ticks)); if (text.Length == 0) { return; } line = Localize("$finedining_station_freydis_progress", "Stored $1/$2 · next collection $3", num3.ToString(CultureInfo.InvariantCulture), num2.ToString(CultureInfo.InvariantCulture), StationText.ColorizeTimer(text)); } AppendHoverLine(hud, line); } catch (Exception exception) { DisableFreydis("Freydis hover timing failed.", exception); } } private static string GetLocalizedItemName(ItemData item) { string text = item.m_shared?.m_name ?? string.Empty; if (Localization.instance == null || text.Length <= 0) { return text; } return Localization.instance.Localize(text); } private static string GetLocalizedItemName(string prefabName, string fallback) { ObjectDB instance = ObjectDB.instance; object obj; if (instance == null) { obj = null; } else { GameObject itemPrefab = instance.GetItemPrefab(prefabName); obj = ((itemPrefab == null) ? null : itemPrefab.GetComponent()?.m_itemData); } ItemData val = (ItemData)obj; string text = ((val != null) ? GetLocalizedItemName(val) : string.Empty); if (!string.IsNullOrWhiteSpace(text)) { return text; } return fallback; } private static Sprite? GetItemIcon(ItemData item) { Sprite[] array = item.m_shared?.m_icons; if (array == null || array.Length == 0) { return null; } return array[0]; } private static string Localize(string token, string fallback, params string[] arguments) { Localization instance = Localization.instance; string text = ((instance != null) ? instance.Localize(token, arguments) : null) ?? token; if (!string.IsNullOrWhiteSpace(text) && !string.Equals(text, token, StringComparison.Ordinal)) { return text; } string text2 = fallback; for (int i = 0; i < arguments.Length; i++) { text2 = text2.Replace("$" + (i + 1).ToString(CultureInfo.InvariantCulture), arguments[i]); } return text2; } private static void AppendHoverLine(Hud hud, string line) { string text = ((TMP_Text)hud.m_hoverName).text ?? string.Empty; if (!ContainsLine(text, line)) { ((TMP_Text)hud.m_hoverName).text = ((text.Length == 0) ? line : (text + (text.EndsWith("\n", StringComparison.Ordinal) ? string.Empty : "\n") + line)); } } private static bool ContainsLine(string text, string line) { if (string.Equals(text, line, StringComparison.Ordinal)) { return true; } string text2 = "\n" + line; int num = text.IndexOf(text2, StringComparison.Ordinal); if (num >= 0) { if (num + text2.Length != text.Length) { return text[num + text2.Length] == '\n'; } return true; } return false; } private static void ResetGrimpyCandidateCache() { _cachedGrimpyConverter = null; _cachedGrimpyMaxHints = 0; _grimpyCacheExpiresAt = 0f; _cachedGrimpyCandidates = Array.Empty(); } private static void DisableGrimpy(string message, Exception exception) { _grimpyBroken = true; _grimpyConverterType = null; _grimpyConversionItemsField = null; _grimpyRecipeSource = null; _grimpyRecipes.Clear(); ResetGrimpyCandidateCache(); FineDiningPlugin.Log.LogWarning((object)(message + " " + exception.GetBaseException().Message)); } private static void DisableFreydis(string message, Exception exception) { _freydisBroken = true; _freydisCollectorType = null; _freydisSecondsPerUnitField = null; _freydisMaximumLevelField = null; FineDiningPlugin.Log.LogWarning((object)(message + " " + exception.GetBaseException().Message)); } } } namespace ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry(ConfigEntry sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register(configSync2.Name + " ConfigSync", (Action)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List CurrentList = new List(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List(adminList.GetList()); List adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register(configSync.Name + " ConfigSync", (Action)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary configValues = new Dictionary(); public readonly Dictionary customValues = new Dictionary(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List Package = new List(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List entries = new List(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet configSyncs; private readonly HashSet allConfigs = new HashSet(); private HashSet allCustomValues = new HashSet(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary> configValueCache = new Dictionary>(); private readonly List> cacheExpirations = new List>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0052: result = 0; goto IL_0053; IL_0053: return (byte)result != 0; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry AddConfigEntry(ConfigEntry configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry syncedEntry = ownConfigEntryBase as SyncedConfigEntry; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty()).Concat(new SyncedConfigEntry[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry AddLockingConfigEntry(ConfigEntry lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(ZRoutedRpc.Everybody, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary value)) { value = new SortedDictionary(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int num2 = 0; num2 < num; num2++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config)); if (configSync == null) { return true; } return configSync.IsSourceOfTruth || !config.SynchronizedConfig || config.LocalBaseValue == null || (!configSync.IsLocked && (config != configSync.lockedConfig || lockExempt)); } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null)) { if (val == null) { val = item.BaseConfig.ConfigFile; saveOnConfigSet = val.SaveOnConfigSet; val.SaveOnConfigSet = false; } item.BaseConfig.BoxedValue = item.LocalBaseValue; item.LocalBaseValue = null; } if (val != null) { val.SaveOnConfigSet = saveOnConfigSet; } foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator distributeConfigToPeers(ZNetPeer peer, ZPackage package) { ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc == null) { yield break; } byte[] data = package.GetArray(); if (data != null && data.LongLength > 250000) { int fragments = (int)(1 + (data.LongLength - 1) / 250000); long packageIdentifier = ++packageCounter; int fragment = 0; while (fragment < fragments) { foreach (bool item in waitForQueue()) { yield return item; } if (peer.m_socket.IsConnected()) { ZPackage fragmentedPackage = new ZPackage(); fragmentedPackage.Write((byte)2); fragmentedPackage.Write(packageIdentifier); fragmentedPackage.Write(fragment); fragmentedPackage.Write(fragments); fragmentedPackage.Write(data.Skip(250000 * fragment).Take(250000).ToArray()); SendPackage(fragmentedPackage); if (fragment != fragments - 1) { yield return true; } int num = fragment + 1; fragment = num; continue; } break; } yield break; } foreach (bool item2 in waitForQueue()) { yield return item2; } SendPackage(package); void SendPackage(ZPackage pkg) { string text = Name + " ConfigSync"; if (isServer) { peer.m_rpc.Invoke(text, new object[1] { pkg }); } else { rpc.InvokeRoutedRPC(peer.m_server ? 0 : peer.m_uid, text, new object[1] { pkg }); } } IEnumerable waitForQueue() { float timeout = Time.time + 30f; while (peer.m_socket.GetSendQueueSize() > 20000) { if (Time.time > timeout) { Debug.Log((object)$"Disconnecting {peer.m_uid} after 30 seconds config sending timeout"); peer.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 }); ZNet.instance.Disconnect(peer); break; } yield return false; } } } private IEnumerator sendZPackage(long target, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { return Enumerable.Empty().GetEnumerator(); } List list = (List)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance); if (target != ZRoutedRpc.Everybody) { list = list.Where((ZNetPeer p) => p.m_uid == target).ToList(); } return sendZPackage(list, package); } private IEnumerator sendZPackage(List peers, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } byte[] rawData = package.GetArray(); if (rawData != null && rawData.LongLength > 10000) { ZPackage compressedPackage = new ZPackage(); compressedPackage.Write((byte)4); MemoryStream output = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(output, CompressionLevel.Optimal)) { deflateStream.Write(rawData, 0, rawData.Length); } compressedPackage.Write(output.ToArray()); package = compressedPackage; } List> writers = (from p in peers where p.IsReady() select distributeConfigToPeers(p, package)).ToList(); writers.RemoveAll((IEnumerator writer) => !writer.MoveNext()); while (writers.Count > 0) { yield return null; writers.RemoveAll((IEnumerator writer) => !writer.MoveNext()); } } private void Broadcast(long target, params ConfigEntryBase[] configs) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(configs); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private void Broadcast(long target, params CustomSyncedValueBase[] customValues) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(null, customValues); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private static OwnConfigEntryBase? configData(ConfigEntryBase config) { return config.Description.Tags?.OfType().SingleOrDefault(); } public static SyncedConfigEntry? ConfigData(ConfigEntry config) { return ((ConfigEntryBase)config).Description.Tags?.OfType>().SingleOrDefault(); } private static T configAttribute(ConfigEntryBase config) { return config.Description.Tags.OfType().First(); } private static Type configType(ConfigEntryBase config) { return configType(config.SettingType); } private static Type configType(Type type) { return type.IsEnum ? Enum.GetUnderlyingType(type) : type; } private static ZPackage ConfigsToPackage(IEnumerable? configs = null, IEnumerable? customValues = null, IEnumerable? packageEntries = null, bool partial = true) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown List list = configs?.Where((ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List(); List list2 = customValues?.ToList() ?? new List(); ZPackage val = new ZPackage(); val.Write((byte)(partial ? 1 : 0)); val.Write(list.Count + list2.Count + (packageEntries?.Count() ?? 0)); foreach (PackageEntry item in packageEntries ?? Array.Empty()) { AddEntryToPackage(val, item); } foreach (CustomSyncedValueBase item2 in list2) { AddEntryToPackage(val, new PackageEntry { section = "Internal", key = item2.Identifier, type = item2.Type, value = item2.BoxedValue }); } foreach (ConfigEntryBase item3 in list) { AddEntryToPackage(val, new PackageEntry { section = item3.Definition.Section, key = item3.Definition.Key, type = configType(item3), value = item3.BoxedValue }); } return val; } private static void AddEntryToPackage(ZPackage package, PackageEntry entry) { package.Write(entry.section); package.Write(entry.key); package.Write((entry.value == null) ? "" : GetZPackageTypeString(entry.type)); AddValueToZPackage(package, entry.value); } private static string GetZPackageTypeString(Type type) { return type.AssemblyQualifiedName; } private static void AddValueToZPackage(ZPackage package, object? value) { Type type = value?.GetType(); if (value is Enum) { value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture); } else { if (value is ICollection collection) { package.Write(collection.Count); { foreach (object item in collection) { AddValueToZPackage(package, item); } return; } } if ((object)type != null && type.IsValueType && !type.IsPrimitive) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); package.Write(fields.Length); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { package.Write(GetZPackageTypeString(fieldInfo.FieldType)); AddValueToZPackage(package, fieldInfo.GetValue(value)); } return; } } ZRpc.Serialize(new object[1] { value }, ref package); } private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type) { if ((object)type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int num = package.ReadInt(); if (num != fields.Length) { throw new InvalidDeserializationTypeException { received = $"(field count: {num})", expected = $"(field count: {fields.Length})" }; } object uninitializedObject = FormatterServices.GetUninitializedObject(type); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { string text = package.ReadString(); if (text != GetZPackageTypeString(fieldInfo.FieldType)) { throw new InvalidDeserializationTypeException { received = text, expected = GetZPackageTypeString(fieldInfo.FieldType), field = fieldInfo.Name }; } fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType)); } return uninitializedObject; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { int num2 = package.ReadInt(); IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments); FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); for (int j = 0; j < num2; j++) { object obj = ReadValueWithTypeFromZPackage(package, type2); dictionary.Add(field.GetValue(obj), field2.GetValue(obj)); } return dictionary; } if (type != typeof(List) && type.IsGenericType) { Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]); if ((object)type3 != null && type3.IsAssignableFrom(type)) { int num3 = package.ReadInt(); object obj2 = Activator.CreateInstance(type); MethodInfo method = type3.GetMethod("Add"); for (int k = 0; k < num3; k++) { method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) }); } return obj2; } } ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo)); AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type); List source = new List(); ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source); return source.First(); } } [PublicAPI] [HarmonyPatch] internal class VersionCheck { private static readonly HashSet versionChecks; private static readonly Dictionary notProcessedNames; public string Name; private string? displayName; private string? currentVersion; private string? minimumRequiredVersion; public bool ModRequired = true; private string? ReceivedCurrentVersion; private string? ReceivedMinimumRequiredVersion; private readonly List ValidatedClients = new List(); private ConfigSync? ConfigSync; public string DisplayName { get { return displayName ?? Name; } set { displayName = value; } } public string CurrentVersion { get { return currentVersion ?? "0.0.0"; } set { currentVersion = value; } } public string MinimumRequiredVersion { get { return minimumRequiredVersion ?? (ModRequired ? CurrentVersion : "0.0.0"); } set { minimumRequiredVersion = value; } } private static void PatchServerSync() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown Patches patchInfo = PatchProcessor.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null)); if (patchInfo != null && patchInfo.Postfixes.Count((Patch p) => p.PatchMethod.DeclaringType == typeof(ConfigSync.RegisterRPCPatch)) > 0) { return; } Harmony val = new Harmony("org.bepinex.helpers.ServerSync"); foreach (Type item in from t in typeof(ConfigSync).GetNestedTypes(BindingFlags.NonPublic).Concat(new Type[1] { typeof(VersionCheck) }) where t.IsClass select t) { val.PatchAll(item); } } static VersionCheck() { versionChecks = new HashSet(); notProcessedNames = new Dictionary(); typeof(ThreadingHelper).GetMethod("StartSyncInvoke").Invoke(ThreadingHelper.Instance, new object[1] { new Action(PatchServerSync) }); } public VersionCheck(string name) { Name = name; ModRequired = true; versionChecks.Add(this); } public VersionCheck(ConfigSync configSync) { ConfigSync = configSync; Name = ConfigSync.Name; versionChecks.Add(this); } public void Initialize() { ReceivedCurrentVersion = null; ReceivedMinimumRequiredVersion = null; if (ConfigSync != null) { Name = ConfigSync.Name; DisplayName = ConfigSync.DisplayName; CurrentVersion = ConfigSync.CurrentVersion; MinimumRequiredVersion = ConfigSync.MinimumRequiredVersion; ModRequired = ConfigSync.ModRequired; } } private bool IsVersionOk() { if (ReceivedMinimumRequiredVersion == null || ReceivedCurrentVersion == null) { return !ModRequired; } bool flag = new System.Version(CurrentVersion) >= new System.Version(ReceivedMinimumRequiredVersion); bool flag2 = new System.Version(ReceivedCurrentVersion) >= new System.Version(MinimumRequiredVersion); return flag && flag2; } private string ErrorClient() { if (ReceivedMinimumRequiredVersion == null) { return DisplayName + " is not installed on the server."; } return (new System.Version(CurrentVersion) >= new System.Version(ReceivedMinimumRequiredVersion)) ? (DisplayName + " may not be higher than version " + ReceivedCurrentVersion + ". You have version " + CurrentVersion + ".") : (DisplayName + " needs to be at least version " + ReceivedMinimumRequiredVersion + ". You have version " + CurrentVersion + "."); } private string ErrorServer(ZRpc rpc) { return "Disconnect: The client (" + rpc.GetSocket().GetHostName() + ") doesn't have the correct " + DisplayName + " version " + MinimumRequiredVersion; } private string Error(ZRpc? rpc = null) { return (rpc == null) ? ErrorClient() : ErrorServer(rpc); } private static VersionCheck[] GetFailedClient() { return versionChecks.Where((VersionCheck check) => !check.IsVersionOk()).ToArray(); } private static VersionCheck[] GetFailedServer(ZRpc rpc) { return versionChecks.Where((VersionCheck check) => check.ModRequired && !check.ValidatedClients.Contains(rpc)).ToArray(); } private static void Logout() { Game.instance.Logout(true, true); AccessTools.DeclaredField(typeof(ZNet), "m_connectionStatus").SetValue(null, (object)(ConnectionStatus)3); } private static void DisconnectClient(ZRpc rpc) { rpc.Invoke("Error", new object[1] { 3 }); } private static void CheckVersion(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, null); } private static void CheckVersion(ZRpc rpc, ZPackage pkg, Action? original) { string text = pkg.ReadString(); string text2 = pkg.ReadString(); string text3 = pkg.ReadString(); bool flag = false; foreach (VersionCheck versionCheck in versionChecks) { if (!(text != versionCheck.Name)) { Debug.Log((object)("Received " + versionCheck.DisplayName + " version " + text3 + " and minimum version " + text2 + " from the " + (ZNet.instance.IsServer() ? "client" : "server") + ".")); versionCheck.ReceivedMinimumRequiredVersion = text2; versionCheck.ReceivedCurrentVersion = text3; if (ZNet.instance.IsServer() && versionCheck.IsVersionOk()) { versionCheck.ValidatedClients.Add(rpc); } flag = true; } } if (flag) { return; } pkg.SetPos(0); if (original != null) { original(rpc, pkg); if (pkg.GetPos() == 0) { notProcessedNames.Add(text, text3); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] [HarmonyPrefix] private static bool RPC_PeerInfo(ZRpc rpc, ZNet __instance) { VersionCheck[] array = (__instance.IsServer() ? GetFailedServer(rpc) : GetFailedClient()); if (array.Length == 0) { return true; } VersionCheck[] array2 = array; foreach (VersionCheck versionCheck in array2) { Debug.LogWarning((object)versionCheck.Error(rpc)); } if (__instance.IsServer()) { DisconnectClient(rpc); } else { Logout(); } return false; } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPrefix] private static void RegisterAndCheckVersion(ZNetPeer peer, ZNet __instance) { //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown notProcessedNames.Clear(); IDictionary dictionary = (IDictionary)typeof(ZRpc).GetField("m_functions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(peer.m_rpc); if (dictionary.Contains(StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck"))) { object obj = dictionary[StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")]; Action action = (Action)obj.GetType().GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj); peer.m_rpc.Register("ServerSync VersionCheck", (Action)delegate(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, action); }); } else { peer.m_rpc.Register("ServerSync VersionCheck", (Action)CheckVersion); } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.Initialize(); if (versionCheck.ModRequired || __instance.IsServer()) { Debug.Log((object)("Sending " + versionCheck.DisplayName + " version " + versionCheck.CurrentVersion + " and minimum version " + versionCheck.MinimumRequiredVersion + " to the " + (__instance.IsServer() ? "client" : "server") + ".")); ZPackage val = new ZPackage(); val.Write(versionCheck.Name); val.Write(versionCheck.MinimumRequiredVersion); val.Write(versionCheck.CurrentVersion); peer.m_rpc.Invoke("ServerSync VersionCheck", new object[1] { val }); } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPrefix] private static void RemoveDisconnected(ZNetPeer peer, ZNet __instance) { if (!__instance.IsServer()) { return; } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.ValidatedClients.Remove(peer.m_rpc); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] [HarmonyPostfix] private static void ShowConnectionError(FejdStartup __instance) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) if (!__instance.m_connectionFailedPanel.activeSelf || (int)ZNet.GetConnectionStatus() != 3) { return; } bool flag = false; VersionCheck[] failedClient = GetFailedClient(); if (failedClient.Length != 0) { string text = string.Join("\n", failedClient.Select((VersionCheck check) => check.Error())); TMP_Text connectionFailedError = __instance.m_connectionFailedError; connectionFailedError.text = connectionFailedError.text + "\n" + text; flag = true; } foreach (KeyValuePair item in notProcessedNames.OrderBy, string>((KeyValuePair kv) => kv.Key)) { if (!__instance.m_connectionFailedError.text.Contains(item.Key)) { TMP_Text connectionFailedError2 = __instance.m_connectionFailedError; connectionFailedError2.text = connectionFailedError2.text + "\nServer expects you to have " + item.Key + " (Version: " + item.Value + ") installed."; flag = true; } } if (flag) { RectTransform component = ((Component)__instance.m_connectionFailedPanel.transform.Find("Image")).GetComponent(); Vector2 sizeDelta = component.sizeDelta; sizeDelta.x = 675f; component.sizeDelta = sizeDelta; __instance.m_connectionFailedError.ForceMeshUpdate(false, false); float num = __instance.m_connectionFailedError.renderedHeight + 105f; RectTransform component2 = ((Component)((Component)component).transform.Find("ButtonOk")).GetComponent(); component2.anchoredPosition = new Vector2(component2.anchoredPosition.x, component2.anchoredPosition.y - (num - component.sizeDelta.y) / 2f); sizeDelta = component.sizeDelta; sizeDelta.y = num; component.sizeDelta = sizeDelta; } } } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } } namespace YamlDotNet { internal sealed class CultureInfoAdapter : CultureInfo { private readonly IFormatProvider provider; public CultureInfoAdapter(CultureInfo baseCulture, IFormatProvider provider) : base(baseCulture.Name) { this.provider = provider; } public override object? GetFormat(Type formatType) { return provider.GetFormat(formatType); } } internal static class Polyfills { [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool Contains(this string source, char c) { return source.IndexOf(c) != -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool EndsWith(this string source, char c) { if (source.Length > 0) { return source[source.Length - 1] == c; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool StartsWith(this string source, char c) { if (source.Length > 0) { return source[0] == c; } return false; } } internal static class PropertyInfoExtensions { public static object? ReadValue(this PropertyInfo property, object target) { return property.GetValue(target, null); } } internal static class ReflectionExtensions { private static readonly Func IsInstance = (PropertyInfo property) => !(property.GetMethod ?? property.SetMethod).IsStatic; private static readonly Func IsInstancePublic = (PropertyInfo property) => IsInstance(property) && (property.GetMethod ?? property.SetMethod).IsPublic; public static Type? BaseType(this Type type) { return type.GetTypeInfo().BaseType; } public static bool IsValueType(this Type type) { return type.GetTypeInfo().IsValueType; } public static bool IsGenericType(this Type type) { return type.GetTypeInfo().IsGenericType; } public static bool IsGenericTypeDefinition(this Type type) { return type.GetTypeInfo().IsGenericTypeDefinition; } public static Type? GetImplementationOfOpenGenericInterface(this Type type, Type openGenericType) { if (!openGenericType.IsGenericType || !openGenericType.IsInterface) { throw new ArgumentException("The type must be a generic type definition and an interface", "openGenericType"); } if (IsGenericDefinitionOfType(type, openGenericType)) { return type; } return type.FindInterfaces((Type t, object context) => IsGenericDefinitionOfType(t, context), openGenericType).FirstOrDefault(); static bool IsGenericDefinitionOfType(Type t, object? context) { if (t.IsGenericType) { return t.GetGenericTypeDefinition() == (Type)context; } return false; } } public static bool IsInterface(this Type type) { return type.GetTypeInfo().IsInterface; } public static bool IsEnum(this Type type) { return type.GetTypeInfo().IsEnum; } public static bool IsRequired(this MemberInfo member) { return member.GetCustomAttributes(inherit: true).Any((object x) => x.GetType().FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute"); } public static bool HasDefaultConstructor(this Type type, bool allowPrivateConstructors) { BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public; if (allowPrivateConstructors) { bindingFlags |= BindingFlags.NonPublic; } if (!type.IsValueType) { return type.GetConstructor(bindingFlags, null, Type.EmptyTypes, null) != null; } return true; } public static bool IsAssignableFrom(this Type type, Type source) { return type.IsAssignableFrom(source.GetTypeInfo()); } public static bool IsAssignableFrom(this Type type, TypeInfo source) { return type.GetTypeInfo().IsAssignableFrom(source); } public static TypeCode GetTypeCode(this Type type) { if (type.IsEnum()) { type = Enum.GetUnderlyingType(type); } if (type == typeof(bool)) { return TypeCode.Boolean; } if (type == typeof(char)) { return TypeCode.Char; } if (type == typeof(sbyte)) { return TypeCode.SByte; } if (type == typeof(byte)) { return TypeCode.Byte; } if (type == typeof(short)) { return TypeCode.Int16; } if (type == typeof(ushort)) { return TypeCode.UInt16; } if (type == typeof(int)) { return TypeCode.Int32; } if (type == typeof(uint)) { return TypeCode.UInt32; } if (type == typeof(long)) { return TypeCode.Int64; } if (type == typeof(ulong)) { return TypeCode.UInt64; } if (type == typeof(float)) { return TypeCode.Single; } if (type == typeof(double)) { return TypeCode.Double; } if (type == typeof(decimal)) { return TypeCode.Decimal; } if (type == typeof(DateTime)) { return TypeCode.DateTime; } if (type == typeof(string)) { return TypeCode.String; } return TypeCode.Object; } public static bool IsDbNull(this object value) { return value?.GetType()?.FullName == "System.DBNull"; } public static Type[] GetGenericArguments(this Type type) { return type.GetTypeInfo().GenericTypeArguments; } public static PropertyInfo? GetPublicProperty(this Type type, string name) { return type.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public).FirstOrDefault((PropertyInfo p) => p.Name == name); } public static FieldInfo? GetPublicStaticField(this Type type, string name) { return type.GetRuntimeField(name); } public static IEnumerable GetProperties(this Type type, bool includeNonPublic) { Func predicate = (includeNonPublic ? IsInstance : IsInstancePublic); if (!type.IsInterface()) { return type.GetRuntimeProperties().Where(predicate); } return new Type[1] { type }.Concat(type.GetInterfaces()).SelectMany((Type i) => i.GetRuntimeProperties().Where(predicate)); } public static IEnumerable GetPublicProperties(this Type type) { return type.GetProperties(includeNonPublic: false); } public static IEnumerable GetPublicFields(this Type type) { return from f in type.GetRuntimeFields() where !f.IsStatic && f.IsPublic select f; } public static IEnumerable GetPublicStaticMethods(this Type type) { return from m in type.GetRuntimeMethods() where m.IsPublic && m.IsStatic select m; } public static MethodInfo GetPrivateStaticMethod(this Type type, string name) { return type.GetRuntimeMethods().FirstOrDefault((MethodInfo m) => !m.IsPublic && m.IsStatic && m.Name.Equals(name)) ?? throw new MissingMethodException("Expected to find a method named '" + name + "' in '" + type.FullName + "'."); } public static MethodInfo? GetPublicStaticMethod(this Type type, string name, params Type[] parameterTypes) { return type.GetRuntimeMethods().FirstOrDefault(delegate(MethodInfo m) { if (m.IsPublic && m.IsStatic && m.Name.Equals(name)) { ParameterInfo[] parameters = m.GetParameters(); if (parameters.Length == parameterTypes.Length) { return parameters.Zip(parameterTypes, (ParameterInfo pi, Type pt) => pi.ParameterType == pt).All((bool r) => r); } return false; } return false; }); } public static MethodInfo? GetPublicInstanceMethod(this Type type, string name) { return type.GetRuntimeMethods().FirstOrDefault((MethodInfo m) => m.IsPublic && !m.IsStatic && m.Name.Equals(name)); } public static MethodInfo? GetGetMethod(this PropertyInfo property, bool nonPublic) { MethodInfo methodInfo = property.GetMethod; if (!nonPublic && !methodInfo.IsPublic) { methodInfo = null; } return methodInfo; } public static MethodInfo? GetSetMethod(this PropertyInfo property) { return property.SetMethod; } public static IEnumerable GetInterfaces(this Type type) { return type.GetTypeInfo().ImplementedInterfaces; } public static bool IsInstanceOf(this Type type, object o) { if (!(o.GetType() == type)) { return o.GetType().GetTypeInfo().IsSubclassOf(type); } return true; } public static Attribute[] GetAllCustomAttributes(this PropertyInfo member) { return Attribute.GetCustomAttributes(member, typeof(TAttribute), inherit: true); } public static bool AcceptsNull(this MemberInfo member) { object[] customAttributes = member.DeclaringType.GetCustomAttributes(inherit: true); object obj = customAttributes.FirstOrDefault((object x) => x.GetType().FullName == "System.Runtime.CompilerServices.NullableContextAttribute"); int num = 0; if (obj != null) { Type type = obj.GetType(); PropertyInfo property = type.GetProperty("Flag"); num = (byte)property.GetValue(obj); } object[] customAttributes2 = member.GetCustomAttributes(inherit: true); object obj2 = customAttributes2.FirstOrDefault((object x) => x.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute"); PropertyInfo propertyInfo = (obj2?.GetType())?.GetProperty("NullableFlags"); byte[] source = (byte[])propertyInfo.GetValue(obj2); return source.Any((byte x) => x == 2) || num == 2; } } internal static class StandardRegexOptions { public const RegexOptions Compiled = RegexOptions.Compiled; } } namespace YamlDotNet.Serialization { internal abstract class BuilderSkeleton where TBuilder : BuilderSkeleton { internal INamingConvention namingConvention = NullNamingConvention.Instance; internal INamingConvention enumNamingConvention = NullNamingConvention.Instance; internal ITypeResolver typeResolver; internal readonly YamlAttributeOverrides overrides; internal readonly LazyComponentRegistrationList typeConverterFactories; internal readonly LazyComponentRegistrationList typeInspectorFactories; internal bool ignoreFields; internal bool includeNonPublicProperties; internal Settings settings; internal YamlFormatter yamlFormatter = YamlFormatter.Default; protected abstract TBuilder Self { get; } internal BuilderSkeleton(ITypeResolver typeResolver) { overrides = new YamlAttributeOverrides(); typeConverterFactories = new LazyComponentRegistrationList { { typeof(YamlDotNet.Serialization.Converters.GuidConverter), (Nothing _) => new YamlDotNet.Serialization.Converters.GuidConverter(jsonCompatible: false) }, { typeof(SystemTypeConverter), (Nothing _) => new SystemTypeConverter() } }; typeInspectorFactories = new LazyComponentRegistrationList(); this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); settings = new Settings(); } public TBuilder IgnoreFields() { ignoreFields = true; return Self; } public TBuilder IncludeNonPublicProperties() { includeNonPublicProperties = true; return Self; } public TBuilder EnablePrivateConstructors() { settings.AllowPrivateConstructors = true; return Self; } public TBuilder WithNamingConvention(INamingConvention namingConvention) { this.namingConvention = namingConvention ?? throw new ArgumentNullException("namingConvention"); return Self; } public TBuilder WithEnumNamingConvention(INamingConvention enumNamingConvention) { this.enumNamingConvention = enumNamingConvention; return Self; } public TBuilder WithTypeResolver(ITypeResolver typeResolver) { this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); return Self; } public abstract TBuilder WithTagMapping(TagName tag, Type type); public TBuilder WithAttributeOverride(Expression> propertyAccessor, Attribute attribute) { overrides.Add(propertyAccessor, attribute); return Self; } public TBuilder WithAttributeOverride(Type type, string member, Attribute attribute) { overrides.Add(type, member, attribute); return Self; } public TBuilder WithTypeConverter(IYamlTypeConverter typeConverter) { return WithTypeConverter(typeConverter, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public TBuilder WithTypeConverter(IYamlTypeConverter typeConverter, Action> where) { if (typeConverter == null) { throw new ArgumentNullException("typeConverter"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeConverterFactories.CreateRegistrationLocationSelector(typeConverter.GetType(), (Nothing _) => typeConverter)); return Self; } public TBuilder WithTypeConverter(WrapperFactory typeConverterFactory, Action> where) where TYamlTypeConverter : IYamlTypeConverter { if (typeConverterFactory == null) { throw new ArgumentNullException("typeConverterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeConverterFactories.CreateTrackingRegistrationLocationSelector(typeof(TYamlTypeConverter), (IYamlTypeConverter wrapped, Nothing _) => typeConverterFactory(wrapped))); return Self; } public TBuilder WithoutTypeConverter() where TYamlTypeConverter : IYamlTypeConverter { return WithoutTypeConverter(typeof(TYamlTypeConverter)); } public TBuilder WithoutTypeConverter(Type converterType) { if (converterType == null) { throw new ArgumentNullException("converterType"); } typeConverterFactories.Remove(converterType); return Self; } public TBuilder WithTypeInspector(Func typeInspectorFactory) where TTypeInspector : ITypeInspector { return WithTypeInspector(typeInspectorFactory, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public TBuilder WithTypeInspector(Func typeInspectorFactory, Action> where) where TTypeInspector : ITypeInspector { if (typeInspectorFactory == null) { throw new ArgumentNullException("typeInspectorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeInspectorFactories.CreateRegistrationLocationSelector(typeof(TTypeInspector), (ITypeInspector inner) => typeInspectorFactory(inner))); return Self; } public TBuilder WithTypeInspector(WrapperFactory typeInspectorFactory, Action> where) where TTypeInspector : ITypeInspector { if (typeInspectorFactory == null) { throw new ArgumentNullException("typeInspectorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeInspectorFactories.CreateTrackingRegistrationLocationSelector(typeof(TTypeInspector), (ITypeInspector wrapped, ITypeInspector inner) => typeInspectorFactory(wrapped, inner))); return Self; } public TBuilder WithoutTypeInspector() where TTypeInspector : ITypeInspector { return WithoutTypeInspector(typeof(TTypeInspector)); } public TBuilder WithoutTypeInspector(Type inspectorType) { if (inspectorType == null) { throw new ArgumentNullException("inspectorType"); } typeInspectorFactories.Remove(inspectorType); return Self; } public TBuilder WithYamlFormatter(YamlFormatter formatter) { yamlFormatter = formatter ?? throw new ArgumentNullException("formatter"); return Self; } protected IEnumerable BuildTypeConverters() { return typeConverterFactories.BuildComponentList(); } } internal delegate TComponent WrapperFactory(TComponentBase wrapped) where TComponent : TComponentBase; internal delegate TComponent WrapperFactory(TComponentBase wrapped, TArgument argument) where TComponent : TComponentBase; [Flags] internal enum DefaultValuesHandling { Preserve = 0, OmitNull = 1, OmitDefaults = 2, OmitEmptyCollections = 4 } internal sealed class Deserializer : IDeserializer { private readonly IValueDeserializer valueDeserializer; public Deserializer() : this(new DeserializerBuilder().BuildValueDeserializer()) { } private Deserializer(IValueDeserializer valueDeserializer) { this.valueDeserializer = valueDeserializer ?? throw new ArgumentNullException("valueDeserializer"); } public static Deserializer FromValueDeserializer(IValueDeserializer valueDeserializer) { return new Deserializer(valueDeserializer); } public T Deserialize(string input) { using StringReader input2 = new StringReader(input); return Deserialize(input2); } public T Deserialize(TextReader input) { return Deserialize(new Parser(input)); } public T Deserialize(IParser parser) { return (T)Deserialize(parser, typeof(T)); } public object? Deserialize(string input) { return Deserialize(input); } public object? Deserialize(TextReader input) { return Deserialize(input); } public object? Deserialize(IParser parser) { return Deserialize(parser); } public object? Deserialize(string input, Type type) { using StringReader input2 = new StringReader(input); return Deserialize(input2, type); } public object? Deserialize(TextReader input, Type type) { return Deserialize(new Parser(input), type); } public object? Deserialize(IParser parser, Type type) { if (parser == null) { throw new ArgumentNullException("parser"); } if (type == null) { throw new ArgumentNullException("type"); } YamlDotNet.Core.Events.StreamStart @event; bool flag = parser.TryConsume(out @event); YamlDotNet.Core.Events.DocumentStart event2; bool flag2 = parser.TryConsume(out event2); object result = null; if (!parser.Accept(out var _) && !parser.Accept(out var _)) { using SerializerState serializerState = new SerializerState(); result = valueDeserializer.DeserializeValue(parser, type, serializerState, valueDeserializer); serializerState.OnDeserialization(); } if (flag2) { parser.Consume(); } if (flag) { parser.Consume(); } return result; } } internal sealed class DeserializerBuilder : BuilderSkeleton { private Lazy objectFactory; private readonly LazyComponentRegistrationList nodeDeserializerFactories; private readonly LazyComponentRegistrationList nodeTypeResolverFactories; private readonly Dictionary tagMappings; private readonly Dictionary typeMappings; private readonly ITypeConverter typeConverter; private bool ignoreUnmatched; private bool duplicateKeyChecking; private bool attemptUnknownTypeDeserialization; private bool enforceNullability; private bool caseInsensitivePropertyMatching; private bool enforceRequiredProperties; protected override DeserializerBuilder Self => this; public DeserializerBuilder() : base((ITypeResolver)new StaticTypeResolver()) { typeMappings = new Dictionary(); objectFactory = new Lazy(() => new DefaultObjectFactory(typeMappings, settings), isThreadSafe: true); tagMappings = new Dictionary { { FailsafeSchema.Tags.Map, typeof(Dictionary) }, { FailsafeSchema.Tags.Str, typeof(string) }, { JsonSchema.Tags.Bool, typeof(bool) }, { JsonSchema.Tags.Float, typeof(double) }, { JsonSchema.Tags.Int, typeof(int) }, { DefaultSchema.Tags.Timestamp, typeof(DateTime) } }; typeInspectorFactories.Add(typeof(CachedTypeInspector), (ITypeInspector inner) => new CachedTypeInspector(inner)); typeInspectorFactories.Add(typeof(NamingConventionTypeInspector), (ITypeInspector inner) => (!(namingConvention is NullNamingConvention)) ? new NamingConventionTypeInspector(inner, namingConvention) : inner); typeInspectorFactories.Add(typeof(YamlAttributesTypeInspector), (ITypeInspector inner) => new YamlAttributesTypeInspector(inner)); typeInspectorFactories.Add(typeof(YamlAttributeOverridesInspector), (ITypeInspector inner) => (overrides == null) ? inner : new YamlAttributeOverridesInspector(inner, overrides.Clone())); typeInspectorFactories.Add(typeof(ReadableAndWritablePropertiesTypeInspector), (ITypeInspector inner) => new ReadableAndWritablePropertiesTypeInspector(inner)); nodeDeserializerFactories = new LazyComponentRegistrationList { { typeof(YamlConvertibleNodeDeserializer), (Nothing _) => new YamlConvertibleNodeDeserializer(objectFactory.Value) }, { typeof(YamlSerializableNodeDeserializer), (Nothing _) => new YamlSerializableNodeDeserializer(objectFactory.Value) }, { typeof(TypeConverterNodeDeserializer), (Nothing _) => new TypeConverterNodeDeserializer(BuildTypeConverters()) }, { typeof(NullNodeDeserializer), (Nothing _) => new NullNodeDeserializer() }, { typeof(ScalarNodeDeserializer), (Nothing _) => new ScalarNodeDeserializer(attemptUnknownTypeDeserialization, typeConverter, BuildTypeInspector(), yamlFormatter, enumNamingConvention) }, { typeof(ArrayNodeDeserializer), (Nothing _) => new ArrayNodeDeserializer(enumNamingConvention, BuildTypeInspector()) }, { typeof(DictionaryNodeDeserializer), (Nothing _) => new DictionaryNodeDeserializer(objectFactory.Value, duplicateKeyChecking) }, { typeof(CollectionNodeDeserializer), (Nothing _) => new CollectionNodeDeserializer(objectFactory.Value, enumNamingConvention, BuildTypeInspector()) }, { typeof(EnumerableNodeDeserializer), (Nothing _) => new EnumerableNodeDeserializer() }, { typeof(ObjectNodeDeserializer), (Nothing _) => new ObjectNodeDeserializer(objectFactory.Value, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking, typeConverter, enumNamingConvention, enforceNullability, caseInsensitivePropertyMatching, enforceRequiredProperties, BuildTypeConverters()) }, { typeof(FsharpListNodeDeserializer), (Nothing _) => new FsharpListNodeDeserializer(BuildTypeInspector(), enumNamingConvention) } }; nodeTypeResolverFactories = new LazyComponentRegistrationList { { typeof(MappingNodeTypeResolver), (Nothing _) => new MappingNodeTypeResolver(typeMappings) }, { typeof(YamlConvertibleTypeResolver), (Nothing _) => new YamlConvertibleTypeResolver() }, { typeof(YamlSerializableTypeResolver), (Nothing _) => new YamlSerializableTypeResolver() }, { typeof(TagNodeTypeResolver), (Nothing _) => new TagNodeTypeResolver(tagMappings) }, { typeof(PreventUnknownTagsNodeTypeResolver), (Nothing _) => new PreventUnknownTagsNodeTypeResolver() }, { typeof(DefaultContainersNodeTypeResolver), (Nothing _) => new DefaultContainersNodeTypeResolver() } }; typeConverter = new ReflectionTypeConverter(); } public ITypeInspector BuildTypeInspector() { ITypeInspector typeInspector = new WritablePropertiesTypeInspector(typeResolver, includeNonPublicProperties); if (!ignoreFields) { typeInspector = new CompositeTypeInspector(new ReadableFieldsTypeInspector(typeResolver), typeInspector); } return typeInspectorFactories.BuildComponentChain(typeInspector); } public DeserializerBuilder WithAttemptingUnquotedStringTypeDeserialization() { attemptUnknownTypeDeserialization = true; return this; } public DeserializerBuilder WithObjectFactory(IObjectFactory objectFactory) { if (objectFactory == null) { throw new ArgumentNullException("objectFactory"); } this.objectFactory = new Lazy(() => objectFactory, isThreadSafe: true); return this; } public DeserializerBuilder WithObjectFactory(Func objectFactory) { if (objectFactory == null) { throw new ArgumentNullException("objectFactory"); } return WithObjectFactory(new LambdaObjectFactory(objectFactory)); } public DeserializerBuilder WithNodeDeserializer(INodeDeserializer nodeDeserializer) { return WithNodeDeserializer(nodeDeserializer, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public DeserializerBuilder WithNodeDeserializer(INodeDeserializer nodeDeserializer, Action> where) { if (nodeDeserializer == null) { throw new ArgumentNullException("nodeDeserializer"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeDeserializerFactories.CreateRegistrationLocationSelector(nodeDeserializer.GetType(), (Nothing _) => nodeDeserializer)); return this; } public DeserializerBuilder WithNodeDeserializer(WrapperFactory nodeDeserializerFactory, Action> where) where TNodeDeserializer : INodeDeserializer { if (nodeDeserializerFactory == null) { throw new ArgumentNullException("nodeDeserializerFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeDeserializerFactories.CreateTrackingRegistrationLocationSelector(typeof(TNodeDeserializer), (INodeDeserializer wrapped, Nothing _) => nodeDeserializerFactory(wrapped))); return this; } public DeserializerBuilder WithoutNodeDeserializer() where TNodeDeserializer : INodeDeserializer { return WithoutNodeDeserializer(typeof(TNodeDeserializer)); } public DeserializerBuilder WithoutNodeDeserializer(Type nodeDeserializerType) { if (nodeDeserializerType == null) { throw new ArgumentNullException("nodeDeserializerType"); } nodeDeserializerFactories.Remove(nodeDeserializerType); return this; } public DeserializerBuilder WithTypeDiscriminatingNodeDeserializer(Action configureTypeDiscriminatingNodeDeserializerOptions, int maxDepth = -1, int maxLength = -1) { TypeDiscriminatingNodeDeserializerOptions typeDiscriminatingNodeDeserializerOptions = new TypeDiscriminatingNodeDeserializerOptions(); configureTypeDiscriminatingNodeDeserializerOptions(typeDiscriminatingNodeDeserializerOptions); TypeDiscriminatingNodeDeserializer nodeDeserializer = new TypeDiscriminatingNodeDeserializer(nodeDeserializerFactories.BuildComponentList(), typeDiscriminatingNodeDeserializerOptions.discriminators, maxDepth, maxLength); return WithNodeDeserializer(nodeDeserializer, delegate(IRegistrationLocationSelectionSyntax s) { s.Before(); }); } public DeserializerBuilder WithNodeTypeResolver(INodeTypeResolver nodeTypeResolver) { return WithNodeTypeResolver(nodeTypeResolver, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public DeserializerBuilder WithNodeTypeResolver(INodeTypeResolver nodeTypeResolver, Action> where) { if (nodeTypeResolver == null) { throw new ArgumentNullException("nodeTypeResolver"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeTypeResolverFactories.CreateRegistrationLocationSelector(nodeTypeResolver.GetType(), (Nothing _) => nodeTypeResolver)); return this; } public DeserializerBuilder WithNodeTypeResolver(WrapperFactory nodeTypeResolverFactory, Action> where) where TNodeTypeResolver : INodeTypeResolver { if (nodeTypeResolverFactory == null) { throw new ArgumentNullException("nodeTypeResolverFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeTypeResolverFactories.CreateTrackingRegistrationLocationSelector(typeof(TNodeTypeResolver), (INodeTypeResolver wrapped, Nothing _) => nodeTypeResolverFactory(wrapped))); return this; } public DeserializerBuilder WithCaseInsensitivePropertyMatching() { caseInsensitivePropertyMatching = true; return this; } public DeserializerBuilder WithEnforceNullability() { enforceNullability = true; return this; } public DeserializerBuilder WithEnforceRequiredMembers() { enforceRequiredProperties = true; return this; } public DeserializerBuilder WithoutNodeTypeResolver() where TNodeTypeResolver : INodeTypeResolver { return WithoutNodeTypeResolver(typeof(TNodeTypeResolver)); } public DeserializerBuilder WithoutNodeTypeResolver(Type nodeTypeResolverType) { if (nodeTypeResolverType == null) { throw new ArgumentNullException("nodeTypeResolverType"); } nodeTypeResolverFactories.Remove(nodeTypeResolverType); return this; } public override DeserializerBuilder WithTagMapping(TagName tag, Type type) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (type == null) { throw new ArgumentNullException("type"); } if (tagMappings.TryGetValue(tag, out Type value)) { throw new ArgumentException($"Type already has a registered type '{value.FullName}' for tag '{tag}'", "tag"); } tagMappings.Add(tag, type); return this; } public DeserializerBuilder WithTypeMapping() where TConcrete : TInterface { Type typeFromHandle = typeof(TInterface); Type typeFromHandle2 = typeof(TConcrete); if (!typeFromHandle.IsAssignableFrom(typeFromHandle2)) { throw new InvalidOperationException("The type '" + typeFromHandle2.Name + "' does not implement interface '" + typeFromHandle.Name + "'."); } if (!DictionaryExtensions.TryAdd(typeMappings, typeFromHandle, typeFromHandle2)) { typeMappings[typeFromHandle] = typeFromHandle2; } return this; } public DeserializerBuilder WithoutTagMapping(TagName tag) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (!tagMappings.Remove(tag)) { throw new KeyNotFoundException($"Tag '{tag}' is not registered"); } return this; } public DeserializerBuilder IgnoreUnmatchedProperties() { ignoreUnmatched = true; return this; } public DeserializerBuilder WithDuplicateKeyChecking() { duplicateKeyChecking = true; return this; } public IDeserializer Build() { if (FsharpHelper.Instance == null) { FsharpHelper.Instance = new DefaultFsharpHelper(); } return Deserializer.FromValueDeserializer(BuildValueDeserializer()); } public IValueDeserializer BuildValueDeserializer() { return new AliasValueDeserializer(new NodeValueDeserializer(nodeDeserializerFactories.BuildComponentList(), nodeTypeResolverFactories.BuildComponentList(), typeConverter, enumNamingConvention, BuildTypeInspector())); } } internal sealed class EmissionPhaseObjectGraphVisitorArgs { private readonly IEnumerable> preProcessingPhaseVisitors; public IObjectGraphVisitor InnerVisitor { get; private set; } public IEventEmitter EventEmitter { get; private set; } public ObjectSerializer NestedObjectSerializer { get; private set; } public IEnumerable TypeConverters { get; private set; } public EmissionPhaseObjectGraphVisitorArgs(IObjectGraphVisitor innerVisitor, IEventEmitter eventEmitter, IEnumerable> preProcessingPhaseVisitors, IEnumerable typeConverters, ObjectSerializer nestedObjectSerializer) { InnerVisitor = innerVisitor ?? throw new ArgumentNullException("innerVisitor"); EventEmitter = eventEmitter ?? throw new ArgumentNullException("eventEmitter"); this.preProcessingPhaseVisitors = preProcessingPhaseVisitors ?? throw new ArgumentNullException("preProcessingPhaseVisitors"); TypeConverters = typeConverters ?? throw new ArgumentNullException("typeConverters"); NestedObjectSerializer = nestedObjectSerializer ?? throw new ArgumentNullException("nestedObjectSerializer"); } public T GetPreProcessingPhaseObjectGraphVisitor() where T : IObjectGraphVisitor { return preProcessingPhaseVisitors.OfType().Single(); } } internal abstract class EventInfo { public IObjectDescriptor Source { get; } protected EventInfo(IObjectDescriptor source) { Source = source ?? throw new ArgumentNullException("source"); } } internal class AliasEventInfo : EventInfo { public AnchorName Alias { get; } public bool NeedsExpansion { get; set; } public AliasEventInfo(IObjectDescriptor source, AnchorName alias) : base(source) { if (alias.IsEmpty) { throw new ArgumentNullException("alias"); } Alias = alias; } } internal class ObjectEventInfo : EventInfo { public AnchorName Anchor { get; set; } public TagName Tag { get; set; } protected ObjectEventInfo(IObjectDescriptor source) : base(source) { } } internal sealed class ScalarEventInfo : ObjectEventInfo { public string RenderedValue { get; set; } public ScalarStyle Style { get; set; } public bool IsPlainImplicit { get; set; } public bool IsQuotedImplicit { get; set; } public ScalarEventInfo(IObjectDescriptor source) : base(source) { Style = source.ScalarStyle; RenderedValue = string.Empty; } } internal sealed class MappingStartEventInfo : ObjectEventInfo { public bool IsImplicit { get; set; } public MappingStyle Style { get; set; } public MappingStartEventInfo(IObjectDescriptor source) : base(source) { } } internal sealed class MappingEndEventInfo : EventInfo { public MappingEndEventInfo(IObjectDescriptor source) : base(source) { } } internal sealed class SequenceStartEventInfo : ObjectEventInfo { public bool IsImplicit { get; set; } public SequenceStyle Style { get; set; } public SequenceStartEventInfo(IObjectDescriptor source) : base(source) { } } internal sealed class SequenceEndEventInfo : EventInfo { public SequenceEndEventInfo(IObjectDescriptor source) : base(source) { } } internal interface IAliasProvider { AnchorName GetAlias(object target); } internal interface IDeserializer { T Deserialize(string input); T Deserialize(TextReader input); T Deserialize(IParser parser); object? Deserialize(string input); object? Deserialize(TextReader input); object? Deserialize(IParser parser); object? Deserialize(string input, Type type); object? Deserialize(TextReader input, Type type); object? Deserialize(IParser parser, Type type); } internal interface IEventEmitter { void Emit(AliasEventInfo eventInfo, IEmitter emitter); void Emit(ScalarEventInfo eventInfo, IEmitter emitter); void Emit(MappingStartEventInfo eventInfo, IEmitter emitter); void Emit(MappingEndEventInfo eventInfo, IEmitter emitter); void Emit(SequenceStartEventInfo eventInfo, IEmitter emitter); void Emit(SequenceEndEventInfo eventInfo, IEmitter emitter); } internal interface INamingConvention { string Apply(string value); string Reverse(string value); } internal interface INodeDeserializer { bool Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer); } internal interface INodeTypeResolver { bool Resolve(NodeEvent? nodeEvent, ref Type currentType); } internal interface IObjectAccessor { void Set(string name, object target, object value); object? Read(string name, object target); } internal interface IObjectDescriptor { object? Value { get; } Type Type { get; } Type StaticType { get; } ScalarStyle ScalarStyle { get; } } internal static class ObjectDescriptorExtensions { public static object NonNullValue(this IObjectDescriptor objectDescriptor) { return objectDescriptor.Value ?? throw new InvalidOperationException("Attempted to use a IObjectDescriptor of type '" + objectDescriptor.Type.FullName + "' whose Value is null at a point whete it is invalid to do so. This may indicate a bug in YamlDotNet."); } } internal interface IObjectFactory { object Create(Type type); object? CreatePrimitive(Type type); bool GetDictionary(IObjectDescriptor descriptor, out IDictionary? dictionary, out Type[]? genericArguments); Type GetValueType(Type type); void ExecuteOnDeserializing(object value); void ExecuteOnDeserialized(object value); void ExecuteOnSerializing(object value); void ExecuteOnSerialized(object value); } internal interface IObjectGraphTraversalStrategy { void Traverse(IObjectDescriptor graph, IObjectGraphVisitor visitor, TContext context, ObjectSerializer serializer); } internal interface IObjectGraphVisitor { bool Enter(IPropertyDescriptor? propertyDescriptor, IObjectDescriptor value, TContext context, ObjectSerializer serializer); bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value, TContext context, ObjectSerializer serializer); bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, TContext context, ObjectSerializer serializer); void VisitScalar(IObjectDescriptor scalar, TContext context, ObjectSerializer serializer); void VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, TContext context, ObjectSerializer serializer); void VisitMappingEnd(IObjectDescriptor mapping, TContext context, ObjectSerializer serializer); void VisitSequenceStart(IObjectDescriptor sequence, Type elementType, TContext context, ObjectSerializer serializer); void VisitSequenceEnd(IObjectDescriptor sequence, TContext context, ObjectSerializer serializer); } internal interface IPropertyDescriptor { string Name { get; } bool AllowNulls { get; } bool CanWrite { get; } Type Type { get; } Type? TypeOverride { get; set; } int Order { get; set; } ScalarStyle ScalarStyle { get; set; } bool Required { get; } Type? ConverterType { get; } T? GetCustomAttribute() where T : Attribute; IObjectDescriptor Read(object target); void Write(object target, object? value); } internal interface IRegistrationLocationSelectionSyntax { void InsteadOf() where TRegistrationType : TBaseRegistrationType; void Before() where TRegistrationType : TBaseRegistrationType; void After() where TRegistrationType : TBaseRegistrationType; void OnTop(); void OnBottom(); } internal interface ITrackingRegistrationLocationSelectionSyntax { void InsteadOf() where TRegistrationType : TBaseRegistrationType; } internal interface ISerializer { string Serialize(object? graph); string Serialize(object? graph, Type type); void Serialize(TextWriter writer, object? graph); void Serialize(TextWriter writer, object? graph, Type type); void Serialize(IEmitter emitter, object? graph); void Serialize(IEmitter emitter, object? graph, Type type); } internal interface ITypeInspector { IEnumerable GetProperties(Type type, object? container); IPropertyDescriptor GetProperty(Type type, object? container, string name, [MaybeNullWhen(true)] bool ignoreUnmatched, bool caseInsensitivePropertyMatching); string GetEnumName(Type enumType, string name); string GetEnumValue(object enumValue); } internal interface ITypeResolver { Type Resolve(Type staticType, object? actualValue); } internal interface IValueDeserializer { object? DeserializeValue(IParser parser, Type expectedType, SerializerState state, IValueDeserializer nestedObjectDeserializer); } internal interface IValuePromise { event Action ValueAvailable; } internal interface IValueSerializer { void SerializeValue(IEmitter emitter, object? value, Type? type); } internal interface IYamlConvertible { void Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer); void Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer); } internal delegate object? ObjectDeserializer(Type type); internal delegate void ObjectSerializer(object? value, Type? type = null); [Obsolete("Please use IYamlConvertible instead")] internal interface IYamlSerializable { void ReadYaml(IParser parser); void WriteYaml(IEmitter emitter); } internal interface IYamlTypeConverter { bool Accepts(Type type); object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer); void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer); } internal sealed class LazyComponentRegistrationList : IEnumerable>, IEnumerable { public sealed class LazyComponentRegistration { public readonly Type ComponentType; public readonly Func Factory; public LazyComponentRegistration(Type componentType, Func factory) { ComponentType = componentType; Factory = factory; } } public sealed class TrackingLazyComponentRegistration { public readonly Type ComponentType; public readonly Func Factory; public TrackingLazyComponentRegistration(Type componentType, Func factory) { ComponentType = componentType; Factory = factory; } } private class RegistrationLocationSelector : IRegistrationLocationSelectionSyntax { private readonly LazyComponentRegistrationList registrations; private readonly LazyComponentRegistration newRegistration; public RegistrationLocationSelector(LazyComponentRegistrationList registrations, LazyComponentRegistration newRegistration) { this.registrations = registrations; this.newRegistration = newRegistration; } void IRegistrationLocationSelectionSyntax.InsteadOf() { if (newRegistration.ComponentType != typeof(TRegistrationType)) { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); } int index = registrations.EnsureRegistrationExists(); registrations.entries[index] = newRegistration; } void IRegistrationLocationSelectionSyntax.After() { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); int num = registrations.EnsureRegistrationExists(); registrations.entries.Insert(num + 1, newRegistration); } void IRegistrationLocationSelectionSyntax.Before() { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); int index = registrations.EnsureRegistrationExists(); registrations.entries.Insert(index, newRegistration); } void IRegistrationLocationSelectionSyntax.OnBottom() { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); registrations.entries.Add(newRegistration); } void IRegistrationLocationSelectionSyntax.OnTop() { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); registrations.entries.Insert(0, newRegistration); } } private class TrackingRegistrationLocationSelector : ITrackingRegistrationLocationSelectionSyntax { private readonly LazyComponentRegistrationList registrations; private readonly TrackingLazyComponentRegistration newRegistration; public TrackingRegistrationLocationSelector(LazyComponentRegistrationList registrations, TrackingLazyComponentRegistration newRegistration) { this.registrations = registrations; this.newRegistration = newRegistration; } void ITrackingRegistrationLocationSelectionSyntax.InsteadOf() { if (newRegistration.ComponentType != typeof(TRegistrationType)) { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); } int index = registrations.EnsureRegistrationExists(); Func innerComponentFactory = registrations.entries[index].Factory; registrations.entries[index] = new LazyComponentRegistration(newRegistration.ComponentType, (TArgument arg) => newRegistration.Factory(innerComponentFactory(arg), arg)); } } private readonly List entries = new List(); public int Count => entries.Count; public IEnumerable> InReverseOrder { get { int i = entries.Count - 1; while (i >= 0) { yield return entries[i].Factory; int num = i - 1; i = num; } } } public LazyComponentRegistrationList Clone() { LazyComponentRegistrationList lazyComponentRegistrationList = new LazyComponentRegistrationList(); foreach (LazyComponentRegistration entry in entries) { lazyComponentRegistrationList.entries.Add(entry); } return lazyComponentRegistrationList; } public void Clear() { entries.Clear(); } public void Add(Type componentType, Func factory) { entries.Add(new LazyComponentRegistration(componentType, factory)); } public void Remove(Type componentType) { for (int i = 0; i < entries.Count; i++) { if (entries[i].ComponentType == componentType) { entries.RemoveAt(i); return; } } throw new KeyNotFoundException("A component registration of type '" + componentType.FullName + "' was not found."); } public IRegistrationLocationSelectionSyntax CreateRegistrationLocationSelector(Type componentType, Func factory) { return new RegistrationLocationSelector(this, new LazyComponentRegistration(componentType, factory)); } public ITrackingRegistrationLocationSelectionSyntax CreateTrackingRegistrationLocationSelector(Type componentType, Func factory) { return new TrackingRegistrationLocationSelector(this, new TrackingLazyComponentRegistration(componentType, factory)); } public IEnumerator> GetEnumerator() { return entries.Select((LazyComponentRegistration e) => e.Factory).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private int IndexOfRegistration(Type registrationType) { for (int i = 0; i < entries.Count; i++) { if (registrationType == entries[i].ComponentType) { return i; } } return -1; } private void EnsureNoDuplicateRegistrationType(Type componentType) { if (IndexOfRegistration(componentType) != -1) { throw new InvalidOperationException("A component of type '" + componentType.FullName + "' has already been registered."); } } private int EnsureRegistrationExists() { int num = IndexOfRegistration(typeof(TRegistrationType)); if (num == -1) { throw new InvalidOperationException("A component of type '" + typeof(TRegistrationType).FullName + "' has not been registered."); } return num; } } internal static class LazyComponentRegistrationListExtensions { public static TComponent BuildComponentChain(this LazyComponentRegistrationList registrations, TComponent innerComponent) { return registrations.InReverseOrder.Aggregate(innerComponent, (TComponent inner, Func factory) => factory(inner)); } public static TComponent BuildComponentChain(this LazyComponentRegistrationList registrations, TComponent innerComponent, Func argumentBuilder) { return registrations.InReverseOrder.Aggregate(innerComponent, (TComponent inner, Func factory) => factory(argumentBuilder(inner))); } public static List BuildComponentList(this LazyComponentRegistrationList registrations) { return registrations.Select((Func factory) => factory(default(Nothing))).ToList(); } public static List BuildComponentList(this LazyComponentRegistrationList registrations, TArgument argument) { return registrations.Select((Func factory) => factory(argument)).ToList(); } } [StructLayout(LayoutKind.Sequential, Size = 1)] internal struct Nothing { } internal sealed class ObjectDescriptor : IObjectDescriptor { public object? Value { get; private set; } public Type Type { get; private set; } public Type StaticType { get; private set; } public ScalarStyle ScalarStyle { get; private set; } public ObjectDescriptor(object? value, Type type, Type staticType) : this(value, type, staticType, ScalarStyle.Any) { } public ObjectDescriptor(object? value, Type type, Type staticType, ScalarStyle scalarStyle) { Value = value; Type = type ?? throw new ArgumentNullException("type"); StaticType = staticType ?? throw new ArgumentNullException("staticType"); ScalarStyle = scalarStyle; } } internal delegate IObjectGraphTraversalStrategy ObjectGraphTraversalStrategyFactory(ITypeInspector typeInspector, ITypeResolver typeResolver, IEnumerable typeConverters, int maximumRecursion); internal sealed class PropertyDescriptor : IPropertyDescriptor { private readonly IPropertyDescriptor baseDescriptor; public bool AllowNulls => baseDescriptor.AllowNulls; public string Name { get; set; } public bool Required => baseDescriptor.Required; public Type Type => baseDescriptor.Type; public Type? TypeOverride { get { return baseDescriptor.TypeOverride; } set { baseDescriptor.TypeOverride = value; } } public Type? ConverterType => baseDescriptor.ConverterType; public int Order { get; set; } public ScalarStyle ScalarStyle { get { return baseDescriptor.ScalarStyle; } set { baseDescriptor.ScalarStyle = value; } } public bool CanWrite => baseDescriptor.CanWrite; public PropertyDescriptor(IPropertyDescriptor baseDescriptor) { this.baseDescriptor = baseDescriptor; Name = baseDescriptor.Name; } public void Write(object target, object? value) { baseDescriptor.Write(target, value); } public T? GetCustomAttribute() where T : Attribute { return baseDescriptor.GetCustomAttribute(); } public IObjectDescriptor Read(object target) { return baseDescriptor.Read(target); } } internal sealed class Serializer : ISerializer { private readonly IValueSerializer valueSerializer; private readonly EmitterSettings emitterSettings; public Serializer() : this(new SerializerBuilder().BuildValueSerializer(), EmitterSettings.Default) { } private Serializer(IValueSerializer valueSerializer, EmitterSettings emitterSettings) { this.valueSerializer = valueSerializer ?? throw new ArgumentNullException("valueSerializer"); this.emitterSettings = emitterSettings ?? throw new ArgumentNullException("emitterSettings"); } public static Serializer FromValueSerializer(IValueSerializer valueSerializer, EmitterSettings emitterSettings) { return new Serializer(valueSerializer, emitterSettings); } public string Serialize(object? graph) { using StringWriter stringWriter = new StringWriter(); Serialize(stringWriter, graph); return stringWriter.ToString(); } public string Serialize(object? graph, Type type) { using StringWriter stringWriter = new StringWriter(); Serialize(stringWriter, graph, type); return stringWriter.ToString(); } public void Serialize(TextWriter writer, object? graph) { Serialize(new Emitter(writer, emitterSettings), graph); } public void Serialize(TextWriter writer, object? graph, Type type) { Serialize(new Emitter(writer, emitterSettings), graph, type); } public void Serialize(IEmitter emitter, object? graph) { if (emitter == null) { throw new ArgumentNullException("emitter"); } EmitDocument(emitter, graph, null); } public void Serialize(IEmitter emitter, object? graph, Type type) { if (emitter == null) { throw new ArgumentNullException("emitter"); } if (type == null) { throw new ArgumentNullException("type"); } EmitDocument(emitter, graph, type); } private void EmitDocument(IEmitter emitter, object? graph, Type? type) { emitter.Emit(new YamlDotNet.Core.Events.StreamStart()); emitter.Emit(new YamlDotNet.Core.Events.DocumentStart()); valueSerializer.SerializeValue(emitter, graph, type); emitter.Emit(new YamlDotNet.Core.Events.DocumentEnd(isImplicit: true)); emitter.Emit(new YamlDotNet.Core.Events.StreamEnd()); } } internal sealed class SerializerBuilder : BuilderSkeleton { private class ValueSerializer : IValueSerializer { private readonly IObjectGraphTraversalStrategy traversalStrategy; private readonly IEventEmitter eventEmitter; private readonly IEnumerable typeConverters; private readonly LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories; public ValueSerializer(IObjectGraphTraversalStrategy traversalStrategy, IEventEmitter eventEmitter, IEnumerable typeConverters, LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories, LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories) { this.traversalStrategy = traversalStrategy; this.eventEmitter = eventEmitter; this.typeConverters = typeConverters; this.preProcessingPhaseObjectGraphVisitorFactories = preProcessingPhaseObjectGraphVisitorFactories; this.emissionPhaseObjectGraphVisitorFactories = emissionPhaseObjectGraphVisitorFactories; } public void SerializeValue(IEmitter emitter, object? value, Type? type) { Type type2 = type ?? ((value != null) ? value.GetType() : typeof(object)); Type staticType = type ?? typeof(object); ObjectDescriptor graph = new ObjectDescriptor(value, type2, staticType); List> preProcessingPhaseObjectGraphVisitors = preProcessingPhaseObjectGraphVisitorFactories.BuildComponentList(typeConverters); IObjectGraphVisitor visitor = emissionPhaseObjectGraphVisitorFactories.BuildComponentChain>(new EmittingObjectGraphVisitor(eventEmitter), (IObjectGraphVisitor inner) => new EmissionPhaseObjectGraphVisitorArgs(inner, eventEmitter, preProcessingPhaseObjectGraphVisitors, typeConverters, NestedObjectSerializer)); foreach (IObjectGraphVisitor item in preProcessingPhaseObjectGraphVisitors) { traversalStrategy.Traverse(graph, item, default(Nothing), NestedObjectSerializer); } traversalStrategy.Traverse(graph, visitor, emitter, NestedObjectSerializer); void NestedObjectSerializer(object? v, Type? t) { SerializeValue(emitter, v, t); } } } private ObjectGraphTraversalStrategyFactory objectGraphTraversalStrategyFactory; private readonly LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList eventEmitterFactories; private readonly Dictionary tagMappings = new Dictionary(); private readonly IObjectFactory objectFactory; private int maximumRecursion = 50; private EmitterSettings emitterSettings = EmitterSettings.Default; private DefaultValuesHandling defaultValuesHandlingConfiguration; private ScalarStyle defaultScalarStyle; private bool quoteNecessaryStrings; private bool quoteYaml1_1Strings; protected override SerializerBuilder Self => this; public SerializerBuilder() : base((ITypeResolver)new DynamicTypeResolver()) { typeInspectorFactories.Add(typeof(CachedTypeInspector), (ITypeInspector inner) => new CachedTypeInspector(inner)); typeInspectorFactories.Add(typeof(NamingConventionTypeInspector), (ITypeInspector inner) => (!(namingConvention is NullNamingConvention)) ? new NamingConventionTypeInspector(inner, namingConvention) : inner); typeInspectorFactories.Add(typeof(YamlAttributesTypeInspector), (ITypeInspector inner) => new YamlAttributesTypeInspector(inner)); typeInspectorFactories.Add(typeof(YamlAttributeOverridesInspector), (ITypeInspector inner) => (overrides == null) ? inner : new YamlAttributeOverridesInspector(inner, overrides.Clone())); preProcessingPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList, IObjectGraphVisitor> { { typeof(AnchorAssigner), (IEnumerable typeConverters) => new AnchorAssigner(typeConverters) } }; emissionPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList> { { typeof(CustomSerializationObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new CustomSerializationObjectGraphVisitor(args.InnerVisitor, args.TypeConverters, args.NestedObjectSerializer) }, { typeof(AnchorAssigningObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new AnchorAssigningObjectGraphVisitor(args.InnerVisitor, args.EventEmitter, args.GetPreProcessingPhaseObjectGraphVisitor()) }, { typeof(DefaultValuesObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new DefaultValuesObjectGraphVisitor(defaultValuesHandlingConfiguration, args.InnerVisitor, new DefaultObjectFactory()) }, { typeof(CommentsObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new CommentsObjectGraphVisitor(args.InnerVisitor) } }; eventEmitterFactories = new LazyComponentRegistrationList { { typeof(TypeAssigningEventEmitter), (IEventEmitter inner) => new TypeAssigningEventEmitter(inner, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings, defaultScalarStyle, yamlFormatter, enumNamingConvention, BuildTypeInspector()) } }; objectFactory = new DefaultObjectFactory(); objectGraphTraversalStrategyFactory = (ITypeInspector typeInspector, ITypeResolver typeResolver, IEnumerable typeConverters, int maximumRecursion) => new FullObjectGraphTraversalStrategy(typeInspector, typeResolver, maximumRecursion, namingConvention, objectFactory); } public SerializerBuilder WithQuotingNecessaryStrings(bool quoteYaml1_1Strings = false) { quoteNecessaryStrings = true; this.quoteYaml1_1Strings = quoteYaml1_1Strings; return this; } public SerializerBuilder WithDefaultScalarStyle(ScalarStyle style) { defaultScalarStyle = style; return this; } public SerializerBuilder WithMaximumRecursion(int maximumRecursion) { if (maximumRecursion <= 0) { throw new ArgumentOutOfRangeException("maximumRecursion", $"The maximum recursion specified ({maximumRecursion}) is invalid. It should be a positive integer."); } this.maximumRecursion = maximumRecursion; return this; } public SerializerBuilder WithEventEmitter(Func eventEmitterFactory) where TEventEmitter : IEventEmitter { return WithEventEmitter(eventEmitterFactory, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public SerializerBuilder WithEventEmitter(Func eventEmitterFactory) where TEventEmitter : IEventEmitter { return WithEventEmitter(eventEmitterFactory, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public SerializerBuilder WithEventEmitter(Func eventEmitterFactory, Action> where) where TEventEmitter : IEventEmitter { return WithEventEmitter((IEventEmitter e, ITypeInspector _) => eventEmitterFactory(e), where); } public SerializerBuilder WithEventEmitter(Func eventEmitterFactory, Action> where) where TEventEmitter : IEventEmitter { if (eventEmitterFactory == null) { throw new ArgumentNullException("eventEmitterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(eventEmitterFactories.CreateRegistrationLocationSelector(typeof(TEventEmitter), (IEventEmitter inner) => eventEmitterFactory(inner, BuildTypeInspector()))); return Self; } public SerializerBuilder WithEventEmitter(WrapperFactory eventEmitterFactory, Action> where) where TEventEmitter : IEventEmitter { if (eventEmitterFactory == null) { throw new ArgumentNullException("eventEmitterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(eventEmitterFactories.CreateTrackingRegistrationLocationSelector(typeof(TEventEmitter), (IEventEmitter wrapped, IEventEmitter inner) => eventEmitterFactory(wrapped, inner))); return Self; } public SerializerBuilder WithoutEventEmitter() where TEventEmitter : IEventEmitter { return WithoutEventEmitter(typeof(TEventEmitter)); } public SerializerBuilder WithoutEventEmitter(Type eventEmitterType) { if (eventEmitterType == null) { throw new ArgumentNullException("eventEmitterType"); } eventEmitterFactories.Remove(eventEmitterType); return this; } public override SerializerBuilder WithTagMapping(TagName tag, Type type) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (type == null) { throw new ArgumentNullException("type"); } if (tagMappings.TryGetValue(type, out var value)) { throw new ArgumentException($"Type already has a registered tag '{value}' for type '{type.FullName}'", "type"); } tagMappings.Add(type, tag); return this; } public SerializerBuilder WithoutTagMapping(Type type) { if (type == null) { throw new ArgumentNullException("type"); } if (!tagMappings.Remove(type)) { throw new KeyNotFoundException("Tag for type '" + type.FullName + "' is not registered"); } return this; } public SerializerBuilder EnsureRoundtrip() { objectGraphTraversalStrategyFactory = (ITypeInspector typeInspector, ITypeResolver typeResolver, IEnumerable typeConverters, int maximumRecursion) => new RoundtripObjectGraphTraversalStrategy(typeConverters, typeInspector, typeResolver, maximumRecursion, namingConvention, settings, objectFactory); WithEventEmitter((IEventEmitter inner) => new TypeAssigningEventEmitter(inner, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings, defaultScalarStyle, yamlFormatter, enumNamingConvention, BuildTypeInspector()), delegate(IRegistrationLocationSelectionSyntax loc) { loc.InsteadOf(); }); return WithTypeInspector((ITypeInspector inner) => new ReadableAndWritablePropertiesTypeInspector(inner), delegate(IRegistrationLocationSelectionSyntax loc) { loc.OnBottom(); }); } public SerializerBuilder DisableAliases() { preProcessingPhaseObjectGraphVisitorFactories.Remove(typeof(AnchorAssigner)); emissionPhaseObjectGraphVisitorFactories.Remove(typeof(AnchorAssigningObjectGraphVisitor)); return this; } [Obsolete("The default behavior is now to always emit default values, thefore calling this method has no effect. This behavior is now controlled by ConfigureDefaultValuesHandling.", true)] public SerializerBuilder EmitDefaults() { return ConfigureDefaultValuesHandling(DefaultValuesHandling.Preserve); } public SerializerBuilder ConfigureDefaultValuesHandling(DefaultValuesHandling configuration) { defaultValuesHandlingConfiguration = configuration; return this; } public SerializerBuilder JsonCompatible() { emitterSettings = emitterSettings.WithMaxSimpleKeyLength(int.MaxValue).WithoutAnchorName().WithUtf16SurrogatePairs(); return WithTypeConverter(new YamlDotNet.Serialization.Converters.GuidConverter(jsonCompatible: true), delegate(IRegistrationLocationSelectionSyntax w) { w.InsteadOf(); }).WithTypeConverter(new DateTime8601Converter(ScalarStyle.DoubleQuoted)).WithEventEmitter((IEventEmitter inner) => new JsonEventEmitter(inner, yamlFormatter, enumNamingConvention, BuildTypeInspector()), delegate(IRegistrationLocationSelectionSyntax loc) { loc.InsteadOf(); }); } public SerializerBuilder WithNewLine(string newLine) { emitterSettings = emitterSettings.WithNewLine(newLine); return this; } public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(TObjectGraphVisitor objectGraphVisitor) where TObjectGraphVisitor : IObjectGraphVisitor { return WithPreProcessingPhaseObjectGraphVisitor(objectGraphVisitor, delegate(IRegistrationLocationSelectionSyntax> w) { w.OnTop(); }); } public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(Func, TObjectGraphVisitor> objectGraphVisitorFactory) where TObjectGraphVisitor : IObjectGraphVisitor { return WithPreProcessingPhaseObjectGraphVisitor(objectGraphVisitorFactory, delegate(IRegistrationLocationSelectionSyntax> w) { w.OnTop(); }); } public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(TObjectGraphVisitor objectGraphVisitor, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitor == null) { throw new ArgumentNullException("objectGraphVisitor"); } if (where == null) { throw new ArgumentNullException("where"); } where(preProcessingPhaseObjectGraphVisitorFactories.CreateRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IEnumerable _) => objectGraphVisitor)); return this; } public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(Func, TObjectGraphVisitor> objectGraphVisitorFactory, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(preProcessingPhaseObjectGraphVisitorFactories.CreateRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IEnumerable typeConverters) => objectGraphVisitorFactory(typeConverters))); return this; } public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(WrapperFactory, TObjectGraphVisitor> objectGraphVisitorFactory, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(preProcessingPhaseObjectGraphVisitorFactories.CreateTrackingRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IObjectGraphVisitor wrapped, IEnumerable _) => objectGraphVisitorFactory(wrapped))); return this; } public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(WrapperFactory, IObjectGraphVisitor, TObjectGraphVisitor> objectGraphVisitorFactory, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(preProcessingPhaseObjectGraphVisitorFactories.CreateTrackingRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IObjectGraphVisitor wrapped, IEnumerable typeConverters) => objectGraphVisitorFactory(wrapped, typeConverters))); return this; } public SerializerBuilder WithoutPreProcessingPhaseObjectGraphVisitor() where TObjectGraphVisitor : IObjectGraphVisitor { return WithoutPreProcessingPhaseObjectGraphVisitor(typeof(TObjectGraphVisitor)); } public SerializerBuilder WithoutPreProcessingPhaseObjectGraphVisitor(Type objectGraphVisitorType) { if (objectGraphVisitorType == null) { throw new ArgumentNullException("objectGraphVisitorType"); } preProcessingPhaseObjectGraphVisitorFactories.Remove(objectGraphVisitorType); return this; } public SerializerBuilder WithObjectGraphTraversalStrategyFactory(ObjectGraphTraversalStrategyFactory objectGraphTraversalStrategyFactory) { this.objectGraphTraversalStrategyFactory = objectGraphTraversalStrategyFactory; return this; } public SerializerBuilder WithEmissionPhaseObjectGraphVisitor(Func objectGraphVisitorFactory) where TObjectGraphVisitor : IObjectGraphVisitor { return WithEmissionPhaseObjectGraphVisitor(objectGraphVisitorFactory, delegate(IRegistrationLocationSelectionSyntax> w) { w.OnTop(); }); } public SerializerBuilder WithEmissionPhaseObjectGraphVisitor(Func objectGraphVisitorFactory, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(emissionPhaseObjectGraphVisitorFactories.CreateRegistrationLocationSelector(typeof(TObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => objectGraphVisitorFactory(args))); return this; } public SerializerBuilder WithEmissionPhaseObjectGraphVisitor(WrapperFactory, TObjectGraphVisitor> objectGraphVisitorFactory, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(emissionPhaseObjectGraphVisitorFactories.CreateTrackingRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IObjectGraphVisitor wrapped, EmissionPhaseObjectGraphVisitorArgs args) => objectGraphVisitorFactory(wrapped, args))); return this; } public SerializerBuilder WithoutEmissionPhaseObjectGraphVisitor() where TObjectGraphVisitor : IObjectGraphVisitor { return WithoutEmissionPhaseObjectGraphVisitor(typeof(TObjectGraphVisitor)); } public SerializerBuilder WithoutEmissionPhaseObjectGraphVisitor(Type objectGraphVisitorType) { if (objectGraphVisitorType == null) { throw new ArgumentNullException("objectGraphVisitorType"); } emissionPhaseObjectGraphVisitorFactories.Remove(objectGraphVisitorType); return this; } public SerializerBuilder WithIndentedSequences() { emitterSettings = emitterSettings.WithIndentedSequences(); return this; } public ISerializer Build() { if (FsharpHelper.Instance == null) { FsharpHelper.Instance = new DefaultFsharpHelper(); } return Serializer.FromValueSerializer(BuildValueSerializer(), emitterSettings); } public IValueSerializer BuildValueSerializer() { IEnumerable typeConverters = BuildTypeConverters(); ITypeInspector typeInspector = BuildTypeInspector(); IObjectGraphTraversalStrategy traversalStrategy = objectGraphTraversalStrategyFactory(typeInspector, typeResolver, typeConverters, maximumRecursion); IEventEmitter eventEmitter = eventEmitterFactories.BuildComponentChain(new WriterEventEmitter()); return new ValueSerializer(traversalStrategy, eventEmitter, typeConverters, preProcessingPhaseObjectGraphVisitorFactories.Clone(), emissionPhaseObjectGraphVisitorFactories.Clone()); } public ITypeInspector BuildTypeInspector() { ITypeInspector typeInspector = new ReadablePropertiesTypeInspector(typeResolver, includeNonPublicProperties); if (!ignoreFields) { typeInspector = new CompositeTypeInspector(new ReadableFieldsTypeInspector(typeResolver), typeInspector); } return typeInspectorFactories.BuildComponentChain(typeInspector); } } internal class Settings { public bool AllowPrivateConstructors { get; set; } } internal abstract class StaticBuilderSkeleton where TBuilder : StaticBuilderSkeleton { internal INamingConvention namingConvention = NullNamingConvention.Instance; internal INamingConvention enumNamingConvention = NullNamingConvention.Instance; internal ITypeResolver typeResolver; internal readonly LazyComponentRegistrationList typeConverterFactories; internal readonly LazyComponentRegistrationList typeInspectorFactories; internal bool includeNonPublicProperties; internal Settings settings; internal YamlFormatter yamlFormatter = YamlFormatter.Default; protected abstract TBuilder Self { get; } internal StaticBuilderSkeleton(ITypeResolver typeResolver) { typeConverterFactories = new LazyComponentRegistrationList { { typeof(YamlDotNet.Serialization.Converters.GuidConverter), (Nothing _) => new YamlDotNet.Serialization.Converters.GuidConverter(jsonCompatible: false) } }; typeInspectorFactories = new LazyComponentRegistrationList(); this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); settings = new Settings(); } public TBuilder WithNamingConvention(INamingConvention namingConvention) { this.namingConvention = namingConvention ?? throw new ArgumentNullException("namingConvention"); return Self; } public TBuilder WithEnumNamingConvention(INamingConvention enumNamingConvention) { this.enumNamingConvention = enumNamingConvention ?? throw new ArgumentNullException("enumNamingConvention"); return Self; } public TBuilder WithTypeResolver(ITypeResolver typeResolver) { this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); return Self; } public abstract TBuilder WithTagMapping(TagName tag, Type type); public TBuilder WithTypeConverter(IYamlTypeConverter typeConverter) { return WithTypeConverter(typeConverter, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public TBuilder WithTypeConverter(IYamlTypeConverter typeConverter, Action> where) { if (typeConverter == null) { throw new ArgumentNullException("typeConverter"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeConverterFactories.CreateRegistrationLocationSelector(typeConverter.GetType(), (Nothing _) => typeConverter)); return Self; } public TBuilder WithTypeConverter(WrapperFactory typeConverterFactory, Action> where) where TYamlTypeConverter : IYamlTypeConverter { if (typeConverterFactory == null) { throw new ArgumentNullException("typeConverterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeConverterFactories.CreateTrackingRegistrationLocationSelector(typeof(TYamlTypeConverter), (IYamlTypeConverter wrapped, Nothing _) => typeConverterFactory(wrapped))); return Self; } public TBuilder WithoutTypeConverter() where TYamlTypeConverter : IYamlTypeConverter { return WithoutTypeConverter(typeof(TYamlTypeConverter)); } public TBuilder WithoutTypeConverter(Type converterType) { if (converterType == null) { throw new ArgumentNullException("converterType"); } typeConverterFactories.Remove(converterType); return Self; } public TBuilder WithTypeInspector(Func typeInspectorFactory) where TTypeInspector : ITypeInspector { return WithTypeInspector(typeInspectorFactory, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public TBuilder WithTypeInspector(Func typeInspectorFactory, Action> where) where TTypeInspector : ITypeInspector { if (typeInspectorFactory == null) { throw new ArgumentNullException("typeInspectorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeInspectorFactories.CreateRegistrationLocationSelector(typeof(TTypeInspector), (ITypeInspector inner) => typeInspectorFactory(inner))); return Self; } public TBuilder WithTypeInspector(WrapperFactory typeInspectorFactory, Action> where) where TTypeInspector : ITypeInspector { if (typeInspectorFactory == null) { throw new ArgumentNullException("typeInspectorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeInspectorFactories.CreateTrackingRegistrationLocationSelector(typeof(TTypeInspector), (ITypeInspector wrapped, ITypeInspector inner) => typeInspectorFactory(wrapped, inner))); return Self; } public TBuilder WithoutTypeInspector() where TTypeInspector : ITypeInspector { return WithoutTypeInspector(typeof(TTypeInspector)); } public TBuilder WithoutTypeInspector(Type inspectorType) { if (inspectorType == null) { throw new ArgumentNullException("inspectorType"); } typeInspectorFactories.Remove(inspectorType); return Self; } public TBuilder WithYamlFormatter(YamlFormatter formatter) { yamlFormatter = formatter ?? throw new ArgumentNullException("formatter"); return Self; } protected IEnumerable BuildTypeConverters() { return typeConverterFactories.BuildComponentList(); } } internal abstract class StaticContext { public virtual bool IsKnownType(Type type) { throw new NotImplementedException(); } public virtual ITypeResolver GetTypeResolver() { throw new NotImplementedException(); } public virtual StaticObjectFactory GetFactory() { throw new NotImplementedException(); } public virtual ITypeInspector GetTypeInspector() { throw new NotImplementedException(); } } internal sealed class StaticDeserializerBuilder : StaticBuilderSkeleton { private readonly StaticContext context; private readonly StaticObjectFactory factory; private readonly LazyComponentRegistrationList nodeDeserializerFactories; private readonly LazyComponentRegistrationList nodeTypeResolverFactories; private readonly Dictionary tagMappings; private readonly ITypeConverter typeConverter; private readonly Dictionary typeMappings; private bool ignoreUnmatched; private bool duplicateKeyChecking; private bool attemptUnknownTypeDeserialization; private bool enforceNullability; private bool caseInsensitivePropertyMatching; protected override StaticDeserializerBuilder Self => this; public StaticDeserializerBuilder(StaticContext context) : base(context.GetTypeResolver()) { this.context = context; factory = context.GetFactory(); typeMappings = new Dictionary(); tagMappings = new Dictionary { { FailsafeSchema.Tags.Map, typeof(Dictionary) }, { FailsafeSchema.Tags.Str, typeof(string) }, { JsonSchema.Tags.Bool, typeof(bool) }, { JsonSchema.Tags.Float, typeof(double) }, { JsonSchema.Tags.Int, typeof(int) }, { DefaultSchema.Tags.Timestamp, typeof(DateTime) } }; typeInspectorFactories.Add(typeof(CachedTypeInspector), (ITypeInspector inner) => new CachedTypeInspector(inner)); typeInspectorFactories.Add(typeof(NamingConventionTypeInspector), (ITypeInspector inner) => (!(namingConvention is NullNamingConvention)) ? new NamingConventionTypeInspector(inner, namingConvention) : inner); typeInspectorFactories.Add(typeof(YamlAttributesTypeInspector), (ITypeInspector inner) => new YamlAttributesTypeInspector(inner)); nodeDeserializerFactories = new LazyComponentRegistrationList { { typeof(YamlConvertibleNodeDeserializer), (Nothing _) => new YamlConvertibleNodeDeserializer(factory) }, { typeof(YamlSerializableNodeDeserializer), (Nothing _) => new YamlSerializableNodeDeserializer(factory) }, { typeof(TypeConverterNodeDeserializer), (Nothing _) => new TypeConverterNodeDeserializer(BuildTypeConverters()) }, { typeof(NullNodeDeserializer), (Nothing _) => new NullNodeDeserializer() }, { typeof(ScalarNodeDeserializer), (Nothing _) => new ScalarNodeDeserializer(attemptUnknownTypeDeserialization, typeConverter, BuildTypeInspector(), yamlFormatter, enumNamingConvention) }, { typeof(StaticArrayNodeDeserializer), (Nothing _) => new StaticArrayNodeDeserializer(factory) }, { typeof(StaticDictionaryNodeDeserializer), (Nothing _) => new StaticDictionaryNodeDeserializer(factory, duplicateKeyChecking) }, { typeof(StaticCollectionNodeDeserializer), (Nothing _) => new StaticCollectionNodeDeserializer(factory) }, { typeof(ObjectNodeDeserializer), (Nothing _) => new ObjectNodeDeserializer(factory, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking, typeConverter, enumNamingConvention, enforceNullability, caseInsensitivePropertyMatching, enforceRequiredProperties: false, BuildTypeConverters()) } }; nodeTypeResolverFactories = new LazyComponentRegistrationList { { typeof(MappingNodeTypeResolver), (Nothing _) => new MappingNodeTypeResolver(typeMappings) }, { typeof(YamlConvertibleTypeResolver), (Nothing _) => new YamlConvertibleTypeResolver() }, { typeof(YamlSerializableTypeResolver), (Nothing _) => new YamlSerializableTypeResolver() }, { typeof(TagNodeTypeResolver), (Nothing _) => new TagNodeTypeResolver(tagMappings) }, { typeof(PreventUnknownTagsNodeTypeResolver), (Nothing _) => new PreventUnknownTagsNodeTypeResolver() }, { typeof(DefaultContainersNodeTypeResolver), (Nothing _) => new DefaultContainersNodeTypeResolver() } }; typeConverter = new NullTypeConverter(); } public ITypeInspector BuildTypeInspector() { ITypeInspector typeInspector = context.GetTypeInspector(); return typeInspectorFactories.BuildComponentChain(typeInspector); } public StaticDeserializerBuilder WithAttemptingUnquotedStringTypeDeserialization() { attemptUnknownTypeDeserialization = true; return this; } public StaticDeserializerBuilder WithNodeDeserializer(INodeDeserializer nodeDeserializer) { return WithNodeDeserializer(nodeDeserializer, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public StaticDeserializerBuilder WithNodeDeserializer(INodeDeserializer nodeDeserializer, Action> where) { if (nodeDeserializer == null) { throw new ArgumentNullException("nodeDeserializer"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeDeserializerFactories.CreateRegistrationLocationSelector(nodeDeserializer.GetType(), (Nothing _) => nodeDeserializer)); return this; } public StaticDeserializerBuilder WithNodeDeserializer(WrapperFactory nodeDeserializerFactory, Action> where) where TNodeDeserializer : INodeDeserializer { if (nodeDeserializerFactory == null) { throw new ArgumentNullException("nodeDeserializerFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeDeserializerFactories.CreateTrackingRegistrationLocationSelector(typeof(TNodeDeserializer), (INodeDeserializer wrapped, Nothing _) => nodeDeserializerFactory(wrapped))); return this; } public StaticDeserializerBuilder WithCaseInsensitivePropertyMatching() { caseInsensitivePropertyMatching = true; return this; } public StaticDeserializerBuilder WithEnforceNullability() { enforceNullability = true; return this; } public StaticDeserializerBuilder WithoutNodeDeserializer() where TNodeDeserializer : INodeDeserializer { return WithoutNodeDeserializer(typeof(TNodeDeserializer)); } public StaticDeserializerBuilder WithoutNodeDeserializer(Type nodeDeserializerType) { if (nodeDeserializerType == null) { throw new ArgumentNullException("nodeDeserializerType"); } nodeDeserializerFactories.Remove(nodeDeserializerType); return this; } public StaticDeserializerBuilder WithTypeDiscriminatingNodeDeserializer(Action configureTypeDiscriminatingNodeDeserializerOptions, int maxDepth = -1, int maxLength = -1) { TypeDiscriminatingNodeDeserializerOptions typeDiscriminatingNodeDeserializerOptions = new TypeDiscriminatingNodeDeserializerOptions(); configureTypeDiscriminatingNodeDeserializerOptions(typeDiscriminatingNodeDeserializerOptions); TypeDiscriminatingNodeDeserializer nodeDeserializer = new TypeDiscriminatingNodeDeserializer(nodeDeserializerFactories.BuildComponentList(), typeDiscriminatingNodeDeserializerOptions.discriminators, maxDepth, maxLength); return WithNodeDeserializer(nodeDeserializer, delegate(IRegistrationLocationSelectionSyntax s) { s.Before(); }); } public StaticDeserializerBuilder WithNodeTypeResolver(INodeTypeResolver nodeTypeResolver) { return WithNodeTypeResolver(nodeTypeResolver, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public StaticDeserializerBuilder WithNodeTypeResolver(INodeTypeResolver nodeTypeResolver, Action> where) { if (nodeTypeResolver == null) { throw new ArgumentNullException("nodeTypeResolver"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeTypeResolverFactories.CreateRegistrationLocationSelector(nodeTypeResolver.GetType(), (Nothing _) => nodeTypeResolver)); return this; } public StaticDeserializerBuilder WithNodeTypeResolver(WrapperFactory nodeTypeResolverFactory, Action> where) where TNodeTypeResolver : INodeTypeResolver { if (nodeTypeResolverFactory == null) { throw new ArgumentNullException("nodeTypeResolverFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeTypeResolverFactories.CreateTrackingRegistrationLocationSelector(typeof(TNodeTypeResolver), (INodeTypeResolver wrapped, Nothing _) => nodeTypeResolverFactory(wrapped))); return this; } public StaticDeserializerBuilder WithoutNodeTypeResolver() where TNodeTypeResolver : INodeTypeResolver { return WithoutNodeTypeResolver(typeof(TNodeTypeResolver)); } public StaticDeserializerBuilder WithoutNodeTypeResolver(Type nodeTypeResolverType) { if (nodeTypeResolverType == null) { throw new ArgumentNullException("nodeTypeResolverType"); } nodeTypeResolverFactories.Remove(nodeTypeResolverType); return this; } public override StaticDeserializerBuilder WithTagMapping(TagName tag, Type type) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (type == null) { throw new ArgumentNullException("type"); } if (tagMappings.TryGetValue(tag, out Type value)) { throw new ArgumentException($"Type already has a registered type '{value.FullName}' for tag '{tag}'", "tag"); } tagMappings.Add(tag, type); return this; } public StaticDeserializerBuilder WithTypeMapping() where TConcrete : TInterface { Type typeFromHandle = typeof(TInterface); Type typeFromHandle2 = typeof(TConcrete); if (!typeFromHandle.IsAssignableFrom(typeFromHandle2)) { throw new InvalidOperationException("The type '" + typeFromHandle2.Name + "' does not implement interface '" + typeFromHandle.Name + "'."); } typeMappings[typeFromHandle] = typeFromHandle2; return this; } public StaticDeserializerBuilder WithoutTagMapping(TagName tag) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (!tagMappings.Remove(tag)) { throw new KeyNotFoundException($"Tag '{tag}' is not registered"); } return this; } public StaticDeserializerBuilder IgnoreUnmatchedProperties() { ignoreUnmatched = true; return this; } public StaticDeserializerBuilder WithDuplicateKeyChecking() { duplicateKeyChecking = true; return this; } public IDeserializer Build() { return Deserializer.FromValueDeserializer(BuildValueDeserializer()); } public IValueDeserializer BuildValueDeserializer() { return new AliasValueDeserializer(new NodeValueDeserializer(nodeDeserializerFactories.BuildComponentList(), nodeTypeResolverFactories.BuildComponentList(), typeConverter, enumNamingConvention, BuildTypeInspector())); } } internal sealed class StaticSerializerBuilder : StaticBuilderSkeleton { private class ValueSerializer : IValueSerializer { private readonly IObjectGraphTraversalStrategy traversalStrategy; private readonly IEventEmitter eventEmitter; private readonly IEnumerable typeConverters; private readonly LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories; public ValueSerializer(IObjectGraphTraversalStrategy traversalStrategy, IEventEmitter eventEmitter, IEnumerable typeConverters, LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories, LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories) { this.traversalStrategy = traversalStrategy; this.eventEmitter = eventEmitter; this.typeConverters = typeConverters; this.preProcessingPhaseObjectGraphVisitorFactories = preProcessingPhaseObjectGraphVisitorFactories; this.emissionPhaseObjectGraphVisitorFactories = emissionPhaseObjectGraphVisitorFactories; } public void SerializeValue(IEmitter emitter, object? value, Type? type) { Type type2 = type ?? ((value != null) ? value.GetType() : typeof(object)); Type staticType = type ?? typeof(object); ObjectDescriptor graph = new ObjectDescriptor(value, type2, staticType); List> preProcessingPhaseObjectGraphVisitors = preProcessingPhaseObjectGraphVisitorFactories.BuildComponentList(typeConverters); foreach (IObjectGraphVisitor item in preProcessingPhaseObjectGraphVisitors) { traversalStrategy.Traverse(graph, item, default(Nothing), NestedObjectSerializer); } IObjectGraphVisitor visitor = emissionPhaseObjectGraphVisitorFactories.BuildComponentChain>(new EmittingObjectGraphVisitor(eventEmitter), (IObjectGraphVisitor inner) => new EmissionPhaseObjectGraphVisitorArgs(inner, eventEmitter, preProcessingPhaseObjectGraphVisitors, typeConverters, NestedObjectSerializer)); traversalStrategy.Traverse(graph, visitor, emitter, NestedObjectSerializer); void NestedObjectSerializer(object? v, Type? t) { SerializeValue(emitter, v, t); } } } private readonly StaticContext context; private readonly StaticObjectFactory factory; private ObjectGraphTraversalStrategyFactory objectGraphTraversalStrategyFactory; private readonly LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList eventEmitterFactories; private readonly Dictionary tagMappings = new Dictionary(); private int maximumRecursion = 50; private EmitterSettings emitterSettings = EmitterSettings.Default; private DefaultValuesHandling defaultValuesHandlingConfiguration; private bool quoteNecessaryStrings; private bool quoteYaml1_1Strings; private ScalarStyle defaultScalarStyle; protected override StaticSerializerBuilder Self => this; public StaticSerializerBuilder(StaticContext context) : base((ITypeResolver)new DynamicTypeResolver()) { this.context = context; factory = context.GetFactory(); typeInspectorFactories.Add(typeof(CachedTypeInspector), (ITypeInspector inner) => new CachedTypeInspector(inner)); typeInspectorFactories.Add(typeof(NamingConventionTypeInspector), (ITypeInspector inner) => (!(namingConvention is NullNamingConvention)) ? new NamingConventionTypeInspector(inner, namingConvention) : inner); typeInspectorFactories.Add(typeof(YamlAttributesTypeInspector), (ITypeInspector inner) => new YamlAttributesTypeInspector(inner)); preProcessingPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList, IObjectGraphVisitor> { { typeof(AnchorAssigner), (IEnumerable typeConverters) => new AnchorAssigner(typeConverters) } }; emissionPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList> { { typeof(CustomSerializationObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new CustomSerializationObjectGraphVisitor(args.InnerVisitor, args.TypeConverters, args.NestedObjectSerializer) }, { typeof(AnchorAssigningObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new AnchorAssigningObjectGraphVisitor(args.InnerVisitor, args.EventEmitter, args.GetPreProcessingPhaseObjectGraphVisitor()) }, { typeof(DefaultValuesObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new DefaultValuesObjectGraphVisitor(defaultValuesHandlingConfiguration, args.InnerVisitor, factory) }, { typeof(CommentsObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new CommentsObjectGraphVisitor(args.InnerVisitor) } }; eventEmitterFactories = new LazyComponentRegistrationList { { typeof(TypeAssigningEventEmitter), (IEventEmitter inner) => new TypeAssigningEventEmitter(inner, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings, defaultScalarStyle, yamlFormatter, enumNamingConvention, BuildTypeInspector()) } }; objectGraphTraversalStrategyFactory = (ITypeInspector typeInspector, ITypeResolver typeResolver, IEnumerable typeConverters, int maximumRecursion) => new FullObjectGraphTraversalStrategy(typeInspector, typeResolver, maximumRecursion, namingConvention, factory); } public StaticSerializerBuilder WithQuotingNecessaryStrings(bool quoteYaml1_1Strings = false) { quoteNecessaryStrings = true; this.quoteYaml1_1Strings = quoteYaml1_1Strings; return this; } public StaticSerializerBuilder WithQuotingNecessaryStrings() { quoteNecessaryStrings = true; return this; } public StaticSerializerBuilder WithDefaultScalarStyle(ScalarStyle style) { defaultScalarStyle = style; return this; } public StaticSerializerBuilder WithMaximumRecursion(int maximumRecursion) { if (maximumRecursion <= 0) { throw new ArgumentOutOfRangeException("maximumRecursion", $"The maximum recursion specified ({maximumRecursion}) is invalid. It should be a positive integer."); } this.maximumRecursion = maximumRecursion; return this; } public StaticSerializerBuilder WithEventEmitter(Func eventEmitterFactory) where TEventEmitter : IEventEmitter { return WithEventEmitter(eventEmitterFactory, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public StaticSerializerBuilder WithEventEmitter(Func eventEmitterFactory) where TEventEmitter : IEventEmitter { return WithEventEmitter(eventEmitterFactory, delegate(IRegistrationLocationSelectionSyntax w) { w.OnTop(); }); } public StaticSerializerBuilder WithEventEmitter(Func eventEmitterFactory, Action> where) where TEventEmitter : IEventEmitter { return WithEventEmitter((IEventEmitter e, ITypeInspector _) => eventEmitterFactory(e), where); } public StaticSerializerBuilder WithEventEmitter(Func eventEmitterFactory, Action> where) where TEventEmitter : IEventEmitter { if (eventEmitterFactory == null) { throw new ArgumentNullException("eventEmitterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(eventEmitterFactories.CreateRegistrationLocationSelector(typeof(TEventEmitter), (IEventEmitter inner) => eventEmitterFactory(inner, BuildTypeInspector()))); return Self; } public StaticSerializerBuilder WithEventEmitter(WrapperFactory eventEmitterFactory, Action> where) where TEventEmitter : IEventEmitter { if (eventEmitterFactory == null) { throw new ArgumentNullException("eventEmitterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(eventEmitterFactories.CreateTrackingRegistrationLocationSelector(typeof(TEventEmitter), (IEventEmitter wrapped, IEventEmitter inner) => eventEmitterFactory(wrapped, inner))); return Self; } public StaticSerializerBuilder WithoutEventEmitter() where TEventEmitter : IEventEmitter { return WithoutEventEmitter(typeof(TEventEmitter)); } public StaticSerializerBuilder WithoutEventEmitter(Type eventEmitterType) { if (eventEmitterType == null) { throw new ArgumentNullException("eventEmitterType"); } eventEmitterFactories.Remove(eventEmitterType); return this; } public override StaticSerializerBuilder WithTagMapping(TagName tag, Type type) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (type == null) { throw new ArgumentNullException("type"); } if (tagMappings.TryGetValue(type, out var value)) { throw new ArgumentException($"Type already has a registered tag '{value}' for type '{type.FullName}'", "type"); } tagMappings.Add(type, tag); return this; } public StaticSerializerBuilder WithoutTagMapping(Type type) { if (type == null) { throw new ArgumentNullException("type"); } if (!tagMappings.Remove(type)) { throw new KeyNotFoundException("Tag for type '" + type.FullName + "' is not registered"); } return this; } public StaticSerializerBuilder EnsureRoundtrip() { objectGraphTraversalStrategyFactory = (ITypeInspector typeInspector, ITypeResolver typeResolver, IEnumerable typeConverters, int maximumRecursion) => new RoundtripObjectGraphTraversalStrategy(typeConverters, typeInspector, typeResolver, maximumRecursion, namingConvention, settings, factory); WithEventEmitter((IEventEmitter inner) => new TypeAssigningEventEmitter(inner, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings: false, ScalarStyle.Plain, YamlFormatter.Default, enumNamingConvention, BuildTypeInspector()), delegate(IRegistrationLocationSelectionSyntax loc) { loc.InsteadOf(); }); return WithTypeInspector((ITypeInspector inner) => new ReadableAndWritablePropertiesTypeInspector(inner), delegate(IRegistrationLocationSelectionSyntax loc) { loc.OnBottom(); }); } public StaticSerializerBuilder DisableAliases() { preProcessingPhaseObjectGraphVisitorFactories.Remove(typeof(AnchorAssigner)); emissionPhaseObjectGraphVisitorFactories.Remove(typeof(AnchorAssigningObjectGraphVisitor)); return this; } [Obsolete("The default behavior is now to always emit default values, thefore calling this method has no effect. This behavior is now controlled by ConfigureDefaultValuesHandling.", true)] public StaticSerializerBuilder EmitDefaults() { return ConfigureDefaultValuesHandling(DefaultValuesHandling.Preserve); } public StaticSerializerBuilder ConfigureDefaultValuesHandling(DefaultValuesHandling configuration) { defaultValuesHandlingConfiguration = configuration; return this; } public StaticSerializerBuilder JsonCompatible() { emitterSettings = emitterSettings.WithMaxSimpleKeyLength(int.MaxValue).WithoutAnchorName().WithUtf16SurrogatePairs(); return WithTypeConverter(new YamlDotNet.Serialization.Converters.GuidConverter(jsonCompatible: true), delegate(IRegistrationLocationSelectionSyntax w) { w.InsteadOf(); }).WithTypeConverter(new DateTime8601Converter(ScalarStyle.DoubleQuoted)).WithEventEmitter((IEventEmitter inner) => new JsonEventEmitter(inner, yamlFormatter, enumNamingConvention, BuildTypeInspector()), delegate(IRegistrationLocationSelectionSyntax loc) { loc.InsteadOf(); }); } public StaticSerializerBuilder WithNewLine(string newLine) { emitterSettings = emitterSettings.WithNewLine(newLine); return this; } public StaticSerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(TObjectGraphVisitor objectGraphVisitor) where TObjectGraphVisitor : IObjectGraphVisitor { return WithPreProcessingPhaseObjectGraphVisitor(objectGraphVisitor, delegate(IRegistrationLocationSelectionSyntax> w) { w.OnTop(); }); } public StaticSerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(Func, TObjectGraphVisitor> objectGraphVisitorFactory) where TObjectGraphVisitor : IObjectGraphVisitor { return WithPreProcessingPhaseObjectGraphVisitor(objectGraphVisitorFactory, delegate(IRegistrationLocationSelectionSyntax> w) { w.OnTop(); }); } public StaticSerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(TObjectGraphVisitor objectGraphVisitor, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitor == null) { throw new ArgumentNullException("objectGraphVisitor"); } if (where == null) { throw new ArgumentNullException("where"); } where(preProcessingPhaseObjectGraphVisitorFactories.CreateRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IEnumerable _) => objectGraphVisitor)); return this; } public StaticSerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(Func, TObjectGraphVisitor> objectGraphVisitorFactory, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(preProcessingPhaseObjectGraphVisitorFactories.CreateRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IEnumerable typeConverters) => objectGraphVisitorFactory(typeConverters))); return this; } public StaticSerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(WrapperFactory, TObjectGraphVisitor> objectGraphVisitorFactory, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(preProcessingPhaseObjectGraphVisitorFactories.CreateTrackingRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IObjectGraphVisitor wrapped, IEnumerable _) => objectGraphVisitorFactory(wrapped))); return this; } public StaticSerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(WrapperFactory, IObjectGraphVisitor, TObjectGraphVisitor> objectGraphVisitorFactory, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(preProcessingPhaseObjectGraphVisitorFactories.CreateTrackingRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IObjectGraphVisitor wrapped, IEnumerable typeConverters) => objectGraphVisitorFactory(wrapped, typeConverters))); return this; } public StaticSerializerBuilder WithoutPreProcessingPhaseObjectGraphVisitor() where TObjectGraphVisitor : IObjectGraphVisitor { return WithoutPreProcessingPhaseObjectGraphVisitor(typeof(TObjectGraphVisitor)); } public StaticSerializerBuilder WithoutPreProcessingPhaseObjectGraphVisitor(Type objectGraphVisitorType) { if (objectGraphVisitorType == null) { throw new ArgumentNullException("objectGraphVisitorType"); } preProcessingPhaseObjectGraphVisitorFactories.Remove(objectGraphVisitorType); return this; } public StaticSerializerBuilder WithObjectGraphTraversalStrategyFactory(ObjectGraphTraversalStrategyFactory objectGraphTraversalStrategyFactory) { this.objectGraphTraversalStrategyFactory = objectGraphTraversalStrategyFactory; return this; } public StaticSerializerBuilder WithEmissionPhaseObjectGraphVisitor(Func objectGraphVisitorFactory) where TObjectGraphVisitor : IObjectGraphVisitor { return WithEmissionPhaseObjectGraphVisitor(objectGraphVisitorFactory, delegate(IRegistrationLocationSelectionSyntax> w) { w.OnTop(); }); } public StaticSerializerBuilder WithEmissionPhaseObjectGraphVisitor(Func objectGraphVisitorFactory, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(emissionPhaseObjectGraphVisitorFactories.CreateRegistrationLocationSelector(typeof(TObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => objectGraphVisitorFactory(args))); return this; } public StaticSerializerBuilder WithEmissionPhaseObjectGraphVisitor(WrapperFactory, TObjectGraphVisitor> objectGraphVisitorFactory, Action>> where) where TObjectGraphVisitor : IObjectGraphVisitor { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(emissionPhaseObjectGraphVisitorFactories.CreateTrackingRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IObjectGraphVisitor wrapped, EmissionPhaseObjectGraphVisitorArgs args) => objectGraphVisitorFactory(wrapped, args))); return this; } public StaticSerializerBuilder WithoutEmissionPhaseObjectGraphVisitor() where TObjectGraphVisitor : IObjectGraphVisitor { return WithoutEmissionPhaseObjectGraphVisitor(typeof(TObjectGraphVisitor)); } public StaticSerializerBuilder WithoutEmissionPhaseObjectGraphVisitor(Type objectGraphVisitorType) { if (objectGraphVisitorType == null) { throw new ArgumentNullException("objectGraphVisitorType"); } emissionPhaseObjectGraphVisitorFactories.Remove(objectGraphVisitorType); return this; } public StaticSerializerBuilder WithIndentedSequences() { emitterSettings = emitterSettings.WithIndentedSequences(); return this; } public ISerializer Build() { return Serializer.FromValueSerializer(BuildValueSerializer(), emitterSettings); } public IValueSerializer BuildValueSerializer() { IEnumerable typeConverters = BuildTypeConverters(); ITypeInspector typeInspector = BuildTypeInspector(); IObjectGraphTraversalStrategy traversalStrategy = objectGraphTraversalStrategyFactory(typeInspector, typeResolver, typeConverters, maximumRecursion); IEventEmitter eventEmitter = eventEmitterFactories.BuildComponentChain(new WriterEventEmitter()); return new ValueSerializer(traversalStrategy, eventEmitter, typeConverters, preProcessingPhaseObjectGraphVisitorFactories.Clone(), emissionPhaseObjectGraphVisitorFactories.Clone()); } public ITypeInspector BuildTypeInspector() { ITypeInspector typeInspector = context.GetTypeInspector(); return typeInspectorFactories.BuildComponentChain(typeInspector); } } internal sealed class StreamFragment : IYamlConvertible { private readonly List events = new List(); public IList Events => events; void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) { events.Clear(); int num = 0; do { if (!parser.MoveNext()) { throw new InvalidOperationException("The parser has reached the end before deserialization completed."); } ParsingEvent current = parser.Current; events.Add(current); num += current.NestingIncrease; } while (num > 0); } void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) { foreach (ParsingEvent @event in events) { emitter.Emit(@event); } } } internal sealed class TagMappings { private readonly Dictionary mappings; public TagMappings() { mappings = new Dictionary(); } public TagMappings(IDictionary mappings) { this.mappings = new Dictionary(mappings); } public void Add(string tag, Type mapping) { mappings.Add(tag, mapping); } internal Type? GetMapping(string tag) { if (!mappings.TryGetValue(tag, out Type value)) { return null; } return value; } } internal sealed class YamlAttributeOverrides { private readonly struct AttributeKey { public readonly Type AttributeType; public readonly string PropertyName; public AttributeKey(Type attributeType, string propertyName) { AttributeType = attributeType; PropertyName = propertyName; } public override bool Equals(object? obj) { if (obj is AttributeKey attributeKey && AttributeType.Equals(attributeKey.AttributeType)) { return PropertyName.Equals(attributeKey.PropertyName); } return false; } public override int GetHashCode() { return YamlDotNet.Core.HashCode.CombineHashCodes(AttributeType.GetHashCode(), PropertyName.GetHashCode()); } } private sealed class AttributeMapping { public readonly Type RegisteredType; public readonly Attribute Attribute; public AttributeMapping(Type registeredType, Attribute attribute) { RegisteredType = registeredType; Attribute = attribute; } public override bool Equals(object? obj) { if (obj is AttributeMapping attributeMapping && RegisteredType.Equals(attributeMapping.RegisteredType)) { return Attribute.Equals(attributeMapping.Attribute); } return false; } public override int GetHashCode() { return YamlDotNet.Core.HashCode.CombineHashCodes(RegisteredType.GetHashCode(), Attribute.GetHashCode()); } public int Matches(Type matchType) { int num = 0; Type type = matchType; while (type != null) { num++; if (type == RegisteredType) { return num; } type = type.BaseType(); } if (matchType.GetInterfaces().Contains(RegisteredType)) { return num; } return 0; } } private readonly Dictionary> overrides = new Dictionary>(); [return: MaybeNull] public T GetAttribute(Type type, string member) where T : Attribute { if (overrides.TryGetValue(new AttributeKey(typeof(T), member), out List value)) { int num = 0; AttributeMapping attributeMapping = null; foreach (AttributeMapping item in value) { int num2 = item.Matches(type); if (num2 > num) { num = num2; attributeMapping = item; } } if (num > 0) { return (T)attributeMapping.Attribute; } } return null; } public void Add(Type type, string member, Attribute attribute) { AttributeMapping item = new AttributeMapping(type, attribute); AttributeKey key = new AttributeKey(attribute.GetType(), member); if (!overrides.TryGetValue(key, out List value)) { value = new List(); overrides.Add(key, value); } else if (value.Contains(item)) { throw new InvalidOperationException($"Attribute ({attribute}) already set for Type {type.FullName}, Member {member}"); } value.Add(item); } public YamlAttributeOverrides Clone() { YamlAttributeOverrides yamlAttributeOverrides = new YamlAttributeOverrides(); foreach (KeyValuePair> @override in overrides) { foreach (AttributeMapping item in @override.Value) { yamlAttributeOverrides.Add(item.RegisteredType, @override.Key.PropertyName, item.Attribute); } } return yamlAttributeOverrides; } public void Add(Expression> propertyAccessor, Attribute attribute) { PropertyInfo propertyInfo = propertyAccessor.AsProperty(); Add(typeof(TClass), propertyInfo.Name, attribute); } } internal sealed class YamlAttributeOverridesInspector : ReflectionTypeInspector { public sealed class OverridePropertyDescriptor : IPropertyDescriptor { private readonly IPropertyDescriptor baseDescriptor; private readonly YamlAttributeOverrides overrides; private readonly Type classType; public string Name => baseDescriptor.Name; public bool Required => baseDescriptor.Required; public bool AllowNulls => baseDescriptor.AllowNulls; public bool CanWrite => baseDescriptor.CanWrite; public Type Type => baseDescriptor.Type; public Type? TypeOverride { get { return baseDescriptor.TypeOverride; } set { baseDescriptor.TypeOverride = value; } } public Type? ConverterType => GetCustomAttribute()?.ConverterType ?? baseDescriptor.ConverterType; public int Order { get { return baseDescriptor.Order; } set { baseDescriptor.Order = value; } } public ScalarStyle ScalarStyle { get { return baseDescriptor.ScalarStyle; } set { baseDescriptor.ScalarStyle = value; } } public OverridePropertyDescriptor(IPropertyDescriptor baseDescriptor, YamlAttributeOverrides overrides, Type classType) { this.baseDescriptor = baseDescriptor; this.overrides = overrides; this.classType = classType; } public void Write(object target, object? value) { baseDescriptor.Write(target, value); } public T? GetCustomAttribute() where T : Attribute { T attribute = overrides.GetAttribute(classType, Name); return attribute ?? baseDescriptor.GetCustomAttribute(); } public IObjectDescriptor Read(object target) { return baseDescriptor.Read(target); } } private readonly ITypeInspector innerTypeDescriptor; private readonly YamlAttributeOverrides overrides; public YamlAttributeOverridesInspector(ITypeInspector innerTypeDescriptor, YamlAttributeOverrides overrides) { this.innerTypeDescriptor = innerTypeDescriptor; this.overrides = overrides; } public override IEnumerable GetProperties(Type type, object? container) { IEnumerable enumerable = innerTypeDescriptor.GetProperties(type, container); if (overrides != null) { enumerable = enumerable.Select((Func)((IPropertyDescriptor p) => new OverridePropertyDescriptor(p, overrides, type))); } return enumerable; } } internal sealed class YamlAttributesTypeInspector : TypeInspectorSkeleton { private readonly ITypeInspector innerTypeDescriptor; public YamlAttributesTypeInspector(ITypeInspector innerTypeDescriptor) { this.innerTypeDescriptor = innerTypeDescriptor; } public override string GetEnumName(Type enumType, string name) { return innerTypeDescriptor.GetEnumName(enumType, name); } public override string GetEnumValue(object enumValue) { return innerTypeDescriptor.GetEnumValue(enumValue); } public override IEnumerable GetProperties(Type type, object? container) { return from p in (from p in innerTypeDescriptor.GetProperties(type, container) where p.GetCustomAttribute() == null select p).Select((Func)delegate(IPropertyDescriptor p) { PropertyDescriptor propertyDescriptor = new PropertyDescriptor(p); YamlMemberAttribute customAttribute = p.GetCustomAttribute(); if (customAttribute != null) { if (customAttribute.SerializeAs != null) { propertyDescriptor.TypeOverride = customAttribute.SerializeAs; } propertyDescriptor.Order = customAttribute.Order; propertyDescriptor.ScalarStyle = customAttribute.ScalarStyle; if (customAttribute.Alias != null) { propertyDescriptor.Name = customAttribute.Alias; } } return propertyDescriptor; }) orderby p.Order select p; } } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] internal sealed class YamlConverterAttribute : Attribute { public Type ConverterType { get; } public YamlConverterAttribute(Type converterType) { ConverterType = converterType; } } internal class YamlFormatter { public static YamlFormatter Default { get; } = new YamlFormatter(); public NumberFormatInfo NumberFormat { get; set; } = new NumberFormatInfo { CurrencyDecimalSeparator = ".", CurrencyGroupSeparator = "_", CurrencyGroupSizes = new int[1] { 3 }, CurrencySymbol = string.Empty, CurrencyDecimalDigits = 99, NumberDecimalSeparator = ".", NumberGroupSeparator = "_", NumberGroupSizes = new int[1] { 3 }, NumberDecimalDigits = 99, NaNSymbol = ".nan", PositiveInfinitySymbol = ".inf", NegativeInfinitySymbol = "-.inf" }; public virtual Func FormatEnum { get; set; } = delegate(object value, ITypeInspector typeInspector, INamingConvention enumNamingConvention) { string empty = string.Empty; empty = ((value != null) ? typeInspector.GetEnumValue(value) : string.Empty); return enumNamingConvention.Apply(empty); }; public virtual Func PotentiallyQuoteEnums { get; set; } = (object _) => true; public string FormatNumber(object number) { return Convert.ToString(number, NumberFormat); } public string FormatNumber(double number) { return number.ToString("G", NumberFormat); } public string FormatNumber(float number) { return number.ToString("G", NumberFormat); } public string FormatBoolean(object boolean) { if (!boolean.Equals(true)) { return "false"; } return "true"; } public string FormatDateTime(object dateTime) { return ((DateTime)dateTime).ToString("o", CultureInfo.InvariantCulture); } public string FormatTimeSpan(object timeSpan) { return ((TimeSpan)timeSpan/*cast due to .constrained prefix*/).ToString(); } } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] internal sealed class YamlIgnoreAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] internal sealed class YamlMemberAttribute : Attribute { private DefaultValuesHandling? defaultValuesHandling; public string? Description { get; set; } public Type? SerializeAs { get; set; } public int Order { get; set; } public string? Alias { get; set; } public bool ApplyNamingConventions { get; set; } public ScalarStyle ScalarStyle { get; set; } public DefaultValuesHandling DefaultValuesHandling { get { return defaultValuesHandling.GetValueOrDefault(); } set { defaultValuesHandling = value; } } public bool IsDefaultValuesHandlingSpecified => defaultValuesHandling.HasValue; public YamlMemberAttribute() { ScalarStyle = ScalarStyle.Any; ApplyNamingConventions = true; } public YamlMemberAttribute(Type serializeAs) : this() { SerializeAs = serializeAs ?? throw new ArgumentNullException("serializeAs"); } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum, Inherited = false, AllowMultiple = true)] internal sealed class YamlSerializableAttribute : Attribute { public YamlSerializableAttribute() { } public YamlSerializableAttribute(Type serializableType) { } } [AttributeUsage(AttributeTargets.Class)] internal sealed class YamlStaticContextAttribute : Attribute { } } namespace YamlDotNet.Serialization.ValueDeserializers { internal sealed class AliasValueDeserializer : IValueDeserializer { private sealed class AliasState : Dictionary, IPostDeserializationCallback { public void OnDeserialization() { foreach (ValuePromise value in base.Values) { if (!value.HasValue) { YamlDotNet.Core.Events.AnchorAlias alias = value.Alias; throw new AnchorNotFoundException(alias.Start, alias.End, $"Anchor '{alias.Value}' not found"); } } } } private sealed class ValuePromise : IValuePromise { private object? value; public readonly YamlDotNet.Core.Events.AnchorAlias? Alias; public bool HasValue { get; private set; } public object? Value { get { if (!HasValue) { throw new InvalidOperationException("Value not set"); } return value; } set { if (HasValue) { throw new InvalidOperationException("Value already set"); } HasValue = true; this.value = value; this.ValueAvailable?.Invoke(value); } } public event Action? ValueAvailable; public ValuePromise(YamlDotNet.Core.Events.AnchorAlias alias) { Alias = alias; } public ValuePromise(object? value) { HasValue = true; this.value = value; } } private readonly IValueDeserializer innerDeserializer; public AliasValueDeserializer(IValueDeserializer innerDeserializer) { this.innerDeserializer = innerDeserializer ?? throw new ArgumentNullException("innerDeserializer"); } public object? DeserializeValue(IParser parser, Type expectedType, SerializerState state, IValueDeserializer nestedObjectDeserializer) { if (parser.TryConsume(out var @event)) { AliasState aliasState = state.Get(); if (!aliasState.TryGetValue(@event.Value, out ValuePromise value)) { throw new AnchorNotFoundException(@event.Start, @event.End, $"Alias ${@event.Value} cannot precede anchor declaration"); } if (!value.HasValue) { return value; } return value.Value; } AnchorName anchorName = AnchorName.Empty; if (parser.Accept(out var event2) && !event2.Anchor.IsEmpty) { anchorName = event2.Anchor; AliasState aliasState2 = state.Get(); if (!aliasState2.ContainsKey(anchorName)) { aliasState2[anchorName] = new ValuePromise(new YamlDotNet.Core.Events.AnchorAlias(anchorName)); } } object obj = innerDeserializer.DeserializeValue(parser, expectedType, state, nestedObjectDeserializer); if (!anchorName.IsEmpty) { AliasState aliasState3 = state.Get(); if (!aliasState3.TryGetValue(anchorName, out ValuePromise value2)) { aliasState3.Add(anchorName, new ValuePromise(obj)); } else if (!value2.HasValue) { value2.Value = obj; } else { aliasState3[anchorName] = new ValuePromise(obj); } } return obj; } } internal sealed class NodeValueDeserializer : IValueDeserializer { private readonly IList deserializers; private readonly IList typeResolvers; private readonly ITypeConverter typeConverter; private readonly INamingConvention enumNamingConvention; private readonly ITypeInspector typeInspector; public NodeValueDeserializer(IList deserializers, IList typeResolvers, ITypeConverter typeConverter, INamingConvention enumNamingConvention, ITypeInspector typeInspector) { this.deserializers = deserializers ?? throw new ArgumentNullException("deserializers"); this.typeResolvers = typeResolvers ?? throw new ArgumentNullException("typeResolvers"); this.typeConverter = typeConverter ?? throw new ArgumentNullException("typeConverter"); this.enumNamingConvention = enumNamingConvention ?? throw new ArgumentNullException("enumNamingConvention"); this.typeInspector = typeInspector; } public object? DeserializeValue(IParser parser, Type expectedType, SerializerState state, IValueDeserializer nestedObjectDeserializer) { parser.Accept(out var @event); Type typeFromEvent = GetTypeFromEvent(@event, expectedType); ObjectDeserializer rootDeserializer = (Type x) => DeserializeValue(parser, x, state, nestedObjectDeserializer); try { foreach (INodeDeserializer deserializer in deserializers) { if (deserializer.Deserialize(parser, typeFromEvent, (IParser r, Type t) => nestedObjectDeserializer.DeserializeValue(r, t, state, nestedObjectDeserializer), out object value, rootDeserializer)) { return typeConverter.ChangeType(value, expectedType, enumNamingConvention, typeInspector); } } } catch (YamlException) { throw; } catch (Exception innerException) { throw new YamlException(@event?.Start ?? Mark.Empty, @event?.End ?? Mark.Empty, "Exception during deserialization", innerException); } throw new YamlException(@event?.Start ?? Mark.Empty, @event?.End ?? Mark.Empty, "No node deserializer was able to deserialize the node into type " + expectedType.AssemblyQualifiedName); } private Type GetTypeFromEvent(NodeEvent? nodeEvent, Type currentType) { foreach (INodeTypeResolver typeResolver in typeResolvers) { if (typeResolver.Resolve(nodeEvent, ref currentType)) { break; } } return currentType; } } } namespace YamlDotNet.Serialization.Utilities { internal interface IPostDeserializationCallback { void OnDeserialization(); } internal interface ITypeConverter { object? ChangeType(object? value, Type expectedType, INamingConvention enumNamingConvention, ITypeInspector typeInspector); } internal class NullTypeConverter : ITypeConverter { public object? ChangeType(object? value, Type expectedType, INamingConvention enumNamingConvention, ITypeInspector typeInspector) { return value; } } internal sealed class ObjectAnchorCollection { private readonly Dictionary objectsByAnchor = new Dictionary(); private readonly Dictionary anchorsByObject = new Dictionary(); public object this[string anchor] { get { if (objectsByAnchor.TryGetValue(anchor, out object value)) { return value; } throw new AnchorNotFoundException("The anchor '" + anchor + "' does not exists"); } } public void Add(string anchor, object @object) { objectsByAnchor.Add(anchor, @object); if (@object != null) { anchorsByObject.Add(@object, anchor); } } public bool TryGetAnchor(object @object, [MaybeNullWhen(false)] out string? anchor) { return anchorsByObject.TryGetValue(@object, out anchor); } } internal class ReflectionTypeConverter : ITypeConverter { public object? ChangeType(object? value, Type expectedType, ITypeInspector typeInspector) { return ChangeType(value, expectedType, NullNamingConvention.Instance, typeInspector); } public object? ChangeType(object? value, Type expectedType, INamingConvention enumNamingConvention, ITypeInspector typeInspector) { return TypeConverter.ChangeType(value, expectedType, enumNamingConvention, typeInspector); } } internal sealed class SerializerState : IDisposable { private readonly Dictionary items = new Dictionary(); public T Get() where T : class, new() { if (!items.TryGetValue(typeof(T), out object value)) { value = new T(); items.Add(typeof(T), value); } return (T)value; } public void OnDeserialization() { foreach (IPostDeserializationCallback item in items.Values.OfType()) { item.OnDeserialization(); } } public void Dispose() { foreach (IDisposable item in items.Values.OfType()) { item.Dispose(); } } } internal static class StringExtensions { private static string ToCamelOrPascalCase(string str, Func firstLetterTransform) { string text = Regex.Replace(str, "([_\\-])(?[a-z])", (Match match) => match.Groups["char"].Value.ToUpperInvariant(), RegexOptions.IgnoreCase); return firstLetterTransform(text[0]) + text.Substring(1); } public static string ToCamelCase(this string str) { return ToCamelOrPascalCase(str, char.ToLowerInvariant); } public static string ToPascalCase(this string str) { return ToCamelOrPascalCase(str, char.ToUpperInvariant); } public static string FromCamelCase(this string str, string separator) { str = char.ToLower(str[0], CultureInfo.InvariantCulture) + str.Substring(1); str = Regex.Replace(str.ToCamelCase(), "(?[A-Z])", (Match match) => separator + match.Groups["char"].Value.ToLowerInvariant()); return str; } } internal static class TypeConverter { public static T ChangeType(object? value, INamingConvention enumNamingConvention, ITypeInspector typeInspector) { return (T)ChangeType(value, typeof(T), enumNamingConvention, typeInspector); } public static object? ChangeType(object? value, Type destinationType, INamingConvention enumNamingConvention, ITypeInspector typeInspector) { return ChangeType(value, destinationType, CultureInfo.InvariantCulture, enumNamingConvention, typeInspector); } public static object? ChangeType(object? value, Type destinationType, IFormatProvider provider, INamingConvention enumNamingConvention, ITypeInspector typeInspector) { return ChangeType(value, destinationType, new CultureInfoAdapter(CultureInfo.CurrentCulture, provider), enumNamingConvention, typeInspector); } public static object? ChangeType(object? value, Type destinationType, CultureInfo culture, INamingConvention enumNamingConvention, ITypeInspector typeInspector) { if (value == null || value.IsDbNull()) { if (!destinationType.IsValueType()) { return null; } return Activator.CreateInstance(destinationType); } Type type = value.GetType(); if (destinationType == type || destinationType.IsAssignableFrom(type)) { return value; } if (destinationType.IsGenericType()) { Type genericTypeDefinition = destinationType.GetGenericTypeDefinition(); if (genericTypeDefinition == typeof(Nullable<>) || FsharpHelper.IsOptionType(genericTypeDefinition)) { Type destinationType2 = destinationType.GetGenericArguments()[0]; object obj = ChangeType(value, destinationType2, culture, enumNamingConvention, typeInspector); return Activator.CreateInstance(destinationType, obj); } } if (destinationType.IsEnum()) { object result = value; if (value is string value2) { string name = enumNamingConvention.Reverse(value2); name = typeInspector.GetEnumName(destinationType, name); result = Enum.Parse(destinationType, name, ignoreCase: true); } return result; } if (destinationType == typeof(bool)) { if ("0".Equals(value)) { return false; } if ("1".Equals(value)) { return true; } } System.ComponentModel.TypeConverter converter = TypeDescriptor.GetConverter(type); if (converter != null && converter.CanConvertTo(destinationType)) { return converter.ConvertTo(null, culture, value, destinationType); } System.ComponentModel.TypeConverter converter2 = TypeDescriptor.GetConverter(destinationType); if (converter2 != null && converter2.CanConvertFrom(type)) { return converter2.ConvertFrom(null, culture, value); } Type[] array = new Type[2] { type, destinationType }; foreach (Type type2 in array) { foreach (MethodInfo publicStaticMethod2 in type2.GetPublicStaticMethods()) { if (!publicStaticMethod2.IsSpecialName || (!(publicStaticMethod2.Name == "op_Implicit") && !(publicStaticMethod2.Name == "op_Explicit")) || !destinationType.IsAssignableFrom(publicStaticMethod2.ReturnParameter.ParameterType)) { continue; } ParameterInfo[] parameters = publicStaticMethod2.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType.IsAssignableFrom(type)) { try { return publicStaticMethod2.Invoke(null, new object[1] { value }); } catch (TargetInvocationException ex) { throw ex.InnerException; } } } } if (type == typeof(string)) { try { MethodInfo publicStaticMethod = destinationType.GetPublicStaticMethod("Parse", typeof(string), typeof(IFormatProvider)); if (publicStaticMethod != null) { return publicStaticMethod.Invoke(null, new object[2] { value, culture }); } publicStaticMethod = destinationType.GetPublicStaticMethod("Parse", typeof(string)); if (publicStaticMethod != null) { return publicStaticMethod.Invoke(null, new object[1] { value }); } } catch (TargetInvocationException ex2) { throw ex2.InnerException; } } if (destinationType == typeof(TimeSpan)) { return TimeSpan.Parse((string)ChangeType(value, typeof(string), CultureInfo.InvariantCulture, enumNamingConvention, typeInspector), CultureInfo.InvariantCulture); } return Convert.ChangeType(value, destinationType, CultureInfo.InvariantCulture); } public static void RegisterTypeConverter() where TConverter : System.ComponentModel.TypeConverter { if (!TypeDescriptor.GetAttributes(typeof(TConvertible)).OfType().Any((TypeConverterAttribute a) => a.ConverterTypeName == typeof(TConverter).AssemblyQualifiedName)) { TypeDescriptor.AddAttributes(typeof(TConvertible), new TypeConverterAttribute(typeof(TConverter))); } } } internal sealed class TypeConverterCache { private readonly IYamlTypeConverter[] typeConverters; private readonly ConcurrentDictionary cache = new ConcurrentDictionary(); public TypeConverterCache(IEnumerable? typeConverters) : this(typeConverters?.ToArray() ?? Array.Empty()) { } public TypeConverterCache(IYamlTypeConverter[] typeConverters) { this.typeConverters = typeConverters; } public bool TryGetConverterForType(Type type, [NotNullWhen(true)] out IYamlTypeConverter? typeConverter) { (bool, IYamlTypeConverter) orAdd = DictionaryExtensions.GetOrAdd(cache, type, (Type t, IYamlTypeConverter[] tc) => LookupTypeConverter(t, tc), typeConverters); typeConverter = orAdd.Item2; return orAdd.Item1; } public IYamlTypeConverter GetConverterByType(Type converter) { IYamlTypeConverter[] array = typeConverters; foreach (IYamlTypeConverter yamlTypeConverter in array) { if (yamlTypeConverter.GetType() == converter) { return yamlTypeConverter; } } throw new ArgumentException("IYamlTypeConverter of type " + converter.FullName + " not found", "converter"); } private static (bool HasMatch, IYamlTypeConverter? TypeConverter) LookupTypeConverter(Type type, IYamlTypeConverter[] typeConverters) { foreach (IYamlTypeConverter yamlTypeConverter in typeConverters) { if (yamlTypeConverter.Accepts(type)) { return (HasMatch: true, TypeConverter: yamlTypeConverter); } } return (HasMatch: false, TypeConverter: null); } } } namespace YamlDotNet.Serialization.TypeResolvers { internal sealed class DynamicTypeResolver : ITypeResolver { public Type Resolve(Type staticType, object? actualValue) { if (actualValue == null) { return staticType; } return actualValue.GetType(); } } internal class StaticTypeResolver : ITypeResolver { public virtual Type Resolve(Type staticType, object? actualValue) { if (actualValue != null) { if (actualValue.GetType().IsEnum) { return staticType; } switch (actualValue.GetType().GetTypeCode()) { case TypeCode.Boolean: return typeof(bool); case TypeCode.Char: return typeof(char); case TypeCode.SByte: return typeof(sbyte); case TypeCode.Byte: return typeof(byte); case TypeCode.Int16: return typeof(short); case TypeCode.UInt16: return typeof(ushort); case TypeCode.Int32: return typeof(int); case TypeCode.UInt32: return typeof(uint); case TypeCode.Int64: return typeof(long); case TypeCode.UInt64: return typeof(ulong); case TypeCode.Single: return typeof(float); case TypeCode.Double: return typeof(double); case TypeCode.Decimal: return typeof(decimal); case TypeCode.String: return typeof(string); case TypeCode.DateTime: return typeof(DateTime); } } return staticType; } } } namespace YamlDotNet.Serialization.TypeInspectors { internal class CachedTypeInspector : TypeInspectorSkeleton { private readonly ITypeInspector innerTypeDescriptor; private readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); private readonly ConcurrentDictionary> enumNameCache = new ConcurrentDictionary>(); private readonly ConcurrentDictionary enumValueCache = new ConcurrentDictionary(); public CachedTypeInspector(ITypeInspector innerTypeDescriptor) { this.innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor"); } public override string GetEnumName(Type enumType, string name) { ConcurrentDictionary orAdd = enumNameCache.GetOrAdd(enumType, (Type _) => new ConcurrentDictionary()); return DictionaryExtensions.GetOrAdd(orAdd, name, delegate(string n, (Type enumType, ITypeInspector innerTypeDescriptor) context) { var (enumType2, typeInspector) = context; return typeInspector.GetEnumName(enumType2, n); }, (enumType, innerTypeDescriptor)); } public override string GetEnumValue(object enumValue) { return DictionaryExtensions.GetOrAdd(enumValueCache, enumValue, delegate(object _, (object enumValue, ITypeInspector innerTypeDescriptor) context) { var (enumValue2, typeInspector) = context; return typeInspector.GetEnumValue(enumValue2); }, (enumValue, innerTypeDescriptor)); } public override IEnumerable GetProperties(Type type, object? container) { return DictionaryExtensions.GetOrAdd(cache, type, delegate(Type t, (object container, ITypeInspector innerTypeDescriptor) context) { var (container2, typeInspector) = context; return typeInspector.GetProperties(t, container2).ToList(); }, (container, innerTypeDescriptor)); } } internal class CompositeTypeInspector : TypeInspectorSkeleton { private readonly IEnumerable typeInspectors; public CompositeTypeInspector(params ITypeInspector[] typeInspectors) : this((IEnumerable)typeInspectors) { } public CompositeTypeInspector(IEnumerable typeInspectors) { this.typeInspectors = typeInspectors?.ToList() ?? throw new ArgumentNullException("typeInspectors"); } public override string GetEnumName(Type enumType, string name) { foreach (ITypeInspector typeInspector in typeInspectors) { try { return typeInspector.GetEnumName(enumType, name); } catch { } } throw new ArgumentOutOfRangeException("enumType,name", "Name not found on enum type"); } public override string GetEnumValue(object enumValue) { if (enumValue == null) { throw new ArgumentNullException("enumValue"); } foreach (ITypeInspector typeInspector in typeInspectors) { try { return typeInspector.GetEnumValue(enumValue); } catch { } } throw new ArgumentOutOfRangeException("enumValue", $"Value not found for ({enumValue})"); } public override IEnumerable GetProperties(Type type, object? container) { return typeInspectors.SelectMany((ITypeInspector i) => i.GetProperties(type, container)); } } internal class NamingConventionTypeInspector : TypeInspectorSkeleton { private readonly ITypeInspector innerTypeDescriptor; private readonly INamingConvention namingConvention; public NamingConventionTypeInspector(ITypeInspector innerTypeDescriptor, INamingConvention namingConvention) { this.innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor"); this.namingConvention = namingConvention ?? throw new ArgumentNullException("namingConvention"); } public override string GetEnumName(Type enumType, string name) { return innerTypeDescriptor.GetEnumName(enumType, name); } public override string GetEnumValue(object enumValue) { return innerTypeDescriptor.GetEnumValue(enumValue); } public override IEnumerable GetProperties(Type type, object? container) { return innerTypeDescriptor.GetProperties(type, container).Select(delegate(IPropertyDescriptor p) { YamlMemberAttribute customAttribute = p.GetCustomAttribute(); return (customAttribute != null && !customAttribute.ApplyNamingConventions) ? p : new PropertyDescriptor(p) { Name = namingConvention.Apply(p.Name) }; }); } } internal class ReadableAndWritablePropertiesTypeInspector : TypeInspectorSkeleton { private readonly ITypeInspector innerTypeDescriptor; public ReadableAndWritablePropertiesTypeInspector(ITypeInspector innerTypeDescriptor) { this.innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor"); } public override string GetEnumName(Type enumType, string name) { return innerTypeDescriptor.GetEnumName(enumType, name); } public override string GetEnumValue(object enumValue) { return innerTypeDescriptor.GetEnumValue(enumValue); } public override IEnumerable GetProperties(Type type, object? container) { return from p in innerTypeDescriptor.GetProperties(type, container) where p.CanWrite select p; } } internal class ReadableFieldsTypeInspector : ReflectionTypeInspector { protected class ReflectionFieldDescriptor : IPropertyDescriptor { private readonly FieldInfo fieldInfo; private readonly ITypeResolver typeResolver; public string Name => fieldInfo.Name; public bool Required => fieldInfo.IsRequired(); public Type Type => fieldInfo.FieldType; public Type? ConverterType { get; } public Type? TypeOverride { get; set; } public bool AllowNulls => fieldInfo.AcceptsNull(); public int Order { get; set; } public bool CanWrite => !fieldInfo.IsInitOnly; public ScalarStyle ScalarStyle { get; set; } public ReflectionFieldDescriptor(FieldInfo fieldInfo, ITypeResolver typeResolver) { this.fieldInfo = fieldInfo; this.typeResolver = typeResolver; YamlConverterAttribute customAttribute = fieldInfo.GetCustomAttribute(); if (customAttribute != null) { ConverterType = customAttribute.ConverterType; } ScalarStyle = ScalarStyle.Any; } public void Write(object target, object? value) { fieldInfo.SetValue(target, value); } public T? GetCustomAttribute() where T : Attribute { object[] customAttributes = fieldInfo.GetCustomAttributes(typeof(T), inherit: true); return (T)customAttributes.FirstOrDefault(); } public IObjectDescriptor Read(object target) { object value = fieldInfo.GetValue(target); Type type = TypeOverride ?? typeResolver.Resolve(Type, value); return new ObjectDescriptor(value, type, Type, ScalarStyle); } } private readonly ITypeResolver typeResolver; public ReadableFieldsTypeInspector(ITypeResolver typeResolver) { this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); } public override IEnumerable GetProperties(Type type, object? container) { return type.GetPublicFields().Select((Func)((FieldInfo p) => new ReflectionFieldDescriptor(p, typeResolver))); } } internal class ReadablePropertiesTypeInspector : ReflectionTypeInspector { protected class ReflectionPropertyDescriptor : IPropertyDescriptor { private readonly PropertyInfo propertyInfo; private readonly ITypeResolver typeResolver; public string Name => propertyInfo.Name; public bool Required => propertyInfo.IsRequired(); public Type Type => propertyInfo.PropertyType; public Type? TypeOverride { get; set; } public Type? ConverterType { get; set; } public bool AllowNulls => propertyInfo.AcceptsNull(); public int Order { get; set; } public bool CanWrite => propertyInfo.CanWrite; public ScalarStyle ScalarStyle { get; set; } public ReflectionPropertyDescriptor(PropertyInfo propertyInfo, ITypeResolver typeResolver) { this.propertyInfo = propertyInfo ?? throw new ArgumentNullException("propertyInfo"); this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); ScalarStyle = ScalarStyle.Any; YamlConverterAttribute customAttribute = propertyInfo.GetCustomAttribute(); if (customAttribute != null) { ConverterType = customAttribute.ConverterType; } } public void Write(object target, object? value) { propertyInfo.SetValue(target, value, null); } public T? GetCustomAttribute() where T : Attribute { Attribute[] allCustomAttributes = propertyInfo.GetAllCustomAttributes(); return (T)allCustomAttributes.FirstOrDefault(); } public IObjectDescriptor Read(object target) { object obj = propertyInfo.ReadValue(target); Type type = TypeOverride ?? typeResolver.Resolve(Type, obj); return new ObjectDescriptor(obj, type, Type, ScalarStyle); } } private readonly ITypeResolver typeResolver; private readonly bool includeNonPublicProperties; public ReadablePropertiesTypeInspector(ITypeResolver typeResolver) : this(typeResolver, includeNonPublicProperties: false) { } public ReadablePropertiesTypeInspector(ITypeResolver typeResolver, bool includeNonPublicProperties) { this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); this.includeNonPublicProperties = includeNonPublicProperties; } private static bool IsValidProperty(PropertyInfo property) { if (property.CanRead) { return property.GetGetMethod(nonPublic: true).GetParameters().Length == 0; } return false; } public override IEnumerable GetProperties(Type type, object? container) { return type.GetProperties(includeNonPublicProperties).Where(IsValidProperty).Select((Func)((PropertyInfo p) => new ReflectionPropertyDescriptor(p, typeResolver))); } } internal abstract class ReflectionTypeInspector : TypeInspectorSkeleton { public override string GetEnumName(Type enumType, string name) { return name; } public override string GetEnumValue(object enumValue) { if (enumValue == null) { return string.Empty; } return enumValue.ToString(); } } internal abstract class TypeInspectorSkeleton : ITypeInspector { public abstract string GetEnumName(Type enumType, string name); public abstract string GetEnumValue(object enumValue); public abstract IEnumerable GetProperties(Type type, object? container); public IPropertyDescriptor GetProperty(Type type, object? container, string name, [MaybeNullWhen(true)] bool ignoreUnmatched, bool caseInsensitivePropertyMatching) { IEnumerable enumerable = ((!caseInsensitivePropertyMatching) ? (from p in GetProperties(type, container) where p.Name == name select p) : (from p in GetProperties(type, container) where p.Name.Equals(name, StringComparison.OrdinalIgnoreCase) select p)); using IEnumerator enumerator = enumerable.GetEnumerator(); if (!enumerator.MoveNext()) { if (ignoreUnmatched) { return null; } throw new SerializationException("Property '" + name + "' not found on type '" + type.FullName + "'."); } IPropertyDescriptor current = enumerator.Current; if (enumerator.MoveNext()) { throw new SerializationException("Multiple properties with the name/alias '" + name + "' already exists on type '" + type.FullName + "', maybe you're misusing YamlAlias or maybe you are using the wrong naming convention? The matching properties are: " + string.Join(", ", enumerable.Select((IPropertyDescriptor p) => p.Name).ToArray())); } return current; } } internal class WritablePropertiesTypeInspector : ReflectionTypeInspector { protected class ReflectionPropertyDescriptor : IPropertyDescriptor { private readonly PropertyInfo propertyInfo; private readonly ITypeResolver typeResolver; public string Name => propertyInfo.Name; public bool Required => propertyInfo.IsRequired(); public Type Type => propertyInfo.PropertyType; public Type? TypeOverride { get; set; } public Type? ConverterType { get; set; } public bool AllowNulls => propertyInfo.AcceptsNull(); public int Order { get; set; } public bool CanWrite => propertyInfo.CanWrite; public ScalarStyle ScalarStyle { get; set; } public ReflectionPropertyDescriptor(PropertyInfo propertyInfo, ITypeResolver typeResolver) { this.propertyInfo = propertyInfo ?? throw new ArgumentNullException("propertyInfo"); this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); ScalarStyle = ScalarStyle.Any; YamlConverterAttribute customAttribute = propertyInfo.GetCustomAttribute(); if (customAttribute != null) { ConverterType = customAttribute.ConverterType; } } public void Write(object target, object? value) { propertyInfo.SetValue(target, value, null); } public T? GetCustomAttribute() where T : Attribute { Attribute[] allCustomAttributes = propertyInfo.GetAllCustomAttributes(); return (T)allCustomAttributes.FirstOrDefault(); } public IObjectDescriptor Read(object target) { object obj = propertyInfo.ReadValue(target); Type type = TypeOverride ?? typeResolver.Resolve(Type, obj); return new ObjectDescriptor(obj, type, Type, ScalarStyle); } } private readonly ITypeResolver typeResolver; private readonly bool includeNonPublicProperties; public WritablePropertiesTypeInspector(ITypeResolver typeResolver) : this(typeResolver, includeNonPublicProperties: false) { } public WritablePropertiesTypeInspector(ITypeResolver typeResolver, bool includeNonPublicProperties) { this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); this.includeNonPublicProperties = includeNonPublicProperties; } private static bool IsValidProperty(PropertyInfo property) { if (property.CanWrite) { return property.GetSetMethod(nonPublic: true).GetParameters().Length == 1; } return false; } public override IEnumerable GetProperties(Type type, object? container) { return type.GetProperties(includeNonPublicProperties).Where(IsValidProperty).Select((Func)((PropertyInfo p) => new ReflectionPropertyDescriptor(p, typeResolver))) .ToArray(); } } } namespace YamlDotNet.Serialization.Schemas { internal sealed class FailsafeSchema { public static class Tags { public static readonly TagName Map = new TagName("tag:yaml.org,2002:map"); public static readonly TagName Seq = new TagName("tag:yaml.org,2002:seq"); public static readonly TagName Str = new TagName("tag:yaml.org,2002:str"); } } internal sealed class JsonSchema { public static class Tags { public static readonly TagName Null = new TagName("tag:yaml.org,2002:null"); public static readonly TagName Bool = new TagName("tag:yaml.org,2002:bool"); public static readonly TagName Int = new TagName("tag:yaml.org,2002:int"); public static readonly TagName Float = new TagName("tag:yaml.org,2002:float"); } } internal sealed class CoreSchema { public static class Tags { } } internal sealed class DefaultSchema { public static class Tags { public static readonly TagName Timestamp = new TagName("tag:yaml.org,2002:timestamp"); } } } namespace YamlDotNet.Serialization.ObjectGraphVisitors { internal sealed class AnchorAssigner : PreProcessingPhaseObjectGraphVisitorSkeleton, IAliasProvider { private class AnchorAssignment { public AnchorName Anchor; } private readonly Dictionary assignments = new Dictionary(); private uint nextId; public AnchorAssigner(IEnumerable typeConverters) : base(typeConverters) { } protected override bool Enter(IObjectDescriptor value, ObjectSerializer serializer) { if (value.Value != null && assignments.TryGetValue(value.Value, out AnchorAssignment value2)) { if (value2.Anchor.IsEmpty) { value2.Anchor = new AnchorName("o" + nextId.ToString(CultureInfo.InvariantCulture)); nextId++; } return false; } return true; } protected override bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value, ObjectSerializer serializer) { return true; } protected override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, ObjectSerializer serializer) { return true; } protected override void VisitScalar(IObjectDescriptor scalar, ObjectSerializer serializer) { } protected override void VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, ObjectSerializer serializer) { VisitObject(mapping); } protected override void VisitMappingEnd(IObjectDescriptor mapping, ObjectSerializer serializer) { } protected override void VisitSequenceStart(IObjectDescriptor sequence, Type elementType, ObjectSerializer serializer) { VisitObject(sequence); } protected override void VisitSequenceEnd(IObjectDescriptor sequence, ObjectSerializer serializer) { } private void VisitObject(IObjectDescriptor value) { if (value.Value != null) { assignments.Add(value.Value, new AnchorAssignment()); } } AnchorName IAliasProvider.GetAlias(object target) { if (target != null && assignments.TryGetValue(target, out AnchorAssignment value)) { return value.Anchor; } return AnchorName.Empty; } } internal sealed class AnchorAssigningObjectGraphVisitor : ChainedObjectGraphVisitor { private readonly IEventEmitter eventEmitter; private readonly IAliasProvider aliasProvider; private readonly HashSet emittedAliases = new HashSet(); public AnchorAssigningObjectGraphVisitor(IObjectGraphVisitor nextVisitor, IEventEmitter eventEmitter, IAliasProvider aliasProvider) : base(nextVisitor) { this.eventEmitter = eventEmitter; this.aliasProvider = aliasProvider; } public override bool Enter(IPropertyDescriptor? propertyDescriptor, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { if (value.Value != null) { AnchorName alias = aliasProvider.GetAlias(value.Value); if (!alias.IsEmpty && !emittedAliases.Add(alias)) { AliasEventInfo aliasEventInfo = new AliasEventInfo(value, alias); eventEmitter.Emit(aliasEventInfo, context); return aliasEventInfo.NeedsExpansion; } } return base.Enter(propertyDescriptor, value, context, serializer); } public override void VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, IEmitter context, ObjectSerializer serializer) { AnchorName alias = aliasProvider.GetAlias(mapping.NonNullValue()); eventEmitter.Emit(new MappingStartEventInfo(mapping) { Anchor = alias }, context); } public override void VisitSequenceStart(IObjectDescriptor sequence, Type elementType, IEmitter context, ObjectSerializer serializer) { AnchorName alias = aliasProvider.GetAlias(sequence.NonNullValue()); eventEmitter.Emit(new SequenceStartEventInfo(sequence) { Anchor = alias }, context); } public override void VisitScalar(IObjectDescriptor scalar, IEmitter context, ObjectSerializer serializer) { ScalarEventInfo scalarEventInfo = new ScalarEventInfo(scalar); if (scalar.Value != null) { scalarEventInfo.Anchor = aliasProvider.GetAlias(scalar.Value); } eventEmitter.Emit(scalarEventInfo, context); } } internal abstract class ChainedObjectGraphVisitor : IObjectGraphVisitor { private readonly IObjectGraphVisitor nextVisitor; protected ChainedObjectGraphVisitor(IObjectGraphVisitor nextVisitor) { this.nextVisitor = nextVisitor; } public virtual bool Enter(IPropertyDescriptor? propertyDescriptor, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { return nextVisitor.Enter(propertyDescriptor, value, context, serializer); } public virtual bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { return nextVisitor.EnterMapping(key, value, context, serializer); } public virtual bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { return nextVisitor.EnterMapping(key, value, context, serializer); } public virtual void VisitScalar(IObjectDescriptor scalar, IEmitter context, ObjectSerializer serializer) { nextVisitor.VisitScalar(scalar, context, serializer); } public virtual void VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, IEmitter context, ObjectSerializer serializer) { nextVisitor.VisitMappingStart(mapping, keyType, valueType, context, serializer); } public virtual void VisitMappingEnd(IObjectDescriptor mapping, IEmitter context, ObjectSerializer serializer) { nextVisitor.VisitMappingEnd(mapping, context, serializer); } public virtual void VisitSequenceStart(IObjectDescriptor sequence, Type elementType, IEmitter context, ObjectSerializer serializer) { nextVisitor.VisitSequenceStart(sequence, elementType, context, serializer); } public virtual void VisitSequenceEnd(IObjectDescriptor sequence, IEmitter context, ObjectSerializer serializer) { nextVisitor.VisitSequenceEnd(sequence, context, serializer); } } internal sealed class CommentsObjectGraphVisitor : ChainedObjectGraphVisitor { public CommentsObjectGraphVisitor(IObjectGraphVisitor nextVisitor) : base(nextVisitor) { } public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { YamlMemberAttribute customAttribute = key.GetCustomAttribute(); if (customAttribute != null && customAttribute.Description != null) { context.Emit(new YamlDotNet.Core.Events.Comment(customAttribute.Description, isInline: false)); } return base.EnterMapping(key, value, context, serializer); } } internal sealed class CustomSerializationObjectGraphVisitor : ChainedObjectGraphVisitor { private readonly TypeConverterCache typeConverters; private readonly ObjectSerializer nestedObjectSerializer; public CustomSerializationObjectGraphVisitor(IObjectGraphVisitor nextVisitor, IEnumerable typeConverters, ObjectSerializer nestedObjectSerializer) : base(nextVisitor) { this.typeConverters = new TypeConverterCache(typeConverters); this.nestedObjectSerializer = nestedObjectSerializer; } public override bool Enter(IPropertyDescriptor? propertyDescriptor, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { if (propertyDescriptor?.ConverterType != null) { IYamlTypeConverter converterByType = typeConverters.GetConverterByType(propertyDescriptor.ConverterType); converterByType.WriteYaml(context, value.Value, value.Type, serializer); return false; } if (typeConverters.TryGetConverterForType(value.Type, out IYamlTypeConverter typeConverter)) { typeConverter.WriteYaml(context, value.Value, value.Type, serializer); return false; } if (value.Value is IYamlConvertible yamlConvertible) { yamlConvertible.Write(context, nestedObjectSerializer); return false; } if (value.Value is IYamlSerializable yamlSerializable) { yamlSerializable.WriteYaml(context); return false; } return base.Enter(propertyDescriptor, value, context, serializer); } } internal sealed class DefaultExclusiveObjectGraphVisitor : ChainedObjectGraphVisitor { public DefaultExclusiveObjectGraphVisitor(IObjectGraphVisitor nextVisitor) : base(nextVisitor) { } private static object? GetDefault(Type type) { if (!type.IsValueType()) { return null; } return Activator.CreateInstance(type); } public override bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { if (!object.Equals(value.Value, GetDefault(value.Type))) { return base.EnterMapping(key, value, context, serializer); } return false; } public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { DefaultValueAttribute customAttribute = key.GetCustomAttribute(); object objB = ((customAttribute != null) ? customAttribute.Value : GetDefault(key.Type)); if (!object.Equals(value.Value, objB)) { return base.EnterMapping(key, value, context, serializer); } return false; } } internal sealed class DefaultValuesObjectGraphVisitor : ChainedObjectGraphVisitor { private readonly DefaultValuesHandling handling; private readonly IObjectFactory factory; public DefaultValuesObjectGraphVisitor(DefaultValuesHandling handling, IObjectGraphVisitor nextVisitor, IObjectFactory factory) : base(nextVisitor) { this.handling = handling; this.factory = factory; } private object? GetDefault(Type type) { return factory.CreatePrimitive(type); } public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { DefaultValuesHandling defaultValuesHandling = handling; YamlMemberAttribute customAttribute = key.GetCustomAttribute(); if (customAttribute != null && customAttribute.IsDefaultValuesHandlingSpecified) { defaultValuesHandling = customAttribute.DefaultValuesHandling; } if ((defaultValuesHandling & DefaultValuesHandling.OmitNull) != DefaultValuesHandling.Preserve && value.Value == null) { return false; } if ((defaultValuesHandling & DefaultValuesHandling.OmitEmptyCollections) != DefaultValuesHandling.Preserve && value.Value is IEnumerable enumerable) { IEnumerator enumerator = enumerable.GetEnumerator(); bool flag = enumerator.MoveNext(); if (enumerator is IDisposable disposable) { disposable.Dispose(); } if (!flag) { return false; } } if ((defaultValuesHandling & DefaultValuesHandling.OmitDefaults) != DefaultValuesHandling.Preserve) { object objB = key.GetCustomAttribute()?.Value ?? GetDefault(key.Type); if (object.Equals(value.Value, objB)) { return false; } } return base.EnterMapping(key, value, context, serializer); } } internal sealed class EmittingObjectGraphVisitor : IObjectGraphVisitor { private readonly IEventEmitter eventEmitter; public EmittingObjectGraphVisitor(IEventEmitter eventEmitter) { this.eventEmitter = eventEmitter; } bool IObjectGraphVisitor.Enter(IPropertyDescriptor? propertyDescriptor, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { return true; } bool IObjectGraphVisitor.EnterMapping(IObjectDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { return true; } bool IObjectGraphVisitor.EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) { return true; } void IObjectGraphVisitor.VisitScalar(IObjectDescriptor scalar, IEmitter context, ObjectSerializer serializer) { eventEmitter.Emit(new ScalarEventInfo(scalar), context); } void IObjectGraphVisitor.VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, IEmitter context, ObjectSerializer serializer) { eventEmitter.Emit(new MappingStartEventInfo(mapping), context); } void IObjectGraphVisitor.VisitMappingEnd(IObjectDescriptor mapping, IEmitter context, ObjectSerializer serializer) { eventEmitter.Emit(new MappingEndEventInfo(mapping), context); } void IObjectGraphVisitor.VisitSequenceStart(IObjectDescriptor sequence, Type elementType, IEmitter context, ObjectSerializer serializer) { eventEmitter.Emit(new SequenceStartEventInfo(sequence), context); } void IObjectGraphVisitor.VisitSequenceEnd(IObjectDescriptor sequence, IEmitter context, ObjectSerializer serializer) { eventEmitter.Emit(new SequenceEndEventInfo(sequence), context); } } internal abstract class PreProcessingPhaseObjectGraphVisitorSkeleton : IObjectGraphVisitor { protected readonly IEnumerable typeConverters; private readonly TypeConverterCache typeConverterCache; public PreProcessingPhaseObjectGraphVisitorSkeleton(IEnumerable typeConverters) { typeConverterCache = new TypeConverterCache((IYamlTypeConverter[])(this.typeConverters = typeConverters?.ToArray() ?? Array.Empty())); } bool IObjectGraphVisitor.Enter(IPropertyDescriptor? propertyDescriptor, IObjectDescriptor value, Nothing context, ObjectSerializer serializer) { if (typeConverterCache.TryGetConverterForType(value.Type, out IYamlTypeConverter _)) { return false; } if (value.Value is IYamlConvertible) { return false; } if (value.Value is IYamlSerializable) { return false; } return Enter(value, serializer); } bool IObjectGraphVisitor.EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, Nothing context, ObjectSerializer serializer) { return EnterMapping(key, value, serializer); } bool IObjectGraphVisitor.EnterMapping(IObjectDescriptor key, IObjectDescriptor value, Nothing context, ObjectSerializer serializer) { return EnterMapping(key, value, serializer); } void IObjectGraphVisitor.VisitMappingEnd(IObjectDescriptor mapping, Nothing context, ObjectSerializer serializer) { VisitMappingEnd(mapping, serializer); } void IObjectGraphVisitor.VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, Nothing context, ObjectSerializer serializer) { VisitMappingStart(mapping, keyType, valueType, serializer); } void IObjectGraphVisitor.VisitScalar(IObjectDescriptor scalar, Nothing context, ObjectSerializer serializer) { VisitScalar(scalar, serializer); } void IObjectGraphVisitor.VisitSequenceEnd(IObjectDescriptor sequence, Nothing context, ObjectSerializer serializer) { VisitSequenceEnd(sequence, serializer); } void IObjectGraphVisitor.VisitSequenceStart(IObjectDescriptor sequence, Type elementType, Nothing context, ObjectSerializer serializer) { VisitSequenceStart(sequence, elementType, serializer); } protected abstract bool Enter(IObjectDescriptor value, ObjectSerializer serializer); protected abstract bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, ObjectSerializer serializer); protected abstract bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value, ObjectSerializer serializer); protected abstract void VisitMappingEnd(IObjectDescriptor mapping, ObjectSerializer serializer); protected abstract void VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, ObjectSerializer serializer); protected abstract void VisitScalar(IObjectDescriptor scalar, ObjectSerializer serializer); protected abstract void VisitSequenceEnd(IObjectDescriptor sequence, ObjectSerializer serializer); protected abstract void VisitSequenceStart(IObjectDescriptor sequence, Type elementType, ObjectSerializer serializer); } } namespace YamlDotNet.Serialization.ObjectGraphTraversalStrategies { internal class FullObjectGraphTraversalStrategy : IObjectGraphTraversalStrategy { protected readonly struct ObjectPathSegment { public readonly object Name; public readonly IObjectDescriptor Value; public ObjectPathSegment(object name, IObjectDescriptor value) { Name = name; Value = value; } } private readonly int maxRecursion; private readonly ITypeInspector typeDescriptor; private readonly ITypeResolver typeResolver; private readonly INamingConvention namingConvention; private readonly IObjectFactory objectFactory; public FullObjectGraphTraversalStrategy(ITypeInspector typeDescriptor, ITypeResolver typeResolver, int maxRecursion, INamingConvention namingConvention, IObjectFactory objectFactory) { if (maxRecursion <= 0) { throw new ArgumentOutOfRangeException("maxRecursion", maxRecursion, "maxRecursion must be greater than 1"); } this.typeDescriptor = typeDescriptor ?? throw new ArgumentNullException("typeDescriptor"); this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); this.maxRecursion = maxRecursion; this.namingConvention = namingConvention ?? throw new ArgumentNullException("namingConvention"); this.objectFactory = objectFactory ?? throw new ArgumentNullException("objectFactory"); } void IObjectGraphTraversalStrategy.Traverse(IObjectDescriptor graph, IObjectGraphVisitor visitor, TContext context, ObjectSerializer serializer) { Traverse(null, "", graph, visitor, context, new Stack(maxRecursion), serializer); } protected virtual void Traverse(IPropertyDescriptor? propertyDescriptor, object name, IObjectDescriptor value, IObjectGraphVisitor visitor, TContext context, Stack path, ObjectSerializer serializer) { if (path.Count >= maxRecursion) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Too much recursion when traversing the object graph."); stringBuilder.AppendLine("The path to reach this recursion was:"); Stack> stack = new Stack>(path.Count); int num = 0; foreach (ObjectPathSegment item in path) { string text = item.Name?.ToString() ?? string.Empty; num = Math.Max(num, text.Length); stack.Push(new KeyValuePair(text, item.Value.Type.FullName)); } foreach (KeyValuePair item2 in stack) { stringBuilder.Append(" -> ").Append(item2.Key.PadRight(num)).Append(" [") .Append(item2.Value) .AppendLine("]"); } throw new MaximumRecursionLevelReachedException(stringBuilder.ToString()); } if (!visitor.Enter(propertyDescriptor, value, context, serializer)) { return; } path.Push(new ObjectPathSegment(name, value)); try { TypeCode typeCode = value.Type.GetTypeCode(); switch (typeCode) { case TypeCode.Boolean: case TypeCode.Char: case TypeCode.SByte: case TypeCode.Byte: case TypeCode.Int16: case TypeCode.UInt16: case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: case TypeCode.DateTime: case TypeCode.String: visitor.VisitScalar(value, context, serializer); return; case TypeCode.Empty: throw new NotSupportedException($"TypeCode.{typeCode} is not supported."); } if (value.IsDbNull()) { visitor.VisitScalar(new ObjectDescriptor(null, typeof(object), typeof(object)), context, serializer); } if (value.Value == null || value.Type == typeof(TimeSpan)) { visitor.VisitScalar(value, context, serializer); return; } Type underlyingType = Nullable.GetUnderlyingType(value.Type); Type type = underlyingType ?? FsharpHelper.GetOptionUnderlyingType(value.Type); object obj = ((type != null) ? FsharpHelper.GetValue(value) : null); if (underlyingType != null) { Traverse(propertyDescriptor, "Value", new ObjectDescriptor(value.Value, underlyingType, value.Type, value.ScalarStyle), visitor, context, path, serializer); } else if (type != null && obj != null) { Traverse(propertyDescriptor, "Value", new ObjectDescriptor(FsharpHelper.GetValue(value), type, value.Type, value.ScalarStyle), visitor, context, path, serializer); } else { TraverseObject(propertyDescriptor, value, visitor, context, path, serializer); } } finally { path.Pop(); } } protected virtual void TraverseObject(IPropertyDescriptor? propertyDescriptor, IObjectDescriptor value, IObjectGraphVisitor visitor, TContext context, Stack path, ObjectSerializer serializer) { IDictionary dictionary; Type[] genericArguments; if (typeof(IDictionary).IsAssignableFrom(value.Type)) { TraverseDictionary(propertyDescriptor, value, visitor, typeof(object), typeof(object), context, path, serializer); } else if (objectFactory.GetDictionary(value, out dictionary, out genericArguments)) { TraverseDictionary(propertyDescriptor, new ObjectDescriptor(dictionary, value.Type, value.StaticType, value.ScalarStyle), visitor, genericArguments[0], genericArguments[1], context, path, serializer); } else if (typeof(IEnumerable).IsAssignableFrom(value.Type)) { TraverseList(propertyDescriptor, value, visitor, context, path, serializer); } else { TraverseProperties(value, visitor, context, path, serializer); } } protected virtual void TraverseDictionary(IPropertyDescriptor? propertyDescriptor, IObjectDescriptor dictionary, IObjectGraphVisitor visitor, Type keyType, Type valueType, TContext context, Stack path, ObjectSerializer serializer) { visitor.VisitMappingStart(dictionary, keyType, valueType, context, serializer); bool flag = dictionary.Type.FullName.Equals("System.Dynamic.ExpandoObject"); foreach (DictionaryEntry? item in (IDictionary)dictionary.NonNullValue()) { DictionaryEntry value = item.Value; object obj = (flag ? namingConvention.Apply(value.Key.ToString()) : value.Key); ObjectDescriptor objectDescriptor = GetObjectDescriptor(obj, keyType); ObjectDescriptor objectDescriptor2 = GetObjectDescriptor(value.Value, valueType); if (visitor.EnterMapping(objectDescriptor, objectDescriptor2, context, serializer)) { Traverse(propertyDescriptor, obj, objectDescriptor, visitor, context, path, serializer); Traverse(propertyDescriptor, obj, objectDescriptor2, visitor, context, path, serializer); } } visitor.VisitMappingEnd(dictionary, context, serializer); } private void TraverseList(IPropertyDescriptor propertyDescriptor, IObjectDescriptor value, IObjectGraphVisitor visitor, TContext context, Stack path, ObjectSerializer serializer) { Type valueType = objectFactory.GetValueType(value.Type); visitor.VisitSequenceStart(value, valueType, context, serializer); int num = 0; foreach (object item in (IEnumerable)value.NonNullValue()) { Traverse(propertyDescriptor, num, GetObjectDescriptor(item, valueType), visitor, context, path, serializer); num++; } visitor.VisitSequenceEnd(value, context, serializer); } protected virtual void TraverseProperties(IObjectDescriptor value, IObjectGraphVisitor visitor, TContext context, Stack path, ObjectSerializer serializer) { if (context.GetType() != typeof(Nothing)) { objectFactory.ExecuteOnSerializing(value.Value); } visitor.VisitMappingStart(value, typeof(string), typeof(object), context, serializer); object obj = value.NonNullValue(); foreach (IPropertyDescriptor property in typeDescriptor.GetProperties(value.Type, obj)) { IObjectDescriptor value2 = property.Read(obj); if (visitor.EnterMapping(property, value2, context, serializer)) { Traverse(null, property.Name, new ObjectDescriptor(property.Name, typeof(string), typeof(string), ScalarStyle.Plain), visitor, context, path, serializer); Traverse(property, property.Name, value2, visitor, context, path, serializer); } } visitor.VisitMappingEnd(value, context, serializer); if (context.GetType() != typeof(Nothing)) { objectFactory.ExecuteOnSerialized(value.Value); } } private ObjectDescriptor GetObjectDescriptor(object? value, Type staticType) { return new ObjectDescriptor(value, typeResolver.Resolve(staticType, value), staticType); } } internal class RoundtripObjectGraphTraversalStrategy : FullObjectGraphTraversalStrategy { private readonly TypeConverterCache converters; private readonly Settings settings; public RoundtripObjectGraphTraversalStrategy(IEnumerable converters, ITypeInspector typeDescriptor, ITypeResolver typeResolver, int maxRecursion, INamingConvention namingConvention, Settings settings, IObjectFactory factory) : base(typeDescriptor, typeResolver, maxRecursion, namingConvention, factory) { this.converters = new TypeConverterCache(converters); this.settings = settings; } protected override void TraverseProperties(IObjectDescriptor value, IObjectGraphVisitor visitor, TContext context, Stack path, ObjectSerializer serializer) { if (!value.Type.HasDefaultConstructor(settings.AllowPrivateConstructors) && !converters.TryGetConverterForType(value.Type, out IYamlTypeConverter _)) { throw new InvalidOperationException($"Type '{value.Type}' cannot be deserialized because it does not have a default constructor or a type converter."); } base.TraverseProperties(value, visitor, context, path, serializer); } } } namespace YamlDotNet.Serialization.ObjectFactories { internal class DefaultObjectFactory : ObjectFactoryBase { private readonly Dictionary> stateMethods = new Dictionary> { { typeof(YamlDotNet.Serialization.Callbacks.OnDeserializedAttribute), new ConcurrentDictionary() }, { typeof(YamlDotNet.Serialization.Callbacks.OnDeserializingAttribute), new ConcurrentDictionary() }, { typeof(YamlDotNet.Serialization.Callbacks.OnSerializedAttribute), new ConcurrentDictionary() }, { typeof(YamlDotNet.Serialization.Callbacks.OnSerializingAttribute), new ConcurrentDictionary() } }; private readonly Dictionary defaultGenericInterfaceImplementations = new Dictionary { { typeof(IEnumerable<>), typeof(List<>) }, { typeof(ICollection<>), typeof(List<>) }, { typeof(IList<>), typeof(List<>) }, { typeof(IDictionary<, >), typeof(Dictionary<, >) } }; private readonly Dictionary defaultNonGenericInterfaceImplementations = new Dictionary { { typeof(IEnumerable), typeof(List) }, { typeof(ICollection), typeof(List) }, { typeof(IList), typeof(List) }, { typeof(IDictionary), typeof(Dictionary) } }; private readonly Settings settings; public DefaultObjectFactory() : this(new Dictionary(), new Settings()) { } public DefaultObjectFactory(IDictionary mappings) : this(mappings, new Settings()) { } public DefaultObjectFactory(IDictionary mappings, Settings settings) { foreach (KeyValuePair mapping in mappings) { if (!mapping.Key.IsAssignableFrom(mapping.Value)) { throw new InvalidOperationException($"Type '{mapping.Value}' does not implement type '{mapping.Key}'."); } defaultNonGenericInterfaceImplementations.Add(mapping.Key, mapping.Value); } this.settings = settings; } public override object Create(Type type) { if (type.IsInterface()) { Type value2; if (type.IsGenericType()) { if (defaultGenericInterfaceImplementations.TryGetValue(type.GetGenericTypeDefinition(), out Type value)) { type = value.MakeGenericType(type.GetGenericArguments()); } } else if (defaultNonGenericInterfaceImplementations.TryGetValue(type, out value2)) { type = value2; } } try { return Activator.CreateInstance(type, settings.AllowPrivateConstructors); } catch (Exception innerException) { string message = "Failed to create an instance of type '" + type.FullName + "'."; throw new InvalidOperationException(message, innerException); } } public override void ExecuteOnDeserialized(object value) { ExecuteState(typeof(YamlDotNet.Serialization.Callbacks.OnDeserializedAttribute), value); } public override void ExecuteOnDeserializing(object value) { ExecuteState(typeof(YamlDotNet.Serialization.Callbacks.OnDeserializingAttribute), value); } public override void ExecuteOnSerialized(object value) { ExecuteState(typeof(YamlDotNet.Serialization.Callbacks.OnSerializedAttribute), value); } public override void ExecuteOnSerializing(object value) { ExecuteState(typeof(YamlDotNet.Serialization.Callbacks.OnSerializingAttribute), value); } private void ExecuteState(Type attributeType, object value) { if (value != null) { Type type = value.GetType(); MethodInfo[] array = GetStateMethods(attributeType, type); MethodInfo[] array2 = array; foreach (MethodInfo methodInfo in array2) { methodInfo.Invoke(value, null); } } } private MethodInfo[] GetStateMethods(Type attributeType, Type valueType) { ConcurrentDictionary concurrentDictionary = stateMethods[attributeType]; return concurrentDictionary.GetOrAdd(valueType, delegate(Type type) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); return methods.Where((MethodInfo x) => x.GetCustomAttributes(attributeType, inherit: true).Length != 0).ToArray(); }); } } internal sealed class LambdaObjectFactory : ObjectFactoryBase { private readonly Func factory; public LambdaObjectFactory(Func factory) { this.factory = factory ?? throw new ArgumentNullException("factory"); } public override object Create(Type type) { return factory(type); } } internal abstract class ObjectFactoryBase : IObjectFactory { public abstract object Create(Type type); public virtual object? CreatePrimitive(Type type) { if (!type.IsValueType()) { return null; } return Activator.CreateInstance(type); } public virtual void ExecuteOnDeserialized(object value) { } public virtual void ExecuteOnDeserializing(object value) { } public virtual void ExecuteOnSerialized(object value) { } public virtual void ExecuteOnSerializing(object value) { } public virtual bool GetDictionary(IObjectDescriptor descriptor, out IDictionary? dictionary, out Type[]? genericArguments) { Type implementationOfOpenGenericInterface = descriptor.Type.GetImplementationOfOpenGenericInterface(typeof(IDictionary<, >)); if (implementationOfOpenGenericInterface != null) { genericArguments = implementationOfOpenGenericInterface.GetGenericArguments(); object obj = Activator.CreateInstance(typeof(GenericDictionaryToNonGenericAdapter<, >).MakeGenericType(genericArguments), descriptor.Value); dictionary = obj as IDictionary; return true; } genericArguments = null; dictionary = null; return false; } public virtual Type GetValueType(Type type) { Type implementationOfOpenGenericInterface = type.GetImplementationOfOpenGenericInterface(typeof(IEnumerable<>)); return (implementationOfOpenGenericInterface != null) ? implementationOfOpenGenericInterface.GetGenericArguments()[0] : typeof(object); } } internal abstract class StaticObjectFactory : IObjectFactory { public abstract object Create(Type type); public abstract Array CreateArray(Type type, int count); public abstract bool IsDictionary(Type type); public abstract bool IsArray(Type type); public abstract bool IsList(Type type); public abstract Type GetKeyType(Type type); public abstract Type GetValueType(Type type); public virtual object? CreatePrimitive(Type type) { return Type.GetTypeCode(type) switch { TypeCode.Boolean => false, TypeCode.Byte => (byte)0, TypeCode.Int16 => (short)0, TypeCode.Int32 => 0, TypeCode.Int64 => 0L, TypeCode.SByte => (sbyte)0, TypeCode.UInt16 => (ushort)0, TypeCode.UInt32 => 0u, TypeCode.UInt64 => 0uL, TypeCode.Single => 0f, TypeCode.Double => 0.0, TypeCode.Decimal => 0m, TypeCode.Char => '\0', TypeCode.DateTime => default(DateTime), _ => null, }; } public bool GetDictionary(IObjectDescriptor descriptor, out IDictionary? dictionary, out Type[]? genericArguments) { dictionary = null; genericArguments = null; return false; } public abstract void ExecuteOnDeserializing(object value); public abstract void ExecuteOnDeserialized(object value); public abstract void ExecuteOnSerializing(object value); public abstract void ExecuteOnSerialized(object value); } } namespace YamlDotNet.Serialization.NodeTypeResolvers { internal sealed class DefaultContainersNodeTypeResolver : INodeTypeResolver { bool INodeTypeResolver.Resolve(NodeEvent? nodeEvent, ref Type currentType) { if (currentType == typeof(object)) { if (nodeEvent is SequenceStart) { currentType = typeof(List); return true; } if (nodeEvent is MappingStart) { currentType = typeof(Dictionary); return true; } } return false; } } internal class MappingNodeTypeResolver : INodeTypeResolver { private readonly IDictionary mappings; public MappingNodeTypeResolver(IDictionary mappings) { if (mappings == null) { throw new ArgumentNullException("mappings"); } foreach (KeyValuePair mapping in mappings) { if (!mapping.Key.IsAssignableFrom(mapping.Value)) { throw new InvalidOperationException($"Type '{mapping.Value}' does not implement type '{mapping.Key}'."); } } this.mappings = mappings; } public bool Resolve(NodeEvent? nodeEvent, ref Type currentType) { if (mappings.TryGetValue(currentType, out Type value)) { currentType = value; return true; } return false; } } internal class PreventUnknownTagsNodeTypeResolver : INodeTypeResolver { bool INodeTypeResolver.Resolve(NodeEvent? nodeEvent, ref Type currentType) { if (nodeEvent != null && !nodeEvent.Tag.IsEmpty) { throw new YamlException(nodeEvent.Start, nodeEvent.End, $"Encountered an unresolved tag '{nodeEvent.Tag}'"); } return false; } } internal sealed class TagNodeTypeResolver : INodeTypeResolver { private readonly IDictionary tagMappings; public TagNodeTypeResolver(IDictionary tagMappings) { this.tagMappings = tagMappings ?? throw new ArgumentNullException("tagMappings"); } bool INodeTypeResolver.Resolve(NodeEvent? nodeEvent, ref Type currentType) { if (nodeEvent != null && !nodeEvent.Tag.IsEmpty && tagMappings.TryGetValue(nodeEvent.Tag, out Type value)) { currentType = value; return true; } return false; } } [Obsolete("The mechanism that this class uses to specify type names is non-standard. Register the tags explicitly instead of using this convention.")] internal sealed class TypeNameInTagNodeTypeResolver : INodeTypeResolver { bool INodeTypeResolver.Resolve(NodeEvent? nodeEvent, ref Type currentType) { if (nodeEvent != null && !nodeEvent.Tag.IsEmpty) { Type type = Type.GetType(nodeEvent.Tag.Value.Substring(1), throwOnError: false); if (type != null) { currentType = type; return true; } } return false; } } internal sealed class YamlConvertibleTypeResolver : INodeTypeResolver { public bool Resolve(NodeEvent? nodeEvent, ref Type currentType) { return typeof(IYamlConvertible).IsAssignableFrom(currentType); } } internal sealed class YamlSerializableTypeResolver : INodeTypeResolver { public bool Resolve(NodeEvent? nodeEvent, ref Type currentType) { return typeof(IYamlSerializable).IsAssignableFrom(currentType); } } } namespace YamlDotNet.Serialization.NodeDeserializers { internal sealed class ArrayNodeDeserializer : INodeDeserializer { private sealed class ArrayList : IList, ICollection, IEnumerable { private object?[] data; public bool IsFixedSize => false; public bool IsReadOnly => false; public object? this[int index] { get { return data[index]; } set { data[index] = value; } } public int Count { get; private set; } public bool IsSynchronized => false; public object SyncRoot => data; public ArrayList() { Clear(); } public int Add(object? value) { if (Count == data.Length) { Array.Resize(ref data, data.Length * 2); } data[Count] = value; return Count++; } public void Clear() { data = new object[10]; Count = 0; } bool IList.Contains(object? value) { throw new NotSupportedException(); } int IList.IndexOf(object? value) { throw new NotSupportedException(); } void IList.Insert(int index, object? value) { throw new NotSupportedException(); } void IList.Remove(object? value) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } public void CopyTo(Array array, int index) { Array.Copy(data, 0, array, index, Count); } public IEnumerator GetEnumerator() { int i = 0; while (i < Count) { yield return data[i]; int num = i + 1; i = num; } } } private readonly INamingConvention enumNamingConvention; private readonly ITypeInspector typeInspector; public ArrayNodeDeserializer(INamingConvention enumNamingConvention, ITypeInspector typeInspector) { this.enumNamingConvention = enumNamingConvention; this.typeInspector = typeInspector; } public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { if (!expectedType.IsArray) { value = false; return false; } Type itemType = expectedType.GetElementType(); ArrayList arrayList = new ArrayList(); Array array = null; CollectionNodeDeserializer.DeserializeHelper(itemType, parser, nestedObjectDeserializer, arrayList, canUpdate: true, enumNamingConvention, typeInspector, PromiseResolvedHandler); array = Array.CreateInstance(itemType, arrayList.Count); arrayList.CopyTo(array, 0); value = array; return true; void PromiseResolvedHandler(int index, object? value2) { if (array == null) { throw new InvalidOperationException("Destination array is still null"); } array.SetValue(YamlDotNet.Serialization.Utilities.TypeConverter.ChangeType(value2, itemType, enumNamingConvention, typeInspector), index); } } } internal abstract class CollectionDeserializer { protected static void DeserializeHelper(Type tItem, IParser parser, Func nestedObjectDeserializer, IList result, bool canUpdate, IObjectFactory objectFactory) { parser.Consume(); SequenceEnd @event; while (!parser.TryConsume(out @event)) { ParsingEvent current = parser.Current; object obj = nestedObjectDeserializer(parser, tItem); if (obj is IValuePromise valuePromise) { if (!canUpdate) { throw new ForwardAnchorNotSupportedException(current?.Start ?? Mark.Empty, current?.End ?? Mark.Empty, "Forward alias references are not allowed because this type does not implement IList<>"); } int index = result.Add(objectFactory.CreatePrimitive(tItem)); valuePromise.ValueAvailable += delegate(object? v) { result[index] = v; }; } else { result.Add(obj); } } } } internal sealed class CollectionNodeDeserializer : INodeDeserializer { private readonly IObjectFactory objectFactory; private readonly INamingConvention enumNamingConvention; private readonly ITypeInspector typeInspector; public CollectionNodeDeserializer(IObjectFactory objectFactory, INamingConvention enumNamingConvention, ITypeInspector typeInspector) { this.objectFactory = objectFactory ?? throw new ArgumentNullException("objectFactory"); this.enumNamingConvention = enumNamingConvention; this.typeInspector = typeInspector; } public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { bool canUpdate = true; Type implementationOfOpenGenericInterface = expectedType.GetImplementationOfOpenGenericInterface(typeof(ICollection<>)); Type type; IList list; if (implementationOfOpenGenericInterface != null) { Type[] genericArguments = implementationOfOpenGenericInterface.GetGenericArguments(); type = genericArguments[0]; value = objectFactory.Create(expectedType); list = value as IList; if (list == null) { Type implementationOfOpenGenericInterface2 = expectedType.GetImplementationOfOpenGenericInterface(typeof(IList<>)); canUpdate = implementationOfOpenGenericInterface2 != null; list = (IList)Activator.CreateInstance(typeof(GenericCollectionToNonGenericAdapter<>).MakeGenericType(type), value); } } else { if (!typeof(IList).IsAssignableFrom(expectedType)) { value = null; return false; } type = typeof(object); value = objectFactory.Create(expectedType); list = (IList)value; } DeserializeHelper(type, parser, nestedObjectDeserializer, list, canUpdate, enumNamingConvention, typeInspector); return true; } internal static void DeserializeHelper(Type tItem, IParser parser, Func nestedObjectDeserializer, IList result, bool canUpdate, INamingConvention enumNamingConvention, ITypeInspector typeInspector, Action? promiseResolvedHandler = null) { parser.Consume(); SequenceEnd @event; while (!parser.TryConsume(out @event)) { ParsingEvent current = parser.Current; object obj = nestedObjectDeserializer(parser, tItem); if (obj is IValuePromise valuePromise) { if (!canUpdate) { throw new ForwardAnchorNotSupportedException(current?.Start ?? Mark.Empty, current?.End ?? Mark.Empty, "Forward alias references are not allowed because this type does not implement IList<>"); } int index = result.Add(tItem.IsValueType() ? Activator.CreateInstance(tItem) : null); if (promiseResolvedHandler != null) { valuePromise.ValueAvailable += delegate(object? v) { promiseResolvedHandler(index, v); }; } else { valuePromise.ValueAvailable += delegate(object? v) { result[index] = YamlDotNet.Serialization.Utilities.TypeConverter.ChangeType(v, tItem, enumNamingConvention, typeInspector); }; } } else { result.Add(YamlDotNet.Serialization.Utilities.TypeConverter.ChangeType(obj, tItem, enumNamingConvention, typeInspector)); } } } } internal abstract class DictionaryDeserializer { private readonly bool duplicateKeyChecking; public DictionaryDeserializer(bool duplicateKeyChecking) { this.duplicateKeyChecking = duplicateKeyChecking; } private void TryAssign(IDictionary result, object key, object value, MappingStart propertyName) { if (duplicateKeyChecking && result.Contains(key)) { throw new YamlException(propertyName.Start, propertyName.End, $"Encountered duplicate key {key}"); } result[key] = value; } protected virtual void Deserialize(Type tKey, Type tValue, IParser parser, Func nestedObjectDeserializer, IDictionary result, ObjectDeserializer rootDeserializer) { MappingStart property = parser.Consume(); MappingEnd @event; while (!parser.TryConsume(out @event)) { object key = nestedObjectDeserializer(parser, tKey); object value = nestedObjectDeserializer(parser, tValue); IValuePromise valuePromise = value as IValuePromise; if (key is IValuePromise valuePromise2) { if (valuePromise == null) { valuePromise2.ValueAvailable += delegate(object? v) { result[v] = value; }; continue; } bool hasFirstPart = false; valuePromise2.ValueAvailable += delegate(object? v) { if (hasFirstPart) { TryAssign(result, v, value, property); } else { key = v; hasFirstPart = true; } }; valuePromise.ValueAvailable += delegate(object? v) { if (hasFirstPart) { TryAssign(result, key, v, property); } else { value = v; hasFirstPart = true; } }; continue; } if (key == null) { throw new ArgumentException("Empty key names are not supported yet.", "tKey"); } if (valuePromise == null) { TryAssign(result, key, value, property); continue; } valuePromise.ValueAvailable += delegate(object? v) { result[key] = v; }; } } } internal class DictionaryNodeDeserializer : DictionaryDeserializer, INodeDeserializer { private readonly IObjectFactory objectFactory; public DictionaryNodeDeserializer(IObjectFactory objectFactory, bool duplicateKeyChecking) : base(duplicateKeyChecking) { this.objectFactory = objectFactory ?? throw new ArgumentNullException("objectFactory"); } public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { Type implementationOfOpenGenericInterface = expectedType.GetImplementationOfOpenGenericInterface(typeof(IDictionary<, >)); Type type; Type type2; IDictionary dictionary; if (implementationOfOpenGenericInterface != null) { Type[] genericArguments = implementationOfOpenGenericInterface.GetGenericArguments(); type = genericArguments[0]; type2 = genericArguments[1]; value = objectFactory.Create(expectedType); dictionary = value as IDictionary; if (dictionary == null) { dictionary = (IDictionary)Activator.CreateInstance(typeof(GenericDictionaryToNonGenericAdapter<, >).MakeGenericType(type, type2), value); } } else { if (!typeof(IDictionary).IsAssignableFrom(expectedType)) { value = null; return false; } type = typeof(object); type2 = typeof(object); value = objectFactory.Create(expectedType); dictionary = (IDictionary)value; } Deserialize(type, type2, parser, nestedObjectDeserializer, dictionary, rootDeserializer); return true; } } internal sealed class EnumerableNodeDeserializer : INodeDeserializer { public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { Type type; if (expectedType == typeof(IEnumerable)) { type = typeof(object); } else { Type implementationOfOpenGenericInterface = expectedType.GetImplementationOfOpenGenericInterface(typeof(IEnumerable<>)); if (implementationOfOpenGenericInterface != expectedType) { value = null; return false; } type = implementationOfOpenGenericInterface.GetGenericArguments()[0]; } Type arg = typeof(List<>).MakeGenericType(type); value = nestedObjectDeserializer(parser, arg); return true; } } internal sealed class FsharpListNodeDeserializer : INodeDeserializer { private readonly ITypeInspector typeInspector; private readonly INamingConvention enumNamingConvention; public FsharpListNodeDeserializer(ITypeInspector typeInspector, INamingConvention enumNamingConvention) { this.typeInspector = typeInspector; this.enumNamingConvention = enumNamingConvention; } public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { if (!FsharpHelper.IsFsharpListType(expectedType)) { value = false; return false; } Type type = expectedType.GetGenericArguments()[0]; Type t = expectedType.GetGenericTypeDefinition().MakeGenericType(type); ArrayList arrayList = new ArrayList(); CollectionNodeDeserializer.DeserializeHelper(type, parser, nestedObjectDeserializer, arrayList, canUpdate: true, enumNamingConvention, typeInspector); Array array = Array.CreateInstance(type, arrayList.Count); arrayList.CopyTo(array, 0); object obj = FsharpHelper.CreateFsharpListFromArray(t, type, array); value = obj; return true; } } internal sealed class NullNodeDeserializer : INodeDeserializer { public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { value = null; if (parser.Accept(out var @event) && NodeIsNull(@event)) { parser.SkipThisAndNestedEvents(); return true; } return false; } private static bool NodeIsNull(NodeEvent nodeEvent) { if (nodeEvent.Tag == "tag:yaml.org,2002:null") { return true; } if (nodeEvent is YamlDotNet.Core.Events.Scalar { Style: ScalarStyle.Plain, IsKey: false } scalar) { string value = scalar.Value; switch (value) { default: return value == "NULL"; case "": case "~": case "null": case "Null": return true; } } return false; } } internal sealed class ObjectNodeDeserializer : INodeDeserializer { private readonly IObjectFactory objectFactory; private readonly ITypeInspector typeInspector; private readonly bool ignoreUnmatched; private readonly bool duplicateKeyChecking; private readonly ITypeConverter typeConverter; private readonly INamingConvention enumNamingConvention; private readonly bool enforceNullability; private readonly bool caseInsensitivePropertyMatching; private readonly bool enforceRequiredProperties; private readonly TypeConverterCache typeConverters; public ObjectNodeDeserializer(IObjectFactory objectFactory, ITypeInspector typeInspector, bool ignoreUnmatched, bool duplicateKeyChecking, ITypeConverter typeConverter, INamingConvention enumNamingConvention, bool enforceNullability, bool caseInsensitivePropertyMatching, bool enforceRequiredProperties, IEnumerable typeConverters) { this.objectFactory = objectFactory ?? throw new ArgumentNullException("objectFactory"); this.typeInspector = typeInspector ?? throw new ArgumentNullException("typeInspector"); this.ignoreUnmatched = ignoreUnmatched; this.duplicateKeyChecking = duplicateKeyChecking; this.typeConverter = typeConverter ?? throw new ArgumentNullException("typeConverter"); this.enumNamingConvention = enumNamingConvention ?? throw new ArgumentNullException("enumNamingConvention"); this.enforceNullability = enforceNullability; this.caseInsensitivePropertyMatching = caseInsensitivePropertyMatching; this.enforceRequiredProperties = enforceRequiredProperties; this.typeConverters = new TypeConverterCache(typeConverters); } public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { if (!parser.TryConsume(out var _)) { value = null; return false; } Type type = Nullable.GetUnderlyingType(expectedType) ?? FsharpHelper.GetOptionUnderlyingType(expectedType) ?? expectedType; value = objectFactory.Create(type); objectFactory.ExecuteOnDeserializing(value); HashSet hashSet = new HashSet(StringComparer.Ordinal); HashSet hashSet2 = new HashSet(StringComparer.Ordinal); Mark start = Mark.Empty; MappingEnd event2; while (!parser.TryConsume(out event2)) { YamlDotNet.Core.Events.Scalar propertyName = parser.Consume(); if (duplicateKeyChecking && !hashSet.Add(propertyName.Value)) { throw new YamlException(propertyName.Start, propertyName.End, "Encountered duplicate key " + propertyName.Value); } try { IPropertyDescriptor property = typeInspector.GetProperty(type, null, propertyName.Value, ignoreUnmatched, caseInsensitivePropertyMatching); if (property == null) { parser.SkipThisAndNestedEvents(); continue; } hashSet2.Add(property.Name); object obj; if (property.ConverterType != null) { IYamlTypeConverter converterByType = typeConverters.GetConverterByType(property.ConverterType); obj = converterByType.ReadYaml(parser, property.Type, rootDeserializer); } else { obj = nestedObjectDeserializer(parser, property.Type); } if (obj is IValuePromise valuePromise) { object valueRef = value; valuePromise.ValueAvailable += delegate(object? v) { object value3 = typeConverter.ChangeType(v, property.Type, enumNamingConvention, typeInspector); NullCheck(value3, property, propertyName); property.Write(valueRef, value3); }; } else { object value2 = typeConverter.ChangeType(obj, property.Type, enumNamingConvention, typeInspector); NullCheck(value2, property, propertyName); property.Write(value, value2); } } catch (SerializationException ex) { throw new YamlException(propertyName.Start, propertyName.End, ex.Message); } catch (YamlException) { throw; } catch (Exception innerException) { throw new YamlException(propertyName.Start, propertyName.End, "Exception during deserialization", innerException); } start = propertyName.End; } if (enforceRequiredProperties) { IEnumerable properties = typeInspector.GetProperties(type, value); List list = new List(); foreach (IPropertyDescriptor item in properties) { if (item.Required && !hashSet2.Contains(item.Name)) { list.Add(item.Name); } } if (list.Count > 0) { string text = string.Join(",", list); throw new YamlException(in start, in start, "Missing properties, '" + text + "' in source yaml."); } } objectFactory.ExecuteOnDeserialized(value); return true; } public void NullCheck(object value, IPropertyDescriptor property, YamlDotNet.Core.Events.Scalar propertyName) { if (enforceNullability && value == null && !property.AllowNulls) { throw new YamlException(propertyName.Start, propertyName.End, "Strict nullability enforcement error.", new NullReferenceException("Yaml value is null when target property requires non null values.")); } } } internal sealed class ScalarNodeDeserializer : INodeDeserializer { private const string BooleanTruePattern = "^(true|y|yes|on)$"; private const string BooleanFalsePattern = "^(false|n|no|off)$"; private readonly bool attemptUnknownTypeDeserialization; private readonly ITypeConverter typeConverter; private readonly ITypeInspector typeInspector; private readonly YamlFormatter formatter; private readonly INamingConvention enumNamingConvention; public ScalarNodeDeserializer(bool attemptUnknownTypeDeserialization, ITypeConverter typeConverter, ITypeInspector typeInspector, YamlFormatter formatter, INamingConvention enumNamingConvention) { this.attemptUnknownTypeDeserialization = attemptUnknownTypeDeserialization; this.typeConverter = typeConverter ?? throw new ArgumentNullException("typeConverter"); this.typeInspector = typeInspector; this.formatter = formatter; this.enumNamingConvention = enumNamingConvention; } public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { if (!parser.TryConsume(out var @event)) { value = null; return false; } Type type = Nullable.GetUnderlyingType(expectedType) ?? FsharpHelper.GetOptionUnderlyingType(expectedType) ?? expectedType; if (type.IsEnum()) { string name = enumNamingConvention.Reverse(@event.Value); name = typeInspector.GetEnumName(type, name); value = Enum.Parse(type, name, ignoreCase: true); return true; } TypeCode typeCode = type.GetTypeCode(); switch (typeCode) { case TypeCode.Boolean: value = DeserializeBooleanHelper(@event.Value); break; case TypeCode.SByte: case TypeCode.Byte: case TypeCode.Int16: case TypeCode.UInt16: case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: value = DeserializeIntegerHelper(typeCode, @event.Value); break; case TypeCode.Single: value = float.Parse(@event.Value, formatter.NumberFormat); break; case TypeCode.Double: value = double.Parse(@event.Value, formatter.NumberFormat); break; case TypeCode.Decimal: value = decimal.Parse(@event.Value, formatter.NumberFormat); break; case TypeCode.String: value = @event.Value; break; case TypeCode.Char: value = @event.Value[0]; break; case TypeCode.DateTime: value = DateTime.Parse(@event.Value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); break; default: if (expectedType == typeof(object)) { if (!@event.IsKey && attemptUnknownTypeDeserialization) { value = AttemptUnknownTypeDeserialization(@event); } else { value = @event.Value; } } else { value = typeConverter.ChangeType(@event.Value, expectedType, enumNamingConvention, typeInspector); } break; } return true; } private static bool DeserializeBooleanHelper(string value) { if (Regex.IsMatch(value, "^(true|y|yes|on)$", RegexOptions.IgnoreCase)) { return true; } if (Regex.IsMatch(value, "^(false|n|no|off)$", RegexOptions.IgnoreCase)) { return false; } throw new FormatException("The value \"" + value + "\" is not a valid YAML Boolean"); } private object DeserializeIntegerHelper(TypeCode typeCode, string value) { StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; int i = 0; bool flag = false; ulong num = 0uL; if (value[0] == '-') { i++; flag = true; } else if (value[0] == '+') { i++; } if (value[i] == '0') { int num2; if (i == value.Length - 1) { num2 = 10; num = 0uL; } else { i++; if (value[i] == 'b') { num2 = 2; i++; } else if (value[i] == 'x') { num2 = 16; i++; } else { num2 = 8; } } for (; i < value.Length; i++) { if (value[i] != '_') { builder.Append(value[i]); } } switch (num2) { case 2: case 8: num = Convert.ToUInt64(builder.ToString(), num2); break; case 16: num = ulong.Parse(builder.ToString(), NumberStyles.HexNumber, formatter.NumberFormat); break; } } else { string[] array = value.Substring(i).Split(new char[1] { ':' }); num = 0uL; for (int j = 0; j < array.Length; j++) { num *= 60; num += ulong.Parse(array[j].Replace("_", ""), CultureInfo.InvariantCulture); } } if (!flag) { return CastInteger(num, typeCode); } long number = ((num != 9223372036854775808uL) ? checked(-(long)num) : long.MinValue); return CastInteger(number, typeCode); } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } } private static object CastInteger(long number, TypeCode typeCode) { return checked(typeCode switch { TypeCode.Byte => (byte)number, TypeCode.Int16 => (short)number, TypeCode.Int32 => (int)number, TypeCode.Int64 => number, TypeCode.SByte => (sbyte)number, TypeCode.UInt16 => (ushort)number, TypeCode.UInt32 => (uint)number, TypeCode.UInt64 => (ulong)number, _ => number, }); } private static object CastInteger(ulong number, TypeCode typeCode) { return checked(typeCode switch { TypeCode.Byte => (byte)number, TypeCode.Int16 => (short)number, TypeCode.Int32 => (int)number, TypeCode.Int64 => (long)number, TypeCode.SByte => (sbyte)number, TypeCode.UInt16 => (ushort)number, TypeCode.UInt32 => (uint)number, TypeCode.UInt64 => number, _ => number, }); } private object? AttemptUnknownTypeDeserialization(YamlDotNet.Core.Events.Scalar value) { if (value.Style == ScalarStyle.SingleQuoted || value.Style == ScalarStyle.DoubleQuoted || value.Style == ScalarStyle.Folded) { return value.Value; } string v = value.Value; switch (v) { case "null": case "Null": case "NULL": case "~": case "": return null; case "true": case "True": case "TRUE": return true; case "False": case "FALSE": case "false": return false; default: if (Regex.IsMatch(v, "^0x[0-9a-fA-F]+$")) { v = v.Substring(2); if (byte.TryParse(v, NumberStyles.AllowHexSpecifier, formatter.NumberFormat, out var result)) { return result; } if (short.TryParse(v, NumberStyles.AllowHexSpecifier, formatter.NumberFormat, out var result2)) { return result2; } if (int.TryParse(v, NumberStyles.AllowHexSpecifier, formatter.NumberFormat, out var result3)) { return result3; } if (long.TryParse(v, NumberStyles.AllowHexSpecifier, formatter.NumberFormat, out var result4)) { return result4; } if (ulong.TryParse(v, NumberStyles.AllowHexSpecifier, formatter.NumberFormat, out var result5)) { return result5; } return v; } if (Regex.IsMatch(v, "^0o[0-9a-fA-F]+$")) { if (!TryAndSwallow(() => Convert.ToByte(v, 8), out object value2) && !TryAndSwallow(() => Convert.ToInt16(v, 8), out value2) && !TryAndSwallow(() => Convert.ToInt32(v, 8), out value2) && !TryAndSwallow(() => Convert.ToInt64(v, 8), out value2) && !TryAndSwallow(() => Convert.ToUInt64(v, 8), out value2)) { return v; } return value2; } if (Regex.IsMatch(v, "^[-+]?(\\.[0-9]+|[0-9]+(\\.[0-9]*)?)([eE][-+]?[0-9]+)?$")) { if (byte.TryParse(v, NumberStyles.Integer, formatter.NumberFormat, out var result6)) { return result6; } if (short.TryParse(v, NumberStyles.Integer, formatter.NumberFormat, out var result7)) { return result7; } if (int.TryParse(v, NumberStyles.Integer, formatter.NumberFormat, out var result8)) { return result8; } if (long.TryParse(v, NumberStyles.Integer, formatter.NumberFormat, out var result9)) { return result9; } if (ulong.TryParse(v, NumberStyles.Integer, formatter.NumberFormat, out var result10)) { return result10; } if (float.TryParse(v, NumberStyles.Float, formatter.NumberFormat, out var result11)) { return result11; } if (double.TryParse(v, NumberStyles.Float, formatter.NumberFormat, out var result12)) { return result12; } return v; } if (Regex.IsMatch(v, "^[-+]?(\\.inf|\\.Inf|\\.INF)$")) { if (Polyfills.StartsWith(v, '-')) { return float.NegativeInfinity; } return float.PositiveInfinity; } if (Regex.IsMatch(v, "^(\\.nan|\\.NaN|\\.NAN)$")) { return float.NaN; } return v; } } private static bool TryAndSwallow(Func attempt, out object? value) { try { value = attempt(); return true; } catch { value = null; return false; } } } internal sealed class StaticArrayNodeDeserializer : INodeDeserializer { private sealed class ArrayList : IList, ICollection, IEnumerable { private object?[] data; public bool IsFixedSize => false; public bool IsReadOnly => false; public object? this[int index] { get { return data[index]; } set { data[index] = value; } } public int Count { get; private set; } public bool IsSynchronized => false; public object SyncRoot => data; public ArrayList() { Clear(); } public int Add(object? value) { if (Count == data.Length) { Array.Resize(ref data, data.Length * 2); } data[Count] = value; return Count++; } public void Clear() { data = new object[10]; Count = 0; } bool IList.Contains(object? value) { throw new NotSupportedException(); } int IList.IndexOf(object? value) { throw new NotSupportedException(); } void IList.Insert(int index, object? value) { throw new NotSupportedException(); } void IList.Remove(object? value) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } public void CopyTo(Array array, int index) { Array.Copy(data, 0, array, index, Count); } public IEnumerator GetEnumerator() { int i = 0; while (i < Count) { yield return data[i]; int num = i + 1; i = num; } } } private readonly StaticObjectFactory factory; public StaticArrayNodeDeserializer(StaticObjectFactory factory) { this.factory = factory ?? throw new ArgumentNullException("factory"); } public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { if (!factory.IsArray(expectedType)) { value = false; return false; } Type valueType = factory.GetValueType(expectedType); ArrayList arrayList = new ArrayList(); StaticCollectionNodeDeserializer.DeserializeHelper(valueType, parser, nestedObjectDeserializer, arrayList, factory); Array array = factory.CreateArray(expectedType, arrayList.Count); arrayList.CopyTo(array, 0); value = array; return true; } } internal sealed class StaticCollectionNodeDeserializer : INodeDeserializer { private readonly StaticObjectFactory factory; public StaticCollectionNodeDeserializer(StaticObjectFactory factory) { this.factory = factory ?? throw new ArgumentNullException("factory"); } public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { if (!factory.IsList(expectedType)) { value = null; return false; } DeserializeHelper(result: (IList)(value = factory.Create(expectedType) as IList), tItem: factory.GetValueType(expectedType), parser: parser, nestedObjectDeserializer: nestedObjectDeserializer, factory: factory); return true; } internal static void DeserializeHelper(Type tItem, IParser parser, Func nestedObjectDeserializer, IList result, IObjectFactory factory) { parser.Consume(); SequenceEnd @event; while (!parser.TryConsume(out @event)) { ParsingEvent current = parser.Current; object obj = nestedObjectDeserializer(parser, tItem); if (obj is IValuePromise valuePromise) { int index = result.Add(factory.CreatePrimitive(tItem)); valuePromise.ValueAvailable += delegate(object? v) { result[index] = v; }; } else { result.Add(obj); } } } } internal class StaticDictionaryNodeDeserializer : DictionaryDeserializer, INodeDeserializer { private readonly StaticObjectFactory objectFactory; public StaticDictionaryNodeDeserializer(StaticObjectFactory objectFactory, bool duplicateKeyChecking) : base(duplicateKeyChecking) { this.objectFactory = objectFactory ?? throw new ArgumentNullException("objectFactory"); } public bool Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { if (objectFactory.IsDictionary(expectedType)) { if (!(objectFactory.Create(expectedType) is IDictionary dictionary)) { value = null; return false; } Type keyType = objectFactory.GetKeyType(expectedType); Type valueType = objectFactory.GetValueType(expectedType); value = dictionary; base.Deserialize(keyType, valueType, reader, nestedObjectDeserializer, dictionary, rootDeserializer); return true; } value = null; return false; } } internal sealed class TypeConverterNodeDeserializer : INodeDeserializer { private readonly TypeConverterCache converters; public TypeConverterNodeDeserializer(IEnumerable converters) { this.converters = new TypeConverterCache(converters); } public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { if (!converters.TryGetConverterForType(expectedType, out IYamlTypeConverter typeConverter)) { value = null; return false; } value = typeConverter.ReadYaml(parser, expectedType, rootDeserializer); return true; } } internal sealed class YamlConvertibleNodeDeserializer : INodeDeserializer { private readonly IObjectFactory objectFactory; public YamlConvertibleNodeDeserializer(IObjectFactory objectFactory) { this.objectFactory = objectFactory; } public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { if (typeof(IYamlConvertible).IsAssignableFrom(expectedType)) { IYamlConvertible yamlConvertible = (IYamlConvertible)objectFactory.Create(expectedType); yamlConvertible.Read(parser, expectedType, (Type type) => nestedObjectDeserializer(parser, type)); value = yamlConvertible; return true; } value = null; return false; } } internal sealed class YamlSerializableNodeDeserializer : INodeDeserializer { private readonly IObjectFactory objectFactory; public YamlSerializableNodeDeserializer(IObjectFactory objectFactory) { this.objectFactory = objectFactory; } public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { if (typeof(IYamlSerializable).IsAssignableFrom(expectedType)) { IYamlSerializable yamlSerializable = (IYamlSerializable)objectFactory.Create(expectedType); yamlSerializable.ReadYaml(parser); value = yamlSerializable; return true; } value = null; return false; } } } namespace YamlDotNet.Serialization.NamingConventions { internal sealed class CamelCaseNamingConvention : INamingConvention { public static readonly INamingConvention Instance = new CamelCaseNamingConvention(); [Obsolete("Use the Instance static field instead of creating new instances")] public CamelCaseNamingConvention() { } public string Apply(string value) { return value.ToCamelCase(); } public string Reverse(string value) { return value.ToPascalCase(); } } internal sealed class HyphenatedNamingConvention : INamingConvention { public static readonly INamingConvention Instance = new HyphenatedNamingConvention(); [Obsolete("Use the Instance static field instead of creating new instances")] public HyphenatedNamingConvention() { } public string Apply(string value) { return value.FromCamelCase("-"); } public string Reverse(string value) { return value.ToPascalCase(); } } internal sealed class LowerCaseNamingConvention : INamingConvention { public static readonly INamingConvention Instance = new LowerCaseNamingConvention(); private LowerCaseNamingConvention() { } public string Apply(string value) { return value.ToCamelCase().ToLower(CultureInfo.InvariantCulture); } public string Reverse(string value) { if (string.IsNullOrEmpty(value)) { return value; } return char.ToUpperInvariant(value[0]) + value.Substring(1); } } internal sealed class NullNamingConvention : INamingConvention { public static readonly INamingConvention Instance = new NullNamingConvention(); [Obsolete("Use the Instance static field instead of creating new instances")] public NullNamingConvention() { } public string Apply(string value) { return value; } public string Reverse(string value) { return value; } } internal sealed class PascalCaseNamingConvention : INamingConvention { public static readonly INamingConvention Instance = new PascalCaseNamingConvention(); [Obsolete("Use the Instance static field instead of creating new instances")] public PascalCaseNamingConvention() { } public string Apply(string value) { return value.ToPascalCase(); } public string Reverse(string value) { return value.ToPascalCase(); } } internal sealed class UnderscoredNamingConvention : INamingConvention { public static readonly INamingConvention Instance = new UnderscoredNamingConvention(); [Obsolete("Use the Instance static field instead of creating new instances")] public UnderscoredNamingConvention() { } public string Apply(string value) { return value.FromCamelCase("_"); } public string Reverse(string value) { return value.ToPascalCase(); } } } namespace YamlDotNet.Serialization.EventEmitters { internal abstract class ChainedEventEmitter : IEventEmitter { protected readonly IEventEmitter nextEmitter; protected ChainedEventEmitter(IEventEmitter nextEmitter) { this.nextEmitter = nextEmitter ?? throw new ArgumentNullException("nextEmitter"); } public virtual void Emit(AliasEventInfo eventInfo, IEmitter emitter) { nextEmitter.Emit(eventInfo, emitter); } public virtual void Emit(ScalarEventInfo eventInfo, IEmitter emitter) { nextEmitter.Emit(eventInfo, emitter); } public virtual void Emit(MappingStartEventInfo eventInfo, IEmitter emitter) { nextEmitter.Emit(eventInfo, emitter); } public virtual void Emit(MappingEndEventInfo eventInfo, IEmitter emitter) { nextEmitter.Emit(eventInfo, emitter); } public virtual void Emit(SequenceStartEventInfo eventInfo, IEmitter emitter) { nextEmitter.Emit(eventInfo, emitter); } public virtual void Emit(SequenceEndEventInfo eventInfo, IEmitter emitter) { nextEmitter.Emit(eventInfo, emitter); } } internal sealed class JsonEventEmitter : ChainedEventEmitter { private readonly YamlFormatter formatter; private readonly INamingConvention enumNamingConvention; private readonly ITypeInspector typeInspector; public JsonEventEmitter(IEventEmitter nextEmitter, YamlFormatter formatter, INamingConvention enumNamingConvention, ITypeInspector typeInspector) : base(nextEmitter) { this.formatter = formatter; this.enumNamingConvention = enumNamingConvention; this.typeInspector = typeInspector; } public override void Emit(AliasEventInfo eventInfo, IEmitter emitter) { eventInfo.NeedsExpansion = true; } public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter) { eventInfo.IsPlainImplicit = true; eventInfo.Style = ScalarStyle.Plain; object value = eventInfo.Source.Value; if (value == null) { eventInfo.RenderedValue = "null"; } else { TypeCode typeCode = eventInfo.Source.Type.GetTypeCode(); switch (typeCode) { case TypeCode.Boolean: eventInfo.RenderedValue = formatter.FormatBoolean(value); break; case TypeCode.SByte: case TypeCode.Byte: case TypeCode.Int16: case TypeCode.UInt16: case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: if (eventInfo.Source.Type.IsEnum()) { eventInfo.RenderedValue = formatter.FormatEnum(value, typeInspector, enumNamingConvention); eventInfo.Style = ((!formatter.PotentiallyQuoteEnums(value)) ? ScalarStyle.Plain : ScalarStyle.DoubleQuoted); } else { eventInfo.RenderedValue = formatter.FormatNumber(value); } break; case TypeCode.Single: { float f = (float)value; eventInfo.RenderedValue = f.ToString("G", CultureInfo.InvariantCulture); if (float.IsNaN(f) || float.IsInfinity(f)) { eventInfo.Style = ScalarStyle.DoubleQuoted; } break; } case TypeCode.Double: { double d = (double)value; eventInfo.RenderedValue = d.ToString("G", CultureInfo.InvariantCulture); if (double.IsNaN(d) || double.IsInfinity(d)) { eventInfo.Style = ScalarStyle.DoubleQuoted; } break; } case TypeCode.Decimal: eventInfo.RenderedValue = ((decimal)value).ToString(CultureInfo.InvariantCulture); break; case TypeCode.Char: case TypeCode.String: eventInfo.RenderedValue = value.ToString(); eventInfo.Style = ScalarStyle.DoubleQuoted; break; case TypeCode.DateTime: eventInfo.RenderedValue = formatter.FormatDateTime(value); break; case TypeCode.Empty: eventInfo.RenderedValue = "null"; break; default: if (eventInfo.Source.Type == typeof(TimeSpan)) { eventInfo.RenderedValue = formatter.FormatTimeSpan(value); break; } throw new NotSupportedException($"TypeCode.{typeCode} is not supported."); } } base.Emit(eventInfo, emitter); } public override void Emit(MappingStartEventInfo eventInfo, IEmitter emitter) { eventInfo.Style = MappingStyle.Flow; base.Emit(eventInfo, emitter); } public override void Emit(SequenceStartEventInfo eventInfo, IEmitter emitter) { eventInfo.Style = SequenceStyle.Flow; base.Emit(eventInfo, emitter); } } internal sealed class TypeAssigningEventEmitter : ChainedEventEmitter { private readonly IDictionary tagMappings; private readonly bool quoteNecessaryStrings; private readonly Regex? isSpecialStringValue_Regex; private static readonly string SpecialStrings_Pattern = "^(null|Null|NULL|\\~|true|True|TRUE|false|False|FALSE|[-+]?[0-9]+|0o[0-7]+|0x[0-9a-fA-F]+|[-+]?(\\.[0-9]+|[0-9]+(\\.[0-9]*)?)([eE][-+]?[0-9]+)?|[-+]?(\\.inf|\\.Inf|\\.INF)|\\.nan|\\.NaN|\\.NAN|\\s.*)$"; private static readonly string CombinedYaml1_1SpecialStrings_Pattern = "^(null|Null|NULL|\\~|true|True|TRUE|false|False|FALSE|y|Y|yes|Yes|YES|n|N|no|No|NO|on|On|ON|off|Off|OFF|[-+]?0b[0-1_]+|[-+]?0o?[0-7_]+|[-+]?(0|[1-9][0-9_]*)|[-+]?0x[0-9a-fA-F_]+|[-+]?[1-9][0-9_]*(:[0-5]?[0-9])+|[-+]?([0-9][0-9_]*)?\\.[0-9_]*([eE][-+][0-9]+)?|[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(inf|Inf|INF)|\\.(nan|NaN|NAN))$"; private readonly ScalarStyle defaultScalarStyle; private readonly YamlFormatter formatter; private readonly INamingConvention enumNamingConvention; private readonly ITypeInspector typeInspector; public TypeAssigningEventEmitter(IEventEmitter nextEmitter, IDictionary tagMappings, bool quoteNecessaryStrings, bool quoteYaml1_1Strings, ScalarStyle defaultScalarStyle, YamlFormatter formatter, INamingConvention enumNamingConvention, ITypeInspector typeInspector) : base(nextEmitter) { this.defaultScalarStyle = defaultScalarStyle; this.formatter = formatter; this.tagMappings = tagMappings; this.quoteNecessaryStrings = quoteNecessaryStrings; isSpecialStringValue_Regex = new Regex(quoteYaml1_1Strings ? CombinedYaml1_1SpecialStrings_Pattern : SpecialStrings_Pattern, RegexOptions.Compiled); this.enumNamingConvention = enumNamingConvention; this.typeInspector = typeInspector; } public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter) { ScalarStyle style = ScalarStyle.Plain; object value = eventInfo.Source.Value; if (value == null) { eventInfo.Tag = JsonSchema.Tags.Null; eventInfo.RenderedValue = ""; } else { TypeCode typeCode = eventInfo.Source.Type.GetTypeCode(); switch (typeCode) { case TypeCode.Boolean: eventInfo.Tag = JsonSchema.Tags.Bool; eventInfo.RenderedValue = formatter.FormatBoolean(value); break; case TypeCode.SByte: case TypeCode.Byte: case TypeCode.Int16: case TypeCode.UInt16: case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: if (eventInfo.Source.Type.IsEnum) { eventInfo.Tag = FailsafeSchema.Tags.Str; eventInfo.RenderedValue = formatter.FormatEnum(value, typeInspector, enumNamingConvention); style = ((!quoteNecessaryStrings || !IsSpecialStringValue(eventInfo.RenderedValue) || !formatter.PotentiallyQuoteEnums(value)) ? defaultScalarStyle : ScalarStyle.DoubleQuoted); } else { eventInfo.Tag = JsonSchema.Tags.Int; eventInfo.RenderedValue = formatter.FormatNumber(value); } break; case TypeCode.Single: eventInfo.Tag = JsonSchema.Tags.Float; eventInfo.RenderedValue = formatter.FormatNumber((float)value); break; case TypeCode.Double: eventInfo.Tag = JsonSchema.Tags.Float; eventInfo.RenderedValue = formatter.FormatNumber((double)value); break; case TypeCode.Decimal: eventInfo.Tag = JsonSchema.Tags.Float; eventInfo.RenderedValue = formatter.FormatNumber(value); break; case TypeCode.Char: case TypeCode.String: eventInfo.Tag = FailsafeSchema.Tags.Str; eventInfo.RenderedValue = value.ToString(); style = ((!quoteNecessaryStrings || !IsSpecialStringValue(eventInfo.RenderedValue)) ? defaultScalarStyle : ScalarStyle.DoubleQuoted); break; case TypeCode.DateTime: eventInfo.Tag = DefaultSchema.Tags.Timestamp; eventInfo.RenderedValue = formatter.FormatDateTime(value); break; case TypeCode.Empty: eventInfo.Tag = JsonSchema.Tags.Null; eventInfo.RenderedValue = ""; break; default: if (eventInfo.Source.Type == typeof(TimeSpan)) { eventInfo.RenderedValue = formatter.FormatTimeSpan(value); break; } throw new NotSupportedException($"TypeCode.{typeCode} is not supported."); } } eventInfo.IsPlainImplicit = true; if (eventInfo.Style == ScalarStyle.Any) { eventInfo.Style = style; } base.Emit(eventInfo, emitter); } public override void Emit(MappingStartEventInfo eventInfo, IEmitter emitter) { AssignTypeIfNeeded(eventInfo); base.Emit(eventInfo, emitter); } public override void Emit(SequenceStartEventInfo eventInfo, IEmitter emitter) { AssignTypeIfNeeded(eventInfo); base.Emit(eventInfo, emitter); } private void AssignTypeIfNeeded(ObjectEventInfo eventInfo) { if (tagMappings.TryGetValue(eventInfo.Source.Type, out var value)) { eventInfo.Tag = value; } } private bool IsSpecialStringValue(string value) { if (value.Trim() == string.Empty) { return true; } return isSpecialStringValue_Regex?.IsMatch(value) ?? false; } } internal sealed class WriterEventEmitter : IEventEmitter { void IEventEmitter.Emit(AliasEventInfo eventInfo, IEmitter emitter) { emitter.Emit(new YamlDotNet.Core.Events.AnchorAlias(eventInfo.Alias)); } void IEventEmitter.Emit(ScalarEventInfo eventInfo, IEmitter emitter) { emitter.Emit(new YamlDotNet.Core.Events.Scalar(eventInfo.Anchor, eventInfo.Tag, eventInfo.RenderedValue, eventInfo.Style, eventInfo.IsPlainImplicit, eventInfo.IsQuotedImplicit)); } void IEventEmitter.Emit(MappingStartEventInfo eventInfo, IEmitter emitter) { emitter.Emit(new MappingStart(eventInfo.Anchor, eventInfo.Tag, eventInfo.IsImplicit, eventInfo.Style)); } void IEventEmitter.Emit(MappingEndEventInfo eventInfo, IEmitter emitter) { emitter.Emit(new MappingEnd()); } void IEventEmitter.Emit(SequenceStartEventInfo eventInfo, IEmitter emitter) { emitter.Emit(new SequenceStart(eventInfo.Anchor, eventInfo.Tag, eventInfo.IsImplicit, eventInfo.Style)); } void IEventEmitter.Emit(SequenceEndEventInfo eventInfo, IEmitter emitter) { emitter.Emit(new SequenceEnd()); } } } namespace YamlDotNet.Serialization.Converters { internal class DateTime8601Converter : IYamlTypeConverter { private readonly ScalarStyle scalarStyle; public DateTime8601Converter() : this(ScalarStyle.Any) { } public DateTime8601Converter(ScalarStyle scalarStyle) { this.scalarStyle = scalarStyle; } public bool Accepts(Type type) { return type == typeof(DateTime); } public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) { string value = parser.Consume().Value; DateTime dateTime = DateTime.ParseExact(value, "O", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); return dateTime; } public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) { string value2 = ((DateTime)value).ToString("O", CultureInfo.InvariantCulture); emitter.Emit(new YamlDotNet.Core.Events.Scalar(AnchorName.Empty, TagName.Empty, value2, scalarStyle, isPlainImplicit: true, isQuotedImplicit: false)); } } internal class DateTimeConverter : IYamlTypeConverter { private readonly DateTimeKind kind; private readonly IFormatProvider provider; private readonly bool doubleQuotes; private readonly string[] formats; public DateTimeConverter(DateTimeKind kind = DateTimeKind.Utc, IFormatProvider? provider = null, bool doubleQuotes = false, params string[] formats) { this.kind = ((kind == DateTimeKind.Unspecified) ? DateTimeKind.Utc : kind); this.provider = provider ?? CultureInfo.InvariantCulture; this.doubleQuotes = doubleQuotes; this.formats = formats.DefaultIfEmpty("G").ToArray(); } public bool Accepts(Type type) { return type == typeof(DateTime); } public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) { string value = parser.Consume().Value; DateTimeStyles style = ((kind == DateTimeKind.Local) ? DateTimeStyles.AssumeLocal : DateTimeStyles.AssumeUniversal); DateTime dt = DateTime.ParseExact(value, formats, provider, style); dt = EnsureDateTimeKind(dt, kind); return dt; } public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) { DateTime dateTime = (DateTime)value; string value2 = ((kind == DateTimeKind.Local) ? dateTime.ToLocalTime() : dateTime.ToUniversalTime()).ToString(formats.First(), provider); emitter.Emit(new YamlDotNet.Core.Events.Scalar(AnchorName.Empty, TagName.Empty, value2, doubleQuotes ? ScalarStyle.DoubleQuoted : ScalarStyle.Any, isPlainImplicit: true, isQuotedImplicit: false)); } private static DateTime EnsureDateTimeKind(DateTime dt, DateTimeKind kind) { if (dt.Kind == DateTimeKind.Local && kind == DateTimeKind.Utc) { return dt.ToUniversalTime(); } if (dt.Kind == DateTimeKind.Utc && kind == DateTimeKind.Local) { return dt.ToLocalTime(); } return dt; } } internal class DateTimeOffsetConverter : IYamlTypeConverter { private readonly IFormatProvider provider; private readonly ScalarStyle style; private readonly DateTimeStyles dateStyle; private readonly string[] formats; public DateTimeOffsetConverter(IFormatProvider? provider = null, ScalarStyle style = ScalarStyle.Any, DateTimeStyles dateStyle = DateTimeStyles.None, params string[] formats) { this.provider = provider ?? CultureInfo.InvariantCulture; this.style = style; this.dateStyle = dateStyle; this.formats = formats.DefaultIfEmpty("O").ToArray(); } public bool Accepts(Type type) { return type == typeof(DateTimeOffset); } public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) { string value = parser.Consume().Value; DateTimeOffset dateTimeOffset = DateTimeOffset.ParseExact(value, formats, provider, dateStyle); return dateTimeOffset; } public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) { string value2 = ((DateTimeOffset)value).ToString(formats.First(), provider); emitter.Emit(new YamlDotNet.Core.Events.Scalar(AnchorName.Empty, TagName.Empty, value2, style, isPlainImplicit: true, isQuotedImplicit: false)); } } internal class GuidConverter : IYamlTypeConverter { private readonly bool jsonCompatible; public GuidConverter(bool jsonCompatible) { this.jsonCompatible = jsonCompatible; } public bool Accepts(Type type) { return type == typeof(Guid); } public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) { string value = parser.Consume().Value; return new Guid(value); } public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) { Guid guid = (Guid)value; emitter.Emit(new YamlDotNet.Core.Events.Scalar(AnchorName.Empty, TagName.Empty, guid.ToString("D"), jsonCompatible ? ScalarStyle.DoubleQuoted : ScalarStyle.Any, isPlainImplicit: true, isQuotedImplicit: false)); } } internal class SystemTypeConverter : IYamlTypeConverter { public bool Accepts(Type type) { return typeof(Type).IsAssignableFrom(type); } public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) { string value = parser.Consume().Value; return Type.GetType(value, throwOnError: true); } public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) { Type type2 = (Type)value; emitter.Emit(new YamlDotNet.Core.Events.Scalar(AnchorName.Empty, TagName.Empty, type2.AssemblyQualifiedName, ScalarStyle.Any, isPlainImplicit: true, isQuotedImplicit: false)); } } } namespace YamlDotNet.Serialization.Callbacks { [AttributeUsage(AttributeTargets.Method)] internal sealed class OnDeserializedAttribute : Attribute { } [AttributeUsage(AttributeTargets.Method)] internal sealed class OnDeserializingAttribute : Attribute { } [AttributeUsage(AttributeTargets.Method)] internal sealed class OnSerializedAttribute : Attribute { } [AttributeUsage(AttributeTargets.Method)] internal sealed class OnSerializingAttribute : Attribute { } } namespace YamlDotNet.Serialization.BufferedDeserialization { internal interface ITypeDiscriminatingNodeDeserializerOptions { void AddTypeDiscriminator(ITypeDiscriminator discriminator); void AddKeyValueTypeDiscriminator(string discriminatorKey, IDictionary valueTypeMapping); void AddUniqueKeyTypeDiscriminator(IDictionary uniqueKeyTypeMapping); } internal class ParserBuffer : IParser { private readonly LinkedList buffer; private LinkedListNode? current; public ParsingEvent? Current => current?.Value; public ParserBuffer(IParser parserToBuffer, int maxDepth, int maxLength) { buffer = new LinkedList(); buffer.AddLast(parserToBuffer.Consume()); int num = 0; do { ParsingEvent parsingEvent = parserToBuffer.Consume(); num += parsingEvent.NestingIncrease; buffer.AddLast(parsingEvent); if (maxDepth > -1 && num > maxDepth) { throw new ArgumentOutOfRangeException("parserToBuffer", "Parser buffer exceeded max depth"); } if (maxLength > -1 && buffer.Count > maxLength) { throw new ArgumentOutOfRangeException("parserToBuffer", "Parser buffer exceeded max length"); } } while (num >= 0); current = buffer.First; } public bool MoveNext() { current = current?.Next; return current != null; } public void Reset() { current = buffer.First; } } internal class TypeDiscriminatingNodeDeserializer : INodeDeserializer { private readonly IList innerDeserializers; private readonly IList typeDiscriminators; private readonly int maxDepthToBuffer; private readonly int maxLengthToBuffer; public TypeDiscriminatingNodeDeserializer(IList innerDeserializers, IList typeDiscriminators, int maxDepthToBuffer, int maxLengthToBuffer) { this.innerDeserializers = innerDeserializers; this.typeDiscriminators = typeDiscriminators; this.maxDepthToBuffer = maxDepthToBuffer; this.maxLengthToBuffer = maxLengthToBuffer; } public bool Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value, ObjectDeserializer rootDeserializer) { if (!reader.Accept(out var _)) { value = null; return false; } IEnumerable enumerable = typeDiscriminators.Where((ITypeDiscriminator t) => t.BaseType.IsAssignableFrom(expectedType)); if (!enumerable.Any()) { value = null; return false; } Mark start = reader.Current.Start; Type expectedType2 = expectedType; ParserBuffer parserBuffer; try { parserBuffer = new ParserBuffer(reader, maxDepthToBuffer, maxLengthToBuffer); } catch (Exception innerException) { throw new YamlException(in start, reader.Current.End, "Failed to buffer yaml node", innerException); } try { foreach (ITypeDiscriminator item in enumerable) { parserBuffer.Reset(); if (item.TryDiscriminate(parserBuffer, out Type suggestedType)) { expectedType2 = suggestedType; break; } } } catch (Exception innerException2) { throw new YamlException(in start, reader.Current.End, "Failed to discriminate type", innerException2); } parserBuffer.Reset(); foreach (INodeDeserializer innerDeserializer in innerDeserializers) { if (innerDeserializer.Deserialize(parserBuffer, expectedType2, nestedObjectDeserializer, out value, rootDeserializer)) { return true; } } value = null; return false; } } internal class TypeDiscriminatingNodeDeserializerOptions : ITypeDiscriminatingNodeDeserializerOptions { internal readonly List discriminators = new List(); public void AddTypeDiscriminator(ITypeDiscriminator discriminator) { discriminators.Add(discriminator); } public void AddKeyValueTypeDiscriminator(string discriminatorKey, IDictionary valueTypeMapping) { discriminators.Add(new KeyValueTypeDiscriminator(typeof(T), discriminatorKey, valueTypeMapping)); } public void AddUniqueKeyTypeDiscriminator(IDictionary uniqueKeyTypeMapping) { discriminators.Add(new UniqueKeyTypeDiscriminator(typeof(T), uniqueKeyTypeMapping)); } } } namespace YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators { internal interface ITypeDiscriminator { Type BaseType { get; } bool TryDiscriminate(IParser buffer, out Type? suggestedType); } internal class KeyValueTypeDiscriminator : ITypeDiscriminator { private readonly string targetKey; private readonly IDictionary typeMapping; public Type BaseType { get; private set; } public KeyValueTypeDiscriminator(Type baseType, string targetKey, IDictionary typeMapping) { foreach (KeyValuePair item in typeMapping) { if (!baseType.IsAssignableFrom(item.Value)) { throw new ArgumentOutOfRangeException("typeMapping", $"{item.Value} is not a assignable to {baseType}"); } } BaseType = baseType; this.targetKey = targetKey; this.typeMapping = typeMapping; } public bool TryDiscriminate(IParser parser, out Type? suggestedType) { if (parser.TryFindMappingEntry((YamlDotNet.Core.Events.Scalar scalar2) => targetKey == scalar2.Value, out YamlDotNet.Core.Events.Scalar _, out ParsingEvent value) && value is YamlDotNet.Core.Events.Scalar scalar && typeMapping.TryGetValue(scalar.Value, out Type value2)) { suggestedType = value2; return true; } suggestedType = null; return false; } } internal class UniqueKeyTypeDiscriminator : ITypeDiscriminator { private readonly IDictionary typeMapping; public Type BaseType { get; private set; } public UniqueKeyTypeDiscriminator(Type baseType, IDictionary typeMapping) { foreach (KeyValuePair item in typeMapping) { if (!baseType.IsAssignableFrom(item.Value)) { throw new ArgumentOutOfRangeException("typeMapping", $"{item.Value} is not a assignable to {baseType}"); } } BaseType = baseType; this.typeMapping = typeMapping; } public bool TryDiscriminate(IParser parser, out Type? suggestedType) { if (parser.TryFindMappingEntry((YamlDotNet.Core.Events.Scalar scalar) => typeMapping.ContainsKey(scalar.Value), out YamlDotNet.Core.Events.Scalar key, out ParsingEvent _)) { suggestedType = typeMapping[key.Value]; return true; } suggestedType = null; return false; } } } namespace YamlDotNet.RepresentationModel { internal class DocumentLoadingState { private readonly Dictionary anchors = new Dictionary(); private readonly List nodesWithUnresolvedAliases = new List(); public void AddAnchor(YamlNode node) { if (node.Anchor.IsEmpty) { throw new ArgumentException("The specified node does not have an anchor"); } anchors[node.Anchor] = node; } public YamlNode GetNode(AnchorName anchor, Mark start, Mark end) { if (anchors.TryGetValue(anchor, out YamlNode value)) { return value; } throw new AnchorNotFoundException(in start, in end, $"The anchor '{anchor}' does not exists"); } public bool TryGetNode(AnchorName anchor, [NotNullWhen(true)] out YamlNode? node) { return anchors.TryGetValue(anchor, out node); } public void AddNodeWithUnresolvedAliases(YamlNode node) { nodesWithUnresolvedAliases.Add(node); } public void ResolveAliases() { foreach (YamlNode nodesWithUnresolvedAlias in nodesWithUnresolvedAliases) { nodesWithUnresolvedAlias.ResolveAliases(this); } } } internal class EmitterState { public HashSet EmittedAnchors { get; } = new HashSet(); } internal interface IYamlVisitor { void Visit(YamlStream stream); void Visit(YamlDocument document); void Visit(YamlScalarNode scalar); void Visit(YamlSequenceNode sequence); void Visit(YamlMappingNode mapping); } internal class LibYamlEventStream { private readonly IParser parser; public LibYamlEventStream(IParser iParser) { parser = iParser ?? throw new ArgumentNullException("iParser"); } public void WriteTo(TextWriter textWriter) { while (parser.MoveNext()) { ParsingEvent current = parser.Current; if (!(current is YamlDotNet.Core.Events.AnchorAlias anchorAlias)) { if (!(current is YamlDotNet.Core.Events.DocumentEnd documentEnd)) { if (!(current is YamlDotNet.Core.Events.DocumentStart documentStart)) { if (!(current is MappingEnd)) { if (!(current is MappingStart nodeEvent)) { if (!(current is YamlDotNet.Core.Events.Scalar scalar)) { if (!(current is SequenceEnd)) { if (!(current is SequenceStart nodeEvent2)) { if (!(current is YamlDotNet.Core.Events.StreamEnd)) { if (current is YamlDotNet.Core.Events.StreamStart) { textWriter.Write("+STR"); } } else { textWriter.Write("-STR"); } } else { textWriter.Write("+SEQ"); WriteAnchorAndTag(textWriter, nodeEvent2); } } else { textWriter.Write("-SEQ"); } } else { textWriter.Write("=VAL"); WriteAnchorAndTag(textWriter, scalar); switch (scalar.Style) { case ScalarStyle.DoubleQuoted: textWriter.Write(" \""); break; case ScalarStyle.SingleQuoted: textWriter.Write(" '"); break; case ScalarStyle.Folded: textWriter.Write(" >"); break; case ScalarStyle.Literal: textWriter.Write(" |"); break; default: textWriter.Write(" :"); break; } string value = scalar.Value; foreach (char c in value) { switch (c) { case '\b': textWriter.Write("\\b"); break; case '\t': textWriter.Write("\\t"); break; case '\n': textWriter.Write("\\n"); break; case '\r': textWriter.Write("\\r"); break; case '\\': textWriter.Write("\\\\"); break; default: textWriter.Write(c); break; } } } } else { textWriter.Write("+MAP"); WriteAnchorAndTag(textWriter, nodeEvent); } } else { textWriter.Write("-MAP"); } } else { textWriter.Write("+DOC"); if (!documentStart.IsImplicit) { textWriter.Write(" ---"); } } } else { textWriter.Write("-DOC"); if (!documentEnd.IsImplicit) { textWriter.Write(" ..."); } } } else { textWriter.Write("=ALI *"); textWriter.Write(anchorAlias.Value); } textWriter.WriteLine(); } } private static void WriteAnchorAndTag(TextWriter textWriter, NodeEvent nodeEvent) { if (!nodeEvent.Anchor.IsEmpty) { textWriter.Write(" &"); textWriter.Write(nodeEvent.Anchor); } if (!nodeEvent.Tag.IsEmpty) { textWriter.Write(" <"); textWriter.Write(nodeEvent.Tag.Value); textWriter.Write(">"); } } } internal class YamlAliasNode : YamlNode { public override YamlNodeType NodeType => YamlNodeType.Alias; internal YamlAliasNode(AnchorName anchor) { base.Anchor = anchor; } internal override void ResolveAliases(DocumentLoadingState state) { throw new NotSupportedException("Resolving an alias on an alias node does not make sense"); } internal override void Emit(IEmitter emitter, EmitterState state) { throw new NotSupportedException("A YamlAliasNode is an implementation detail and should never be saved."); } public override void Accept(IYamlVisitor visitor) { throw new NotSupportedException("A YamlAliasNode is an implementation detail and should never be visited."); } public override bool Equals(object? obj) { if (obj is YamlAliasNode yamlAliasNode && Equals(yamlAliasNode)) { return object.Equals(base.Anchor, yamlAliasNode.Anchor); } return false; } public override int GetHashCode() { return base.GetHashCode(); } internal override string ToString(RecursionLevel level) { return "*" + base.Anchor; } internal override IEnumerable SafeAllNodes(RecursionLevel level) { yield return this; } } internal class YamlDocument { private class AnchorAssigningVisitor : YamlVisitorBase { private readonly HashSet existingAnchors = new HashSet(); private readonly Dictionary visitedNodes = new Dictionary(); public void AssignAnchors(YamlDocument document) { existingAnchors.Clear(); visitedNodes.Clear(); document.Accept(this); Random random = new Random(); foreach (KeyValuePair visitedNode in visitedNodes) { if (!visitedNode.Value) { continue; } AnchorName anchorName; if (!visitedNode.Key.Anchor.IsEmpty && !existingAnchors.Contains(visitedNode.Key.Anchor)) { anchorName = visitedNode.Key.Anchor; } else { do { anchorName = new AnchorName(random.Next().ToString(CultureInfo.InvariantCulture)); } while (existingAnchors.Contains(anchorName)); } existingAnchors.Add(anchorName); visitedNode.Key.Anchor = anchorName; } } private bool VisitNodeAndFindDuplicates(YamlNode node) { if (visitedNodes.TryGetValue(node, out var value)) { if (!value) { visitedNodes[node] = true; } return !value; } visitedNodes.Add(node, value: false); return false; } public override void Visit(YamlScalarNode scalar) { VisitNodeAndFindDuplicates(scalar); } public override void Visit(YamlMappingNode mapping) { if (!VisitNodeAndFindDuplicates(mapping)) { base.Visit(mapping); } } public override void Visit(YamlSequenceNode sequence) { if (!VisitNodeAndFindDuplicates(sequence)) { base.Visit(sequence); } } } public YamlNode RootNode { get; private set; } public IEnumerable AllNodes => RootNode.AllNodes; public YamlDocument(YamlNode rootNode) { RootNode = rootNode; } public YamlDocument(string rootNode) { RootNode = new YamlScalarNode(rootNode); } internal YamlDocument(IParser parser) { DocumentLoadingState documentLoadingState = new DocumentLoadingState(); parser.Consume(); YamlDotNet.Core.Events.DocumentEnd @event; while (!parser.TryConsume(out @event)) { RootNode = YamlNode.ParseNode(parser, documentLoadingState); if (RootNode is YamlAliasNode) { throw new YamlException("A document cannot contain only an alias"); } } documentLoadingState.ResolveAliases(); if (RootNode == null) { throw new ArgumentException("Atempted to parse an empty document"); } } private void AssignAnchors() { AnchorAssigningVisitor anchorAssigningVisitor = new AnchorAssigningVisitor(); anchorAssigningVisitor.AssignAnchors(this); } internal void Save(IEmitter emitter, bool assignAnchors = true) { if (assignAnchors) { AssignAnchors(); } emitter.Emit(new YamlDotNet.Core.Events.DocumentStart()); RootNode.Save(emitter, new EmitterState()); emitter.Emit(new YamlDotNet.Core.Events.DocumentEnd(isImplicit: false)); } public void Accept(IYamlVisitor visitor) { visitor.Visit(this); } } internal sealed class YamlMappingNode : YamlNode, IEnumerable>, IEnumerable, IYamlConvertible { private readonly OrderedDictionary children = new OrderedDictionary(); public IOrderedDictionary Children => children; public MappingStyle Style { get; set; } public override YamlNodeType NodeType => YamlNodeType.Mapping; internal YamlMappingNode(IParser parser, DocumentLoadingState state) { Load(parser, state); } private void Load(IParser parser, DocumentLoadingState state) { MappingStart mappingStart = parser.Consume(); Load(mappingStart, state); Style = mappingStart.Style; bool flag = false; MappingEnd @event; while (!parser.TryConsume(out @event)) { YamlNode yamlNode = YamlNode.ParseNode(parser, state); YamlNode yamlNode2 = YamlNode.ParseNode(parser, state); if (!children.TryAdd(yamlNode, yamlNode2)) { throw new YamlException(yamlNode.Start, yamlNode.End, $"Duplicate key {yamlNode}"); } flag = flag || yamlNode is YamlAliasNode || yamlNode2 is YamlAliasNode; } if (flag) { state.AddNodeWithUnresolvedAliases(this); } } public YamlMappingNode() { } public YamlMappingNode(params KeyValuePair[] children) : this((IEnumerable>)children) { } public YamlMappingNode(IEnumerable> children) { foreach (KeyValuePair child in children) { this.children.Add(child); } } public YamlMappingNode(params YamlNode[] children) : this((IEnumerable)children) { } public YamlMappingNode(IEnumerable children) { using IEnumerator enumerator = children.GetEnumerator(); while (enumerator.MoveNext()) { YamlNode current = enumerator.Current; if (!enumerator.MoveNext()) { throw new ArgumentException("When constructing a mapping node with a sequence, the number of elements of the sequence must be even."); } Add(current, enumerator.Current); } } public void Add(YamlNode key, YamlNode value) { children.Add(key, value); } public void Add(string key, YamlNode value) { children.Add(new YamlScalarNode(key), value); } public void Add(YamlNode key, string value) { children.Add(key, new YamlScalarNode(value)); } public void Add(string key, string value) { children.Add(new YamlScalarNode(key), new YamlScalarNode(value)); } internal override void ResolveAliases(DocumentLoadingState state) { Dictionary dictionary = null; Dictionary dictionary2 = null; foreach (KeyValuePair child in children) { if (child.Key is YamlAliasNode) { if (dictionary == null) { dictionary = new Dictionary(); } dictionary.Add(child.Key, state.GetNode(child.Key.Anchor, child.Key.Start, child.Key.End)); } if (child.Value is YamlAliasNode) { if (dictionary2 == null) { dictionary2 = new Dictionary(); } dictionary2.Add(child.Key, state.GetNode(child.Value.Anchor, child.Value.Start, child.Value.End)); } } if (dictionary2 != null) { foreach (KeyValuePair item in dictionary2) { children[item.Key] = item.Value; } } if (dictionary == null) { return; } foreach (KeyValuePair item2 in dictionary) { YamlNode value = children[item2.Key]; children.Remove(item2.Key); children.Add(item2.Value, value); } } internal override void Emit(IEmitter emitter, EmitterState state) { emitter.Emit(new MappingStart(base.Anchor, base.Tag, isImplicit: true, Style)); foreach (KeyValuePair child in children) { child.Key.Save(emitter, state); child.Value.Save(emitter, state); } emitter.Emit(new MappingEnd()); } public override void Accept(IYamlVisitor visitor) { visitor.Visit(this); } public override bool Equals(object? obj) { if (!(obj is YamlMappingNode yamlMappingNode) || !object.Equals(base.Tag, yamlMappingNode.Tag) || children.Count != yamlMappingNode.children.Count) { return false; } foreach (KeyValuePair child in children) { if (!yamlMappingNode.children.TryGetValue(child.Key, out YamlNode value) || !object.Equals(child.Value, value)) { return false; } } return true; } public override int GetHashCode() { int num = base.GetHashCode(); foreach (KeyValuePair child in children) { num = YamlDotNet.Core.HashCode.CombineHashCodes(num, child.Key); num = (child.Value.Anchor.IsEmpty ? YamlDotNet.Core.HashCode.CombineHashCodes(num, child.Value) : YamlDotNet.Core.HashCode.CombineHashCodes(num, child.Value.Anchor)); } return num; } internal override IEnumerable SafeAllNodes(RecursionLevel level) { level.Increment(); yield return this; foreach (KeyValuePair child in children) { foreach (YamlNode item in child.Key.SafeAllNodes(level)) { yield return item; } foreach (YamlNode item2 in child.Value.SafeAllNodes(level)) { yield return item2; } } level.Decrement(); } internal override string ToString(RecursionLevel level) { if (!level.TryIncrement()) { return "WARNING! INFINITE RECURSION!"; } StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; builder.Append("{ "); foreach (KeyValuePair child in children) { if (builder.Length > 2) { builder.Append(", "); } builder.Append("{ ").Append(child.Key.ToString(level)).Append(", ") .Append(child.Value.ToString(level)) .Append(" }"); } builder.Append(" }"); level.Decrement(); return builder.ToString(); } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } } public IEnumerator> GetEnumerator() { return children.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) { Load(parser, new DocumentLoadingState()); } void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) { Emit(emitter, new EmitterState()); } public static YamlMappingNode FromObject(object mapping) { if (mapping == null) { throw new ArgumentNullException("mapping"); } YamlMappingNode yamlMappingNode = new YamlMappingNode(); foreach (PropertyInfo publicProperty in mapping.GetType().GetPublicProperties()) { if (publicProperty.CanRead && publicProperty.GetGetMethod(nonPublic: false).GetParameters().Length == 0) { object value = publicProperty.GetValue(mapping, null); YamlNode yamlNode = value as YamlNode; if (yamlNode == null) { string text = Convert.ToString(value, CultureInfo.InvariantCulture); yamlNode = text ?? string.Empty; } yamlMappingNode.Add(publicProperty.Name, yamlNode); } } return yamlMappingNode; } } internal abstract class YamlNode { private const int MaximumRecursionLevel = 1000; internal const string MaximumRecursionLevelReachedToStringValue = "WARNING! INFINITE RECURSION!"; public AnchorName Anchor { get; set; } public TagName Tag { get; set; } public Mark Start { get; private set; } = Mark.Empty; public Mark End { get; private set; } = Mark.Empty; public IEnumerable AllNodes { get { RecursionLevel level = new RecursionLevel(1000); return SafeAllNodes(level); } } public abstract YamlNodeType NodeType { get; } public YamlNode this[int index] { get { if (!(this is YamlSequenceNode yamlSequenceNode)) { throw new ArgumentException($"Accessed '{NodeType}' with an invalid index: {index}. Only Sequences can be indexed by number."); } return yamlSequenceNode.Children[index]; } } public YamlNode this[YamlNode key] { get { if (!(this is YamlMappingNode yamlMappingNode)) { throw new ArgumentException($"Accessed '{NodeType}' with an invalid index: {key}. Only Mappings can be indexed by key."); } return yamlMappingNode.Children[key]; } } internal void Load(NodeEvent yamlEvent, DocumentLoadingState state) { Tag = yamlEvent.Tag; if (!yamlEvent.Anchor.IsEmpty) { Anchor = yamlEvent.Anchor; state.AddAnchor(this); } Start = yamlEvent.Start; End = yamlEvent.End; } internal static YamlNode ParseNode(IParser parser, DocumentLoadingState state) { if (parser.Accept(out var _)) { return new YamlScalarNode(parser, state); } if (parser.Accept(out var _)) { return new YamlSequenceNode(parser, state); } if (parser.Accept(out var _)) { return new YamlMappingNode(parser, state); } if (parser.TryConsume(out var event4)) { if (!state.TryGetNode(event4.Value, out YamlNode node)) { return new YamlAliasNode(event4.Value); } return node; } throw new ArgumentException("The current event is of an unsupported type.", "parser"); } internal abstract void ResolveAliases(DocumentLoadingState state); internal void Save(IEmitter emitter, EmitterState state) { if (!Anchor.IsEmpty && !state.EmittedAnchors.Add(Anchor)) { emitter.Emit(new YamlDotNet.Core.Events.AnchorAlias(Anchor)); } else { Emit(emitter, state); } } internal abstract void Emit(IEmitter emitter, EmitterState state); public abstract void Accept(IYamlVisitor visitor); public override string ToString() { RecursionLevel recursionLevel = new RecursionLevel(1000); return ToString(recursionLevel); } internal abstract string ToString(RecursionLevel level); internal abstract IEnumerable SafeAllNodes(RecursionLevel level); public static implicit operator YamlNode(string value) { return new YamlScalarNode(value); } public static implicit operator YamlNode(string[] sequence) { return new YamlSequenceNode(((IEnumerable)sequence).Select((Func)((string i) => i))); } public static explicit operator string?(YamlNode node) { if (!(node is YamlScalarNode yamlScalarNode)) { throw new ArgumentException($"Attempted to convert a '{node.NodeType}' to string. This conversion is valid only for Scalars."); } return yamlScalarNode.Value; } } internal sealed class YamlNodeIdentityEqualityComparer : IEqualityComparer { public bool Equals([AllowNull] YamlNode x, [AllowNull] YamlNode y) { return x == y; } public int GetHashCode(YamlNode obj) { return obj.GetHashCode(); } } internal enum YamlNodeType { Alias, Mapping, Scalar, Sequence } [DebuggerDisplay("{Value}")] internal sealed class YamlScalarNode : YamlNode, IYamlConvertible { private bool forceImplicitPlain; private string? value; public string? Value { get { return value; } set { if (value == null) { forceImplicitPlain = true; } else { forceImplicitPlain = false; } this.value = value; } } public ScalarStyle Style { get; set; } public override YamlNodeType NodeType => YamlNodeType.Scalar; internal YamlScalarNode(IParser parser, DocumentLoadingState state) { Load(parser, state); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Load(IParser parser, DocumentLoadingState state) { YamlDotNet.Core.Events.Scalar scalar = parser.Consume(); Load(scalar, state); string text = scalar.Value; if (scalar.Style == ScalarStyle.Plain && base.Tag.IsEmpty) { forceImplicitPlain = text.Length switch { 0 => true, 1 => text == "~", 4 => text == "null" || text == "Null" || text == "NULL", _ => false, }; } value = text; Style = scalar.Style; } public YamlScalarNode() { } public YamlScalarNode(string? value) { Value = value; } internal override void ResolveAliases(DocumentLoadingState state) { throw new NotSupportedException("Resolving an alias on a scalar node does not make sense"); } internal override void Emit(IEmitter emitter, EmitterState state) { TagName tag = base.Tag; bool isPlainImplicit = tag.IsEmpty; if (forceImplicitPlain && Style == ScalarStyle.Plain && (Value == null || Value == "")) { tag = JsonSchema.Tags.Null; isPlainImplicit = true; } else if (tag.IsEmpty && Value == null && (Style == ScalarStyle.Plain || Style == ScalarStyle.Any)) { tag = JsonSchema.Tags.Null; isPlainImplicit = true; } emitter.Emit(new YamlDotNet.Core.Events.Scalar(base.Anchor, tag, Value ?? string.Empty, Style, isPlainImplicit, isQuotedImplicit: false)); } public override void Accept(IYamlVisitor visitor) { visitor.Visit(this); } public override bool Equals(object? obj) { if (obj is YamlScalarNode yamlScalarNode && object.Equals(base.Tag, yamlScalarNode.Tag)) { return object.Equals(Value, yamlScalarNode.Value); } return false; } public override int GetHashCode() { return YamlDotNet.Core.HashCode.CombineHashCodes(base.Tag.GetHashCode(), Value); } public static explicit operator string?(YamlScalarNode value) { return value.Value; } internal override string ToString(RecursionLevel level) { return Value ?? string.Empty; } internal override IEnumerable SafeAllNodes(RecursionLevel level) { yield return this; } void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) { Load(parser, new DocumentLoadingState()); } void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) { Emit(emitter, new EmitterState()); } } [DebuggerDisplay("Count = {children.Count}")] internal sealed class YamlSequenceNode : YamlNode, IEnumerable, IEnumerable, IYamlConvertible { private readonly List children = new List(); public IList Children => children; public SequenceStyle Style { get; set; } public override YamlNodeType NodeType => YamlNodeType.Sequence; internal YamlSequenceNode(IParser parser, DocumentLoadingState state) { Load(parser, state); } private void Load(IParser parser, DocumentLoadingState state) { SequenceStart sequenceStart = parser.Consume(); Load(sequenceStart, state); Style = sequenceStart.Style; bool flag = false; SequenceEnd @event; while (!parser.TryConsume(out @event)) { YamlNode yamlNode = YamlNode.ParseNode(parser, state); children.Add(yamlNode); flag = flag || yamlNode is YamlAliasNode; } if (flag) { state.AddNodeWithUnresolvedAliases(this); } } public YamlSequenceNode() { } public YamlSequenceNode(params YamlNode[] children) : this((IEnumerable)children) { } public YamlSequenceNode(IEnumerable children) { foreach (YamlNode child in children) { this.children.Add(child); } } public void Add(YamlNode child) { children.Add(child); } public void Add(string child) { children.Add(new YamlScalarNode(child)); } internal override void ResolveAliases(DocumentLoadingState state) { for (int i = 0; i < children.Count; i++) { if (children[i] is YamlAliasNode) { children[i] = state.GetNode(children[i].Anchor, children[i].Start, children[i].End); } } } internal override void Emit(IEmitter emitter, EmitterState state) { emitter.Emit(new SequenceStart(base.Anchor, base.Tag, base.Tag.IsEmpty, Style)); foreach (YamlNode child in children) { child.Save(emitter, state); } emitter.Emit(new SequenceEnd()); } public override void Accept(IYamlVisitor visitor) { visitor.Visit(this); } public override bool Equals(object? obj) { if (!(obj is YamlSequenceNode yamlSequenceNode) || !object.Equals(base.Tag, yamlSequenceNode.Tag) || children.Count != yamlSequenceNode.children.Count) { return false; } for (int i = 0; i < children.Count; i++) { if (!object.Equals(children[i], yamlSequenceNode.children[i])) { return false; } } return true; } public override int GetHashCode() { int h = 0; foreach (YamlNode child in children) { h = YamlDotNet.Core.HashCode.CombineHashCodes(h, child); } return YamlDotNet.Core.HashCode.CombineHashCodes(h, base.Tag); } internal override IEnumerable SafeAllNodes(RecursionLevel level) { level.Increment(); yield return this; foreach (YamlNode child in children) { foreach (YamlNode item in child.SafeAllNodes(level)) { yield return item; } } level.Decrement(); } internal override string ToString(RecursionLevel level) { if (!level.TryIncrement()) { return "WARNING! INFINITE RECURSION!"; } StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; builder.Append("[ "); foreach (YamlNode child in children) { if (builder.Length > 2) { builder.Append(", "); } builder.Append(child.ToString(level)); } builder.Append(" ]"); level.Decrement(); return builder.ToString(); } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } } public IEnumerator GetEnumerator() { return Children.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) { Load(parser, new DocumentLoadingState()); } void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) { Emit(emitter, new EmitterState()); } } internal class YamlStream : IEnumerable, IEnumerable { private readonly List documents = new List(); public IList Documents => documents; public YamlStream() { } public YamlStream(params YamlDocument[] documents) : this((IEnumerable)documents) { } public YamlStream(IEnumerable documents) { foreach (YamlDocument document in documents) { this.documents.Add(document); } } public void Add(YamlDocument document) { documents.Add(document); } public void Load(TextReader input) { Load(new Parser(input)); } public void Load(IParser parser) { documents.Clear(); parser.Consume(); YamlDotNet.Core.Events.StreamEnd @event; while (!parser.TryConsume(out @event)) { YamlDocument item = new YamlDocument(parser); documents.Add(item); } } public void Save(TextWriter output) { Save(output, assignAnchors: true); } public void Save(TextWriter output, bool assignAnchors) { Save(new Emitter(output), assignAnchors); } public void Save(IEmitter emitter, bool assignAnchors) { emitter.Emit(new YamlDotNet.Core.Events.StreamStart()); foreach (YamlDocument document in documents) { document.Save(emitter, assignAnchors); } emitter.Emit(new YamlDotNet.Core.Events.StreamEnd()); } public void Accept(IYamlVisitor visitor) { visitor.Visit(this); } public IEnumerator GetEnumerator() { return documents.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } [Obsolete("Use YamlVisitorBase")] internal abstract class YamlVisitor : IYamlVisitor { protected virtual void Visit(YamlStream stream) { } protected virtual void Visited(YamlStream stream) { } protected virtual void Visit(YamlDocument document) { } protected virtual void Visited(YamlDocument document) { } protected virtual void Visit(YamlScalarNode scalar) { } protected virtual void Visited(YamlScalarNode scalar) { } protected virtual void Visit(YamlSequenceNode sequence) { } protected virtual void Visited(YamlSequenceNode sequence) { } protected virtual void Visit(YamlMappingNode mapping) { } protected virtual void Visited(YamlMappingNode mapping) { } protected virtual void VisitChildren(YamlStream stream) { foreach (YamlDocument document in stream.Documents) { document.Accept(this); } } protected virtual void VisitChildren(YamlDocument document) { if (document.RootNode != null) { document.RootNode.Accept(this); } } protected virtual void VisitChildren(YamlSequenceNode sequence) { foreach (YamlNode child in sequence.Children) { child.Accept(this); } } protected virtual void VisitChildren(YamlMappingNode mapping) { foreach (KeyValuePair child in mapping.Children) { child.Key.Accept(this); child.Value.Accept(this); } } void IYamlVisitor.Visit(YamlStream stream) { Visit(stream); VisitChildren(stream); Visited(stream); } void IYamlVisitor.Visit(YamlDocument document) { Visit(document); VisitChildren(document); Visited(document); } void IYamlVisitor.Visit(YamlScalarNode scalar) { Visit(scalar); Visited(scalar); } void IYamlVisitor.Visit(YamlSequenceNode sequence) { Visit(sequence); VisitChildren(sequence); Visited(sequence); } void IYamlVisitor.Visit(YamlMappingNode mapping) { Visit(mapping); VisitChildren(mapping); Visited(mapping); } } internal abstract class YamlVisitorBase : IYamlVisitor { public virtual void Visit(YamlStream stream) { VisitChildren(stream); } public virtual void Visit(YamlDocument document) { VisitChildren(document); } public virtual void Visit(YamlScalarNode scalar) { } public virtual void Visit(YamlSequenceNode sequence) { VisitChildren(sequence); } public virtual void Visit(YamlMappingNode mapping) { VisitChildren(mapping); } protected virtual void VisitPair(YamlNode key, YamlNode value) { key.Accept(this); value.Accept(this); } protected virtual void VisitChildren(YamlStream stream) { foreach (YamlDocument document in stream.Documents) { document.Accept(this); } } protected virtual void VisitChildren(YamlDocument document) { if (document.RootNode != null) { document.RootNode.Accept(this); } } protected virtual void VisitChildren(YamlSequenceNode sequence) { foreach (YamlNode child in sequence.Children) { child.Accept(this); } } protected virtual void VisitChildren(YamlMappingNode mapping) { foreach (KeyValuePair child in mapping.Children) { VisitPair(child.Key, child.Value); } } } } namespace YamlDotNet.Helpers { internal class DefaultFsharpHelper : IFsharpHelper { private static bool IsFsharpCore(Type t) { return t.Namespace == "Microsoft.FSharp.Core"; } public bool IsOptionType(Type t) { if (IsFsharpCore(t)) { return t.Name == "FSharpOption`1"; } return false; } public Type? GetOptionUnderlyingType(Type t) { if (!t.IsGenericType || !IsOptionType(t)) { return null; } return t.GenericTypeArguments[0]; } public object? GetValue(IObjectDescriptor objectDescriptor) { if (!IsOptionType(objectDescriptor.Type)) { throw new InvalidOperationException("Should not be called on non-Option<> type"); } if (objectDescriptor.Value == null) { return null; } return objectDescriptor.Type.GetProperty("Value").GetValue(objectDescriptor.Value); } public bool IsFsharpListType(Type t) { if (t.Namespace == "Microsoft.FSharp.Collections") { return t.Name == "FSharpList`1"; } return false; } public object? CreateFsharpListFromArray(Type t, Type itemsType, Array arr) { if (!IsFsharpListType(t)) { return null; } return t.Assembly.GetType("Microsoft.FSharp.Collections.ListModule").GetMethod("OfArray").MakeGenericMethod(itemsType) .Invoke(null, new object[1] { arr }); } } internal static class DictionaryExtensions { public static bool TryAdd(this Dictionary dictionary, T key, V value) { if (dictionary.ContainsKey(key)) { return false; } dictionary.Add(key, value); return true; } public static TValue GetOrAdd(this ConcurrentDictionary dictionary, TKey key, Func valueFactory, TArg arg) { if (dictionary == null) { throw new ArgumentNullException("dictionary"); } if (key == null) { throw new ArgumentNullException("key"); } if (valueFactory == null) { throw new ArgumentNullException("valueFactory"); } TValue value; do { if (dictionary.TryGetValue(key, out value)) { return value; } value = valueFactory(key, arg); } while (!dictionary.TryAdd(key, value)); return value; } } internal static class ExpressionExtensions { public static PropertyInfo AsProperty(this LambdaExpression propertyAccessor) { PropertyInfo propertyInfo = TryGetMemberExpression(propertyAccessor); if (propertyInfo == null) { throw new ArgumentException("Expected a lambda expression in the form: x => x.SomeProperty", "propertyAccessor"); } return propertyInfo; } [return: MaybeNull] private static TMemberInfo TryGetMemberExpression(LambdaExpression lambdaExpression) where TMemberInfo : MemberInfo { if (lambdaExpression.Parameters.Count != 1) { return null; } Expression expression = lambdaExpression.Body; if (expression is UnaryExpression unaryExpression) { if (unaryExpression.NodeType != ExpressionType.Convert) { return null; } expression = unaryExpression.Operand; } if (expression is MemberExpression memberExpression) { if (memberExpression.Expression != lambdaExpression.Parameters[0]) { return null; } return memberExpression.Member as TMemberInfo; } return null; } } internal static class FsharpHelper { public static IFsharpHelper? Instance { get; set; } public static bool IsOptionType(Type t) { return Instance?.IsOptionType(t) ?? false; } public static Type? GetOptionUnderlyingType(Type t) { return Instance?.GetOptionUnderlyingType(t); } public static object? GetValue(IObjectDescriptor objectDescriptor) { return Instance?.GetValue(objectDescriptor); } public static bool IsFsharpListType(Type t) { return Instance?.IsFsharpListType(t) ?? false; } public static object? CreateFsharpListFromArray(Type t, Type itemsType, Array arr) { return Instance?.CreateFsharpListFromArray(t, itemsType, arr); } } internal sealed class GenericCollectionToNonGenericAdapter : IList, ICollection, IEnumerable { private readonly ICollection genericCollection; public bool IsFixedSize { get { throw new NotSupportedException(); } } public bool IsReadOnly { get { throw new NotSupportedException(); } } public object? this[int index] { get { throw new NotSupportedException(); } set { ((IList)genericCollection)[index] = (T)value; } } public int Count { get { throw new NotSupportedException(); } } public bool IsSynchronized { get { throw new NotSupportedException(); } } public object SyncRoot { get { throw new NotSupportedException(); } } public GenericCollectionToNonGenericAdapter(ICollection genericCollection) { this.genericCollection = genericCollection ?? throw new ArgumentNullException("genericCollection"); } public int Add(object? value) { int count = genericCollection.Count; genericCollection.Add((T)value); return count; } public void Clear() { genericCollection.Clear(); } public bool Contains(object? value) { throw new NotSupportedException(); } public int IndexOf(object? value) { throw new NotSupportedException(); } public void Insert(int index, object? value) { throw new NotSupportedException(); } public void Remove(object? value) { throw new NotSupportedException(); } public void RemoveAt(int index) { throw new NotSupportedException(); } public void CopyTo(Array array, int index) { throw new NotSupportedException(); } public IEnumerator GetEnumerator() { return genericCollection.GetEnumerator(); } } internal sealed class GenericDictionaryToNonGenericAdapter : IDictionary, ICollection, IEnumerable where TKey : notnull { private class DictionaryEnumerator : IDictionaryEnumerator, IEnumerator { private readonly IEnumerator> enumerator; public DictionaryEntry Entry => new DictionaryEntry(Key, Value); public object Key => enumerator.Current.Key; public object? Value => enumerator.Current.Value; public object Current => Entry; public DictionaryEnumerator(IEnumerator> enumerator) { this.enumerator = enumerator; } public bool MoveNext() { return enumerator.MoveNext(); } public void Reset() { enumerator.Reset(); } } private readonly IDictionary genericDictionary; public bool IsFixedSize { get { throw new NotSupportedException(); } } public bool IsReadOnly { get { throw new NotSupportedException(); } } public ICollection Keys { get { throw new NotSupportedException(); } } public ICollection Values { get { throw new NotSupportedException(); } } public object? this[object key] { get { throw new NotSupportedException(); } set { genericDictionary[(TKey)key] = (TValue)value; } } public int Count { get { throw new NotSupportedException(); } } public bool IsSynchronized { get { throw new NotSupportedException(); } } public object SyncRoot { get { throw new NotSupportedException(); } } public GenericDictionaryToNonGenericAdapter(IDictionary genericDictionary) { this.genericDictionary = genericDictionary ?? throw new ArgumentNullException("genericDictionary"); } public void Add(object key, object? value) { throw new NotSupportedException(); } public void Clear() { throw new NotSupportedException(); } public bool Contains(object key) { throw new NotSupportedException(); } public IDictionaryEnumerator GetEnumerator() { return new DictionaryEnumerator(genericDictionary.GetEnumerator()); } public void Remove(object key) { throw new NotSupportedException(); } public void CopyTo(Array array, int index) { throw new NotSupportedException(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } internal interface IFsharpHelper { bool IsOptionType(Type t); Type? GetOptionUnderlyingType(Type t); object? GetValue(IObjectDescriptor objectDescriptor); bool IsFsharpListType(Type t); object? CreateFsharpListFromArray(Type t, Type itemsType, Array arr); } internal interface IOrderedDictionary : IDictionary, ICollection>, IEnumerable>, IEnumerable where TKey : notnull { KeyValuePair this[int index] { get; set; } void Insert(int index, TKey key, TValue value); void RemoveAt(int index); } internal class NullFsharpHelper : IFsharpHelper { public object? CreateFsharpListFromArray(Type t, Type itemsType, Array arr) { return null; } public Type? GetOptionUnderlyingType(Type t) { return null; } public object? GetValue(IObjectDescriptor objectDescriptor) { return null; } public bool IsFsharpListType(Type t) { return false; } public bool IsOptionType(Type t) { return false; } } internal static class NumberExtensions { public static bool IsPowerOfTwo(this int value) { return (value & (value - 1)) == 0; } } [Serializable] internal sealed class OrderedDictionary : IOrderedDictionary, IDictionary, ICollection>, IEnumerable>, IEnumerable where TKey : notnull { private class KeyCollection : ICollection, IEnumerable, IEnumerable { private readonly OrderedDictionary orderedDictionary; public int Count => orderedDictionary.list.Count; public bool IsReadOnly => true; public void Add(TKey item) { throw new NotSupportedException(); } public void Clear() { throw new NotSupportedException(); } public bool Contains(TKey item) { return orderedDictionary.dictionary.ContainsKey(item); } public KeyCollection(OrderedDictionary orderedDictionary) { this.orderedDictionary = orderedDictionary; } public void CopyTo(TKey[] array, int arrayIndex) { for (int i = 0; i < orderedDictionary.list.Count; i++) { array[i] = orderedDictionary.list[i + arrayIndex].Key; } } public IEnumerator GetEnumerator() { return orderedDictionary.list.Select((KeyValuePair kvp) => kvp.Key).GetEnumerator(); } public bool Remove(TKey item) { throw new NotSupportedException(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } private class ValueCollection : ICollection, IEnumerable, IEnumerable { private readonly OrderedDictionary orderedDictionary; public int Count => orderedDictionary.list.Count; public bool IsReadOnly => true; public void Add(TValue item) { throw new NotSupportedException(); } public void Clear() { throw new NotSupportedException(); } public bool Contains(TValue item) { return orderedDictionary.dictionary.ContainsValue(item); } public ValueCollection(OrderedDictionary orderedDictionary) { this.orderedDictionary = orderedDictionary; } public void CopyTo(TValue[] array, int arrayIndex) { for (int i = 0; i < orderedDictionary.list.Count; i++) { array[i] = orderedDictionary.list[i + arrayIndex].Value; } } public IEnumerator GetEnumerator() { return orderedDictionary.list.Select((KeyValuePair kvp) => kvp.Value).GetEnumerator(); } public bool Remove(TValue item) { throw new NotSupportedException(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } [NonSerialized] private Dictionary dictionary; private readonly List> list; private readonly IEqualityComparer comparer; public TValue this[TKey key] { get { return dictionary[key]; } set { if (dictionary.ContainsKey(key)) { int index = list.FindIndex((KeyValuePair kvp) => comparer.Equals(kvp.Key, key)); dictionary[key] = value; list[index] = new KeyValuePair(key, value); } else { Add(key, value); } } } public ICollection Keys => new KeyCollection(this); public ICollection Values => new ValueCollection(this); public int Count => dictionary.Count; public bool IsReadOnly => false; public KeyValuePair this[int index] { get { return list[index]; } set { list[index] = value; } } public OrderedDictionary() : this((IEqualityComparer)EqualityComparer.Default) { } public OrderedDictionary(IEqualityComparer comparer) { list = new List>(); dictionary = new Dictionary(comparer); this.comparer = comparer; } public void Add(KeyValuePair item) { if (!TryAdd(item)) { ThrowDuplicateKeyException(item.Key); } } public void Add(TKey key, TValue value) { if (!TryAdd(key, value)) { ThrowDuplicateKeyException(key); } } private static void ThrowDuplicateKeyException(TKey key) { throw new ArgumentException($"An item with the same key {key} has already been added."); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryAdd(TKey key, TValue value) { if (DictionaryExtensions.TryAdd(dictionary, key, value)) { list.Add(new KeyValuePair(key, value)); return true; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryAdd(KeyValuePair item) { if (DictionaryExtensions.TryAdd(dictionary, item.Key, item.Value)) { list.Add(item); return true; } return false; } public void Clear() { dictionary.Clear(); list.Clear(); } public bool Contains(KeyValuePair item) { return dictionary.Contains(item); } public bool ContainsKey(TKey key) { return dictionary.ContainsKey(key); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { list.CopyTo(array, arrayIndex); } public IEnumerator> GetEnumerator() { return list.GetEnumerator(); } public void Insert(int index, TKey key, TValue value) { dictionary.Add(key, value); list.Insert(index, new KeyValuePair(key, value)); } public bool Remove(TKey key) { if (dictionary.ContainsKey(key)) { int index = list.FindIndex((KeyValuePair kvp) => comparer.Equals(kvp.Key, key)); list.RemoveAt(index); if (!dictionary.Remove(key)) { throw new InvalidOperationException(); } return true; } return false; } public bool Remove(KeyValuePair item) { return Remove(item.Key); } public void RemoveAt(int index) { TKey key = list[index].Key; dictionary.Remove(key); list.RemoveAt(index); } public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { return dictionary.TryGetValue(key, out value); } IEnumerator IEnumerable.GetEnumerator() { return list.GetEnumerator(); } [System.Runtime.Serialization.OnDeserialized] internal void OnDeserializedMethod(StreamingContext context) { dictionary = new Dictionary(); foreach (KeyValuePair item in list) { dictionary[item.Key] = item.Value; } } } internal static class ReadOnlyCollectionExtensions { public static IReadOnlyList AsReadonlyList(this List list) { return list; } public static IReadOnlyDictionary AsReadonlyDictionary(this Dictionary dictionary) where TKey : notnull { return dictionary; } } internal static class ThrowHelper { [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowArgumentOutOfRangeException(string paramName, string message) { throw new ArgumentOutOfRangeException(paramName, message); } } } namespace YamlDotNet.Core { internal readonly struct AnchorName : IEquatable { public static readonly AnchorName Empty; private static readonly Regex AnchorPattern = new Regex("^[^\\[\\]\\{\\},]+$", RegexOptions.Compiled); private readonly string? value; public string Value => value ?? throw new InvalidOperationException("Cannot read the Value of an empty anchor"); public bool IsEmpty => value == null; public AnchorName(string value) { this.value = value ?? throw new ArgumentNullException("value"); if (!AnchorPattern.IsMatch(value)) { throw new ArgumentException("Anchor cannot be empty or contain disallowed characters: []{},\nThe value was '" + value + "'.", "value"); } } public override string ToString() { return value ?? "[empty]"; } public bool Equals(AnchorName other) { return object.Equals(value, other.value); } public override bool Equals(object? obj) { if (obj is AnchorName other) { return Equals(other); } return false; } public override int GetHashCode() { return value?.GetHashCode() ?? 0; } public static bool operator ==(AnchorName left, AnchorName right) { return left.Equals(right); } public static bool operator !=(AnchorName left, AnchorName right) { return !(left == right); } public static implicit operator AnchorName(string? value) { if (value != null) { return new AnchorName(value); } return Empty; } } internal class AnchorNotFoundException : YamlException { public AnchorNotFoundException(string message) : base(message) { } public AnchorNotFoundException(in Mark start, in Mark end, string message) : base(in start, in end, message) { } public AnchorNotFoundException(string message, Exception inner) : base(message, inner) { } } [DebuggerStepThrough] internal readonly struct CharacterAnalyzer where TBuffer : ILookAheadBuffer { public TBuffer Buffer { get; } public bool EndOfInput => Buffer.EndOfInput; public CharacterAnalyzer(TBuffer buffer) { if (buffer == null) { throw new ArgumentNullException("buffer"); } Buffer = buffer; } public char Peek(int offset) { return Buffer.Peek(offset); } public void Skip(int length) { Buffer.Skip(length); } public bool IsAlphaNumericDashOrUnderscore(int offset = 0) { char c = Buffer.Peek(offset); if ((c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && c != '_') { return c == '-'; } return true; } public bool IsAscii(int offset = 0) { return Buffer.Peek(offset) <= '\u007f'; } public bool IsPrintable(int offset = 0) { char c = Buffer.Peek(offset); switch (c) { default: if (c != '\u0085' && (c < '\u00a0' || c > '\ud7ff')) { if (c >= '\ue000') { return c <= '\ufffd'; } return false; } break; case '\t': case '\n': case '\r': case ' ': case '!': case '"': case '#': case '$': case '%': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': case '-': case '.': case '/': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case ':': case ';': case '<': case '=': case '>': case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '[': case '\\': case ']': case '^': case '_': case '`': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': break; } return true; } public bool IsDigit(int offset = 0) { char c = Buffer.Peek(offset); if (c >= '0') { return c <= '9'; } return false; } public int AsDigit(int offset = 0) { return Buffer.Peek(offset) - 48; } public bool IsHex(int offset) { char c = Buffer.Peek(offset); if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) { if (c >= 'a') { return c <= 'f'; } return false; } return true; } public int AsHex(int offset) { char c = Buffer.Peek(offset); if (c <= '9') { return c - 48; } if (c <= 'F') { return c - 65 + 10; } return c - 97 + 10; } public bool IsSpace(int offset = 0) { return Check(' ', offset); } public bool IsZero(int offset = 0) { return Check('\0', offset); } public bool IsTab(int offset = 0) { return Check('\t', offset); } public bool IsWhite(int offset = 0) { if (!IsSpace(offset)) { return IsTab(offset); } return true; } public bool IsBreak(int offset = 0) { return Check("\r\n\u0085\u2028\u2029", offset); } public bool IsCrLf(int offset = 0) { if (Check('\r', offset)) { return Check('\n', offset + 1); } return false; } public bool IsBreakOrZero(int offset = 0) { if (!IsBreak(offset)) { return IsZero(offset); } return true; } public bool IsWhiteBreakOrZero(int offset = 0) { if (!IsWhite(offset)) { return IsBreakOrZero(offset); } return true; } public bool Check(char expected, int offset = 0) { return Buffer.Peek(offset) == expected; } public bool Check(string expectedCharacters, int offset = 0) { char c = Buffer.Peek(offset); return Polyfills.Contains(expectedCharacters, c); } } internal static class Constants { public static readonly TagDirective[] DefaultTagDirectives = new TagDirective[2] { new TagDirective("!", "!"), new TagDirective("!!", "tag:yaml.org,2002:") }; public const int MajorVersion = 1; public const int MinorVersion = 3; } [DebuggerStepThrough] internal sealed class Cursor { public long Index { get; private set; } public long Line { get; private set; } public long LineOffset { get; private set; } public Cursor() { Line = 1L; } public Cursor(Cursor cursor) { Index = cursor.Index; Line = cursor.Line; LineOffset = cursor.LineOffset; } public Mark Mark() { return new Mark(Index, Line, LineOffset + 1); } public void Skip() { Index++; LineOffset++; } public void SkipLineByOffset(int offset) { Index += offset; Line++; LineOffset = 0L; } public void ForceSkipLineAfterNonBreak() { if (LineOffset != 0L) { Line++; LineOffset = 0L; } } } internal class Emitter : IEmitter { private class AnchorData { public AnchorName Anchor; public bool IsAlias; } private class TagData { public string? Handle; public string? Suffix; } private class ScalarData { public string Value = string.Empty; public bool IsMultiline; public bool IsFlowPlainAllowed; public bool IsBlockPlainAllowed; public bool IsSingleQuotedAllowed; public bool IsBlockAllowed; public bool HasSingleQuotes; public ScalarStyle Style; } private static readonly Regex UriReplacer = new Regex("[^0-9A-Za-z_\\-;?@=$~\\\\\\)\\]/:&+,\\.\\*\\(\\[!]", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly string[] NewLineSeparators = new string[3] { "\r\n", "\r", "\n" }; private readonly TextWriter output; private readonly bool outputUsesUnicodeEncoding; private readonly int maxSimpleKeyLength; private readonly bool isCanonical; private readonly bool skipAnchorName; private readonly int bestIndent; private readonly int bestWidth; private EmitterState state; private readonly Stack states = new Stack(); private readonly Queue events = new Queue(); private readonly Stack indents = new Stack(); private readonly TagDirectiveCollection tagDirectives = new TagDirectiveCollection(); private int indent; private int flowLevel; private bool isMappingContext; private bool isSimpleKeyContext; private int column; private bool isWhitespace; private bool isIndentation; private readonly bool forceIndentLess; private readonly bool useUtf16SurrogatePair; private bool isDocumentEndWritten; private readonly AnchorData anchorData = new AnchorData(); private readonly TagData tagData = new TagData(); private readonly ScalarData scalarData = new ScalarData(); public Emitter(TextWriter output) : this(output, EmitterSettings.Default) { } public Emitter(TextWriter output, int bestIndent) : this(output, bestIndent, int.MaxValue) { } public Emitter(TextWriter output, int bestIndent, int bestWidth) : this(output, bestIndent, bestWidth, isCanonical: false) { } public Emitter(TextWriter output, int bestIndent, int bestWidth, bool isCanonical) : this(output, new EmitterSettings(bestIndent, bestWidth, isCanonical, 1024)) { } public Emitter(TextWriter output, EmitterSettings settings) { bestIndent = settings.BestIndent; bestWidth = settings.BestWidth; isCanonical = settings.IsCanonical; maxSimpleKeyLength = settings.MaxSimpleKeyLength; skipAnchorName = settings.SkipAnchorName; forceIndentLess = !settings.IndentSequences; useUtf16SurrogatePair = settings.UseUtf16SurrogatePairs; this.output = output; this.output.NewLine = settings.NewLine; outputUsesUnicodeEncoding = IsUnicode(output.Encoding); } public void Emit(ParsingEvent @event) { events.Enqueue(@event); while (!NeedMoreEvents()) { ParsingEvent evt = events.Peek(); try { AnalyzeEvent(evt); StateMachine(evt); } finally { events.Dequeue(); } } } private bool NeedMoreEvents() { if (events.Count == 0) { return true; } int num; switch (events.Peek().Type) { case EventType.DocumentStart: num = 1; break; case EventType.SequenceStart: num = 2; break; case EventType.MappingStart: num = 3; break; default: return false; } if (events.Count > num) { return false; } int num2 = 0; foreach (ParsingEvent @event in events) { switch (@event.Type) { case EventType.DocumentStart: case EventType.SequenceStart: case EventType.MappingStart: num2++; break; case EventType.DocumentEnd: case EventType.SequenceEnd: case EventType.MappingEnd: num2--; break; } if (num2 == 0) { return false; } } return true; } private void AnalyzeEvent(ParsingEvent evt) { anchorData.Anchor = AnchorName.Empty; tagData.Handle = null; tagData.Suffix = null; if (evt is YamlDotNet.Core.Events.AnchorAlias anchorAlias) { AnalyzeAnchor(anchorAlias.Value, isAlias: true); } else if (evt is NodeEvent nodeEvent) { if (evt is YamlDotNet.Core.Events.Scalar scalar) { AnalyzeScalar(scalar); } AnalyzeAnchor(nodeEvent.Anchor, isAlias: false); if (!nodeEvent.Tag.IsEmpty && (isCanonical || nodeEvent.IsCanonical)) { AnalyzeTag(nodeEvent.Tag); } } } private void AnalyzeAnchor(AnchorName anchor, bool isAlias) { anchorData.Anchor = anchor; anchorData.IsAlias = isAlias; } private void AnalyzeScalar(YamlDotNet.Core.Events.Scalar scalar) { string value = scalar.Value; scalarData.Value = value; if (value.Length == 0) { if (scalar.Tag == "tag:yaml.org,2002:null") { scalarData.IsMultiline = false; scalarData.IsFlowPlainAllowed = false; scalarData.IsBlockPlainAllowed = true; scalarData.IsSingleQuotedAllowed = false; scalarData.IsBlockAllowed = false; } else { scalarData.IsMultiline = false; scalarData.IsFlowPlainAllowed = false; scalarData.IsBlockPlainAllowed = false; scalarData.IsSingleQuotedAllowed = true; scalarData.IsBlockAllowed = false; } return; } bool flag = false; bool flag2 = false; if (value.StartsWith("---", StringComparison.Ordinal) || value.StartsWith("...", StringComparison.Ordinal)) { flag = true; flag2 = true; } StringLookAheadBufferPool.BufferWrapper bufferWrapper = StringLookAheadBufferPool.Rent(value); try { CharacterAnalyzer characterAnalyzer = new CharacterAnalyzer(bufferWrapper.Buffer); bool flag3 = true; bool flag4 = characterAnalyzer.IsWhiteBreakOrZero(1); bool flag5 = false; bool flag6 = false; bool flag7 = false; bool flag8 = false; bool flag9 = false; bool flag10 = false; bool flag11 = false; bool flag12 = false; bool flag13 = false; bool flag14 = false; bool flag15 = false; bool flag16 = !ValueIsRepresentableInOutputEncoding(value); bool flag17 = false; bool flag18 = false; bool flag19 = true; while (!characterAnalyzer.EndOfInput) { if (flag19) { if (characterAnalyzer.Check("#,[]{}&*!|>\"%@`'")) { flag = true; flag2 = true; flag9 = characterAnalyzer.Check('\''); flag17 |= characterAnalyzer.Check('\''); } if (characterAnalyzer.Check("?:")) { flag = true; if (flag4) { flag2 = true; } } if (characterAnalyzer.Check('-') && flag4) { flag = true; flag2 = true; } } else { if (characterAnalyzer.Check(",?[]{}")) { flag = true; } if (characterAnalyzer.Check(':')) { flag = true; if (flag4) { flag2 = true; } } if (characterAnalyzer.Check('#') && flag3) { flag = true; flag2 = true; } flag17 |= characterAnalyzer.Check('\''); } if (!flag16 && !characterAnalyzer.IsPrintable()) { flag16 = true; } if (characterAnalyzer.IsBreak()) { flag15 = true; } if (characterAnalyzer.IsSpace()) { if (flag19) { flag5 = true; } if (characterAnalyzer.Buffer.Position >= characterAnalyzer.Buffer.Length - 1) { flag7 = true; } if (flag13) { flag10 = true; flag14 = true; } flag12 = true; flag13 = false; } else if (characterAnalyzer.IsBreak()) { if (flag19) { flag6 = true; } if (characterAnalyzer.Buffer.Position >= characterAnalyzer.Buffer.Length - 1) { flag8 = true; } if (flag12) { flag11 = true; } if (flag14) { flag18 = true; } flag12 = false; flag13 = true; } else { flag12 = false; flag13 = false; flag14 = false; } flag3 = characterAnalyzer.IsWhiteBreakOrZero(); characterAnalyzer.Skip(1); if (!characterAnalyzer.EndOfInput) { flag4 = characterAnalyzer.IsWhiteBreakOrZero(1); } flag19 = false; } scalarData.IsFlowPlainAllowed = true; scalarData.IsBlockPlainAllowed = true; scalarData.IsSingleQuotedAllowed = true; scalarData.IsBlockAllowed = true; if (flag5 || flag6 || flag7 || flag8 || flag9) { scalarData.IsFlowPlainAllowed = false; scalarData.IsBlockPlainAllowed = false; } if (flag7) { scalarData.IsBlockAllowed = false; } if (flag10) { scalarData.IsFlowPlainAllowed = false; scalarData.IsBlockPlainAllowed = false; scalarData.IsSingleQuotedAllowed = false; } if (flag11 || flag16) { scalarData.IsFlowPlainAllowed = false; scalarData.IsBlockPlainAllowed = false; scalarData.IsSingleQuotedAllowed = false; } if (flag18) { scalarData.IsBlockAllowed = false; } scalarData.IsMultiline = flag15; if (flag15) { scalarData.IsFlowPlainAllowed = false; scalarData.IsBlockPlainAllowed = false; } if (flag) { scalarData.IsFlowPlainAllowed = false; } if (flag2) { scalarData.IsBlockPlainAllowed = false; } scalarData.HasSingleQuotes = flag17; } finally { ((IDisposable)bufferWrapper/*cast due to .constrained prefix*/).Dispose(); } } private bool ValueIsRepresentableInOutputEncoding(string value) { if (outputUsesUnicodeEncoding) { return true; } try { byte[] bytes = output.Encoding.GetBytes(value); string text = output.Encoding.GetString(bytes, 0, bytes.Length); return text.Equals(value); } catch (EncoderFallbackException) { return false; } catch (ArgumentOutOfRangeException) { return false; } } private static bool IsUnicode(Encoding encoding) { if (!(encoding is UTF8Encoding) && !(encoding is UnicodeEncoding)) { return encoding is UTF7Encoding; } return true; } private void AnalyzeTag(TagName tag) { tagData.Handle = tag.Value; foreach (TagDirective tagDirective in tagDirectives) { if (tag.Value.StartsWith(tagDirective.Prefix, StringComparison.Ordinal)) { tagData.Handle = tagDirective.Handle; tagData.Suffix = tag.Value.Substring(tagDirective.Prefix.Length); break; } } } private void StateMachine(ParsingEvent evt) { if (evt is YamlDotNet.Core.Events.Comment comment) { EmitComment(comment); return; } switch (state) { case EmitterState.StreamStart: EmitStreamStart(evt); break; case EmitterState.FirstDocumentStart: EmitDocumentStart(evt, isFirst: true); break; case EmitterState.DocumentStart: EmitDocumentStart(evt, isFirst: false); break; case EmitterState.DocumentContent: EmitDocumentContent(evt); break; case EmitterState.DocumentEnd: EmitDocumentEnd(evt); break; case EmitterState.FlowSequenceFirstItem: EmitFlowSequenceItem(evt, isFirst: true); break; case EmitterState.FlowSequenceItem: EmitFlowSequenceItem(evt, isFirst: false); break; case EmitterState.FlowMappingFirstKey: EmitFlowMappingKey(evt, isFirst: true); break; case EmitterState.FlowMappingKey: EmitFlowMappingKey(evt, isFirst: false); break; case EmitterState.FlowMappingSimpleValue: EmitFlowMappingValue(evt, isSimple: true); break; case EmitterState.FlowMappingValue: EmitFlowMappingValue(evt, isSimple: false); break; case EmitterState.BlockSequenceFirstItem: EmitBlockSequenceItem(evt, isFirst: true); break; case EmitterState.BlockSequenceItem: EmitBlockSequenceItem(evt, isFirst: false); break; case EmitterState.BlockMappingFirstKey: EmitBlockMappingKey(evt, isFirst: true); break; case EmitterState.BlockMappingKey: EmitBlockMappingKey(evt, isFirst: false); break; case EmitterState.BlockMappingSimpleValue: EmitBlockMappingValue(evt, isSimple: true); break; case EmitterState.BlockMappingValue: EmitBlockMappingValue(evt, isSimple: false); break; case EmitterState.StreamEnd: throw new YamlException("Expected nothing after STREAM-END"); default: throw new InvalidOperationException(); } } private void EmitComment(YamlDotNet.Core.Events.Comment comment) { if (flowLevel > 0 || state == EmitterState.FlowMappingFirstKey || state == EmitterState.FlowSequenceFirstItem) { return; } string[] array = comment.Value.Split(NewLineSeparators, StringSplitOptions.None); if (comment.IsInline) { Write(" # "); Write(string.Join(" ", array)); } else { bool flag = state == EmitterState.BlockMappingFirstKey; if (flag) { IncreaseIndent(isFlow: false, isIndentless: false); } string[] array2 = array; foreach (string value in array2) { WriteIndent(); Write("# "); Write(value); WriteBreak(); } if (flag) { indent = indents.Pop(); } } isIndentation = true; } private void EmitStreamStart(ParsingEvent evt) { if (!(evt is YamlDotNet.Core.Events.StreamStart)) { throw new ArgumentException("Expected STREAM-START.", "evt"); } indent = -1; column = 0; isWhitespace = true; isIndentation = true; state = EmitterState.FirstDocumentStart; } private void EmitDocumentStart(ParsingEvent evt, bool isFirst) { if (evt is YamlDotNet.Core.Events.DocumentStart documentStart) { bool flag = documentStart.IsImplicit && isFirst && !isCanonical; TagDirectiveCollection tagDirectiveCollection = NonDefaultTagsAmong(documentStart.Tags); if (!isFirst && !isDocumentEndWritten && (documentStart.Version != null || tagDirectiveCollection.Count > 0)) { isDocumentEndWritten = false; WriteIndicator("...", needWhitespace: true, whitespace: false, indentation: false); WriteIndent(); } if (documentStart.Version != null) { AnalyzeVersionDirective(documentStart.Version); Version version = documentStart.Version.Version; flag = false; WriteIndicator("%YAML", needWhitespace: true, whitespace: false, indentation: false); WriteIndicator(string.Format(CultureInfo.InvariantCulture, "{0}.{1}", version.Major, version.Minor), needWhitespace: true, whitespace: false, indentation: false); WriteIndent(); } foreach (TagDirective item in tagDirectiveCollection) { AppendTagDirectiveTo(item, allowDuplicates: false, tagDirectives); } TagDirective[] defaultTagDirectives = Constants.DefaultTagDirectives; foreach (TagDirective value in defaultTagDirectives) { AppendTagDirectiveTo(value, allowDuplicates: true, tagDirectives); } if (tagDirectiveCollection.Count > 0) { flag = false; TagDirective[] defaultTagDirectives2 = Constants.DefaultTagDirectives; foreach (TagDirective value2 in defaultTagDirectives2) { AppendTagDirectiveTo(value2, allowDuplicates: true, tagDirectiveCollection); } foreach (TagDirective item2 in tagDirectiveCollection) { WriteIndicator("%TAG", needWhitespace: true, whitespace: false, indentation: false); WriteTagHandle(item2.Handle); WriteTagContent(item2.Prefix, needsWhitespace: true); WriteIndent(); } } if (CheckEmptyDocument()) { flag = false; } if (!flag) { WriteIndent(); WriteIndicator("---", needWhitespace: true, whitespace: false, indentation: false); if (isCanonical) { WriteIndent(); } } state = EmitterState.DocumentContent; } else { if (!(evt is YamlDotNet.Core.Events.StreamEnd)) { throw new YamlException("Expected DOCUMENT-START or STREAM-END"); } state = EmitterState.StreamEnd; } } private static TagDirectiveCollection NonDefaultTagsAmong(IEnumerable? tagCollection) { TagDirectiveCollection tagDirectiveCollection = new TagDirectiveCollection(); if (tagCollection == null) { return tagDirectiveCollection; } foreach (TagDirective item2 in tagCollection) { AppendTagDirectiveTo(item2, allowDuplicates: false, tagDirectiveCollection); } TagDirective[] defaultTagDirectives = Constants.DefaultTagDirectives; foreach (TagDirective item in defaultTagDirectives) { tagDirectiveCollection.Remove(item); } return tagDirectiveCollection; } private static void AnalyzeVersionDirective(VersionDirective versionDirective) { if (versionDirective.Version.Major != 1 || versionDirective.Version.Minor > 3) { throw new YamlException("Incompatible %YAML directive"); } } private static void AppendTagDirectiveTo(TagDirective value, bool allowDuplicates, TagDirectiveCollection tagDirectives) { if (tagDirectives.Contains(value)) { if (!allowDuplicates) { throw new YamlException("Duplicate %TAG directive."); } } else { tagDirectives.Add(value); } } private void EmitDocumentContent(ParsingEvent evt) { states.Push(EmitterState.DocumentEnd); EmitNode(evt, isMapping: false, isSimpleKey: false); } private void EmitNode(ParsingEvent evt, bool isMapping, bool isSimpleKey) { isMappingContext = isMapping; isSimpleKeyContext = isSimpleKey; switch (evt.Type) { case EventType.Alias: EmitAlias(); break; case EventType.Scalar: EmitScalar(evt); break; case EventType.SequenceStart: EmitSequenceStart(evt); break; case EventType.MappingStart: EmitMappingStart(evt); break; default: throw new YamlException($"Expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, got {evt.Type}"); } } private void EmitAlias() { ProcessAnchor(); state = states.Pop(); } private void EmitScalar(ParsingEvent evt) { SelectScalarStyle(evt); ProcessAnchor(); ProcessTag(); IncreaseIndent(isFlow: true, isIndentless: false); ProcessScalar(); indent = indents.Pop(); state = states.Pop(); } private void SelectScalarStyle(ParsingEvent evt) { YamlDotNet.Core.Events.Scalar scalar = (YamlDotNet.Core.Events.Scalar)evt; ScalarStyle scalarStyle = scalar.Style; bool flag = tagData.Handle == null && tagData.Suffix == null; if (flag && !scalar.IsPlainImplicit && !scalar.IsQuotedImplicit) { throw new YamlException("Neither tag nor isImplicit flags are specified."); } if (scalarStyle == ScalarStyle.Any) { scalarStyle = ((!scalarData.IsMultiline) ? ScalarStyle.Plain : ScalarStyle.Folded); } if (isCanonical) { scalarStyle = ScalarStyle.DoubleQuoted; } if (isSimpleKeyContext && scalarData.IsMultiline) { scalarStyle = ScalarStyle.DoubleQuoted; } if (scalarStyle == ScalarStyle.Plain) { if ((flowLevel != 0 && !scalarData.IsFlowPlainAllowed) || (flowLevel == 0 && !scalarData.IsBlockPlainAllowed)) { scalarStyle = ((scalarData.IsSingleQuotedAllowed && !scalarData.HasSingleQuotes) ? ScalarStyle.SingleQuoted : ScalarStyle.DoubleQuoted); } if (string.IsNullOrEmpty(scalarData.Value) && (flowLevel != 0 || isSimpleKeyContext)) { scalarStyle = ScalarStyle.SingleQuoted; } if (flag && !scalar.IsPlainImplicit) { scalarStyle = ScalarStyle.SingleQuoted; } } if (scalarStyle == ScalarStyle.SingleQuoted && !scalarData.IsSingleQuotedAllowed) { scalarStyle = ScalarStyle.DoubleQuoted; } if ((scalarStyle == ScalarStyle.Literal || scalarStyle == ScalarStyle.Folded) && (!scalarData.IsBlockAllowed || flowLevel != 0 || isSimpleKeyContext)) { scalarStyle = ScalarStyle.DoubleQuoted; } if (scalarStyle == ScalarStyle.ForcePlain) { scalarStyle = ScalarStyle.Plain; } scalarData.Style = scalarStyle; } private void ProcessScalar() { switch (scalarData.Style) { case ScalarStyle.Plain: WritePlainScalar(scalarData.Value, !isSimpleKeyContext); break; case ScalarStyle.SingleQuoted: WriteSingleQuotedScalar(scalarData.Value, !isSimpleKeyContext); break; case ScalarStyle.DoubleQuoted: WriteDoubleQuotedScalar(scalarData.Value, !isSimpleKeyContext); break; case ScalarStyle.Literal: WriteLiteralScalar(scalarData.Value); break; case ScalarStyle.Folded: WriteFoldedScalar(scalarData.Value); break; default: throw new InvalidOperationException(); } } private void WritePlainScalar(string value, bool allowBreaks) { if (!isWhitespace) { Write(' '); } bool flag = false; bool flag2 = false; for (int i = 0; i < value.Length; i++) { char c = value[i]; if (IsSpace(c)) { if (allowBreaks && !flag && column > bestWidth && i + 1 < value.Length && value[i + 1] != ' ') { WriteIndent(); } else { Write(c); } flag = true; continue; } if (IsBreak(c, out var breakChar)) { if (!flag2 && c == '\n') { WriteBreak(); } WriteBreak(breakChar); isIndentation = true; flag2 = true; continue; } if (flag2) { WriteIndent(); } Write(c); isIndentation = false; flag = false; flag2 = false; } isWhitespace = false; isIndentation = false; } private void WriteSingleQuotedScalar(string value, bool allowBreaks) { WriteIndicator("'", needWhitespace: true, whitespace: false, indentation: false); bool flag = false; bool flag2 = false; for (int i = 0; i < value.Length; i++) { char c = value[i]; if (c == ' ') { if (allowBreaks && !flag && column > bestWidth && i != 0 && i + 1 < value.Length && value[i + 1] != ' ') { WriteIndent(); } else { Write(c); } flag = true; continue; } if (IsBreak(c, out var breakChar)) { if (!flag2 && c == '\n') { WriteBreak(); } WriteBreak(breakChar); isIndentation = true; flag2 = true; continue; } if (flag2) { WriteIndent(); } if (c == '\'') { Write(c); } Write(c); isIndentation = false; flag = false; flag2 = false; } WriteIndicator("'", needWhitespace: false, whitespace: false, indentation: false); isWhitespace = false; isIndentation = false; } private void WriteDoubleQuotedScalar(string value, bool allowBreaks) { WriteIndicator("\"", needWhitespace: true, whitespace: false, indentation: false); bool flag = false; for (int i = 0; i < value.Length; i++) { char c = value[i]; if (IsPrintable(c) && !IsBreak(c, out var _)) { switch (c) { case '"': case '\\': break; case ' ': if (allowBreaks && !flag && column > bestWidth && i > 0 && i + 1 < value.Length) { WriteIndent(); if (value[i + 1] == ' ') { Write('\\'); } } else { Write(c); } flag = true; continue; default: Write(c); flag = false; continue; } } Write('\\'); switch (c) { case '\0': Write('0'); break; case '\a': Write('a'); break; case '\b': Write('b'); break; case '\t': Write('t'); break; case '\n': Write('n'); break; case '\v': Write('v'); break; case '\f': Write('f'); break; case '\r': Write('r'); break; case '\u001b': Write('e'); break; case '"': Write('"'); break; case '\\': Write('\\'); break; case '\u0085': Write('N'); break; case '\u00a0': Write('_'); break; case '\u2028': Write('L'); break; case '\u2029': Write('P'); break; default: { ushort num = c; if (num <= 255) { Write('x'); Write(num.ToString("X02", CultureInfo.InvariantCulture)); } else if (IsHighSurrogate(c)) { if (i + 1 >= value.Length || !IsLowSurrogate(value[i + 1])) { throw new SyntaxErrorException("While writing a quoted scalar, found an orphaned high surrogate."); } if (useUtf16SurrogatePair) { Write('u'); Write(num.ToString("X04", CultureInfo.InvariantCulture)); Write('\\'); Write('u'); Write(((ushort)value[i + 1]).ToString("X04", CultureInfo.InvariantCulture)); } else { Write('U'); Write(char.ConvertToUtf32(c, value[i + 1]).ToString("X08", CultureInfo.InvariantCulture)); } i++; } else { Write('u'); Write(num.ToString("X04", CultureInfo.InvariantCulture)); } break; } } flag = false; } WriteIndicator("\"", needWhitespace: false, whitespace: false, indentation: false); isWhitespace = false; isIndentation = false; } private void WriteLiteralScalar(string value) { bool flag = true; WriteIndicator("|", needWhitespace: true, whitespace: false, indentation: false); WriteBlockScalarHints(value); WriteBreak(); isIndentation = true; isWhitespace = true; for (int i = 0; i < value.Length; i++) { char c = value[i]; if (c == '\r' && i + 1 < value.Length && value[i + 1] == '\n') { continue; } if (IsBreak(c, out var breakChar)) { WriteBreak(breakChar); isIndentation = true; flag = true; continue; } if (flag) { WriteIndent(); } Write(c); isIndentation = false; flag = false; } } private void WriteFoldedScalar(string value) { bool flag = true; bool flag2 = true; WriteIndicator(">", needWhitespace: true, whitespace: false, indentation: false); WriteBlockScalarHints(value); WriteBreak(); isIndentation = true; isWhitespace = true; for (int i = 0; i < value.Length; i++) { char c = value[i]; if (IsBreak(c, out var breakChar)) { if (c == '\r' && i + 1 < value.Length && value[i + 1] == '\n') { continue; } if (!flag && !flag2 && breakChar == '\n') { int j; char breakChar2; for (j = 0; i + j < value.Length && IsBreak(value[i + j], out breakChar2); j++) { } if (i + j < value.Length && !IsBlank(value[i + j]) && !IsBreak(value[i + j], out breakChar2)) { WriteBreak(); } } WriteBreak(breakChar); isIndentation = true; flag = true; } else { if (flag) { WriteIndent(); flag2 = IsBlank(c); } if (!flag && c == ' ' && i + 1 < value.Length && value[i + 1] != ' ' && column > bestWidth) { WriteIndent(); } else { Write(c); } isIndentation = false; flag = false; } } } private static bool IsSpace(char character) { return character == ' '; } private static bool IsBreak(char character, out char breakChar) { switch (character) { case '\n': case '\r': case '\u0085': breakChar = '\n'; return true; case '\u2028': case '\u2029': breakChar = character; return true; default: breakChar = '\0'; return false; } } private static bool IsBlank(char character) { if (character != ' ') { return character == '\t'; } return true; } private static bool IsPrintable(char character) { switch (character) { default: if (character != '\u0085' && (character < '\u00a0' || character > '\ud7ff')) { if (character >= '\ue000') { return character <= '\ufffd'; } return false; } break; case '\t': case '\n': case '\r': case ' ': case '!': case '"': case '#': case '$': case '%': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': case '-': case '.': case '/': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case ':': case ';': case '<': case '=': case '>': case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '[': case '\\': case ']': case '^': case '_': case '`': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': break; } return true; } private static bool IsHighSurrogate(char c) { if ('\ud800' <= c) { return c <= '\udbff'; } return false; } private static bool IsLowSurrogate(char c) { if ('\udc00' <= c) { return c <= '\udfff'; } return false; } private void EmitSequenceStart(ParsingEvent evt) { ProcessAnchor(); ProcessTag(); SequenceStart sequenceStart = (SequenceStart)evt; if (flowLevel != 0 || isCanonical || sequenceStart.Style == SequenceStyle.Flow || CheckEmptySequence()) { state = EmitterState.FlowSequenceFirstItem; } else { state = EmitterState.BlockSequenceFirstItem; } } private void EmitMappingStart(ParsingEvent evt) { ProcessAnchor(); ProcessTag(); MappingStart mappingStart = (MappingStart)evt; if (flowLevel != 0 || isCanonical || mappingStart.Style == MappingStyle.Flow || CheckEmptyMapping()) { state = EmitterState.FlowMappingFirstKey; } else { state = EmitterState.BlockMappingFirstKey; } } private void ProcessAnchor() { if (!anchorData.Anchor.IsEmpty && !skipAnchorName) { WriteIndicator(anchorData.IsAlias ? "*" : "&", needWhitespace: true, whitespace: false, indentation: false); WriteAnchor(anchorData.Anchor); } } private void ProcessTag() { if (tagData.Handle == null && tagData.Suffix == null) { return; } if (tagData.Handle != null) { WriteTagHandle(tagData.Handle); if (tagData.Suffix != null) { WriteTagContent(tagData.Suffix, needsWhitespace: false); } } else { WriteIndicator("!<", needWhitespace: true, whitespace: false, indentation: false); WriteTagContent(tagData.Suffix, needsWhitespace: false); WriteIndicator(">", needWhitespace: false, whitespace: false, indentation: false); } } private void EmitDocumentEnd(ParsingEvent evt) { if (evt is YamlDotNet.Core.Events.DocumentEnd documentEnd) { WriteIndent(); if (!documentEnd.IsImplicit) { WriteIndicator("...", needWhitespace: true, whitespace: false, indentation: false); WriteIndent(); isDocumentEndWritten = true; } state = EmitterState.DocumentStart; tagDirectives.Clear(); return; } throw new YamlException("Expected DOCUMENT-END."); } private void EmitFlowSequenceItem(ParsingEvent evt, bool isFirst) { if (isFirst) { WriteIndicator("[", needWhitespace: true, whitespace: true, indentation: false); IncreaseIndent(isFlow: true, isIndentless: false); flowLevel++; } if (evt is SequenceEnd) { flowLevel--; indent = indents.Pop(); if (isCanonical && !isFirst) { WriteIndicator(",", needWhitespace: false, whitespace: false, indentation: false); WriteIndent(); } WriteIndicator("]", needWhitespace: false, whitespace: false, indentation: false); state = states.Pop(); } else { if (!isFirst) { WriteIndicator(",", needWhitespace: false, whitespace: false, indentation: false); } if (isCanonical || column > bestWidth) { WriteIndent(); } states.Push(EmitterState.FlowSequenceItem); EmitNode(evt, isMapping: false, isSimpleKey: false); } } private void EmitFlowMappingKey(ParsingEvent evt, bool isFirst) { if (isFirst) { WriteIndicator("{", needWhitespace: true, whitespace: true, indentation: false); IncreaseIndent(isFlow: true, isIndentless: false); flowLevel++; } if (evt is MappingEnd) { flowLevel--; indent = indents.Pop(); if (isCanonical && !isFirst) { WriteIndicator(",", needWhitespace: false, whitespace: false, indentation: false); WriteIndent(); } WriteIndicator("}", needWhitespace: false, whitespace: false, indentation: false); state = states.Pop(); return; } if (!isFirst) { WriteIndicator(",", needWhitespace: false, whitespace: false, indentation: false); } if (isCanonical || column > bestWidth) { WriteIndent(); } if (!isCanonical && CheckSimpleKey()) { states.Push(EmitterState.FlowMappingSimpleValue); EmitNode(evt, isMapping: true, isSimpleKey: true); } else { WriteIndicator("?", needWhitespace: true, whitespace: false, indentation: false); states.Push(EmitterState.FlowMappingValue); EmitNode(evt, isMapping: true, isSimpleKey: false); } } private void EmitFlowMappingValue(ParsingEvent evt, bool isSimple) { if (isSimple) { WriteIndicator(":", needWhitespace: false, whitespace: false, indentation: false); } else { if (isCanonical || column > bestWidth) { WriteIndent(); } WriteIndicator(":", needWhitespace: true, whitespace: false, indentation: false); } states.Push(EmitterState.FlowMappingKey); EmitNode(evt, isMapping: true, isSimpleKey: false); } private void EmitBlockSequenceItem(ParsingEvent evt, bool isFirst) { if (isFirst) { IncreaseIndent(isFlow: false, isMappingContext && !isIndentation); } if (evt is SequenceEnd) { indent = indents.Pop(); state = states.Pop(); return; } WriteIndent(); WriteIndicator("-", needWhitespace: true, whitespace: false, indentation: true); states.Push(EmitterState.BlockSequenceItem); EmitNode(evt, isMapping: false, isSimpleKey: false); } private void EmitBlockMappingKey(ParsingEvent evt, bool isFirst) { if (isFirst) { IncreaseIndent(isFlow: false, isIndentless: false); } if (evt is MappingEnd) { indent = indents.Pop(); state = states.Pop(); return; } WriteIndent(); if (CheckSimpleKey()) { states.Push(EmitterState.BlockMappingSimpleValue); EmitNode(evt, isMapping: true, isSimpleKey: true); WriteIndicator(":", needWhitespace: false, whitespace: false, indentation: false); } else { WriteIndicator("?", needWhitespace: true, whitespace: false, indentation: true); states.Push(EmitterState.BlockMappingValue); EmitNode(evt, isMapping: true, isSimpleKey: false); } } private void EmitBlockMappingValue(ParsingEvent evt, bool isSimple) { if (!isSimple) { WriteIndent(); WriteIndicator(":", needWhitespace: true, whitespace: false, indentation: true); } states.Push(EmitterState.BlockMappingKey); EmitNode(evt, isMapping: true, isSimpleKey: false); } private void IncreaseIndent(bool isFlow, bool isIndentless) { indents.Push(indent); if (indent < 0) { indent = (isFlow ? bestIndent : 0); } else if (!isIndentless || !forceIndentLess) { indent += bestIndent; } } private bool CheckEmptyDocument() { int num = 0; foreach (ParsingEvent @event in events) { num++; if (num == 2) { if (@event is YamlDotNet.Core.Events.Scalar scalar) { return string.IsNullOrEmpty(scalar.Value); } break; } } return false; } private bool CheckSimpleKey() { if (events.Count < 1) { return false; } int num; switch (events.Peek().Type) { case EventType.Alias: num = AnchorNameLength(anchorData.Anchor); break; case EventType.Scalar: if (scalarData.IsMultiline) { return false; } num = AnchorNameLength(anchorData.Anchor) + SafeStringLength(tagData.Handle) + SafeStringLength(tagData.Suffix) + SafeStringLength(scalarData.Value); break; case EventType.SequenceStart: if (!CheckEmptySequence()) { return false; } num = AnchorNameLength(anchorData.Anchor) + SafeStringLength(tagData.Handle) + SafeStringLength(tagData.Suffix); break; case EventType.MappingStart: if (!CheckEmptySequence()) { return false; } num = AnchorNameLength(anchorData.Anchor) + SafeStringLength(tagData.Handle) + SafeStringLength(tagData.Suffix); break; default: return false; } return num <= maxSimpleKeyLength; } private static int AnchorNameLength(AnchorName value) { if (!value.IsEmpty) { return value.Value.Length; } return 0; } private static int SafeStringLength(string? value) { return value?.Length ?? 0; } private bool CheckEmptySequence() { return CheckEmptyStructure(); } private bool CheckEmptyMapping() { return CheckEmptyStructure(); } private bool CheckEmptyStructure() where TStart : NodeEvent where TEnd : ParsingEvent { if (events.Count < 2) { return false; } using Queue.Enumerator enumerator = events.GetEnumerator(); return enumerator.MoveNext() && enumerator.Current is TStart && enumerator.MoveNext() && enumerator.Current is TEnd; } private void WriteBlockScalarHints(string value) { StringLookAheadBufferPool.BufferWrapper bufferWrapper = StringLookAheadBufferPool.Rent(value); try { CharacterAnalyzer characterAnalyzer = new CharacterAnalyzer(bufferWrapper.Buffer); if (characterAnalyzer.IsSpace() || characterAnalyzer.IsBreak()) { int num = bestIndent; string indicator = num.ToString(CultureInfo.InvariantCulture); WriteIndicator(indicator, needWhitespace: false, whitespace: false, indentation: false); } string text = null; if (value.Length == 0 || !characterAnalyzer.IsBreak(value.Length - 1)) { text = "-"; } else if (value.Length >= 2 && characterAnalyzer.IsBreak(value.Length - 2)) { text = "+"; } if (text != null) { WriteIndicator(text, needWhitespace: false, whitespace: false, indentation: false); } } finally { ((IDisposable)bufferWrapper/*cast due to .constrained prefix*/).Dispose(); } } private void WriteIndicator(string indicator, bool needWhitespace, bool whitespace, bool indentation) { if (needWhitespace && !isWhitespace) { Write(' '); } Write(indicator); isWhitespace = whitespace; isIndentation &= indentation; } private void WriteIndent() { int num = Math.Max(indent, 0); if (!isIndentation || column > num || (column == num && !isWhitespace)) { WriteBreak(); } while (column < num) { Write(' '); } isWhitespace = true; isIndentation = true; } private void WriteAnchor(AnchorName value) { Write(value.Value); isWhitespace = false; isIndentation = false; } private void WriteTagHandle(string value) { if (!isWhitespace) { Write(' '); } Write(value); isWhitespace = false; isIndentation = false; } private void WriteTagContent(string value, bool needsWhitespace) { if (needsWhitespace && !isWhitespace) { Write(' '); } Write(UrlEncode(value)); isWhitespace = false; isIndentation = false; } private static string UrlEncode(string text) { return UriReplacer.Replace(text, delegate(Match match) { StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; byte[] bytes = Encoding.UTF8.GetBytes(match.Value); foreach (byte b in bytes) { builder.AppendFormat(CultureInfo.InvariantCulture, "%{0:X02}", b); } return builder.ToString(); } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } }); } private void Write(char value) { output.Write(value); column++; } private void Write(string value) { output.Write(value); column += value.Length; } private void WriteBreak(char breakCharacter = '\n') { if (breakCharacter == '\n') { output.WriteLine(); } else { output.Write(breakCharacter); } column = 0; } } internal sealed class EmitterSettings { public static readonly EmitterSettings Default = new EmitterSettings(); public int BestIndent { get; } = 2; public int BestWidth { get; } = int.MaxValue; public string NewLine { get; } = Environment.NewLine; public bool IsCanonical { get; } public bool SkipAnchorName { get; private set; } public int MaxSimpleKeyLength { get; } = 1024; public bool IndentSequences { get; } public bool UseUtf16SurrogatePairs { get; } public EmitterSettings() { } public EmitterSettings(int bestIndent, int bestWidth, bool isCanonical, int maxSimpleKeyLength, bool skipAnchorName = false, bool indentSequences = false, string? newLine = null, bool useUtf16SurrogatePairs = false) { if (bestIndent < 2 || bestIndent > 9) { throw new ArgumentOutOfRangeException("bestIndent", "BestIndent must be between 2 and 9, inclusive"); } if (bestWidth <= bestIndent * 2) { throw new ArgumentOutOfRangeException("bestWidth", "BestWidth must be greater than BestIndent x 2."); } if (maxSimpleKeyLength < 0) { throw new ArgumentOutOfRangeException("maxSimpleKeyLength", "MaxSimpleKeyLength must be >= 0"); } BestIndent = bestIndent; BestWidth = bestWidth; IsCanonical = isCanonical; MaxSimpleKeyLength = maxSimpleKeyLength; SkipAnchorName = skipAnchorName; IndentSequences = indentSequences; NewLine = newLine ?? Environment.NewLine; UseUtf16SurrogatePairs = useUtf16SurrogatePairs; } public EmitterSettings WithBestIndent(int bestIndent) { return new EmitterSettings(bestIndent, BestWidth, IsCanonical, MaxSimpleKeyLength, SkipAnchorName, IndentSequences, NewLine, UseUtf16SurrogatePairs); } public EmitterSettings WithBestWidth(int bestWidth) { return new EmitterSettings(BestIndent, bestWidth, IsCanonical, MaxSimpleKeyLength, SkipAnchorName, IndentSequences, NewLine, UseUtf16SurrogatePairs); } public EmitterSettings WithMaxSimpleKeyLength(int maxSimpleKeyLength) { return new EmitterSettings(BestIndent, BestWidth, IsCanonical, maxSimpleKeyLength, SkipAnchorName, IndentSequences, NewLine, UseUtf16SurrogatePairs); } public EmitterSettings WithNewLine(string newLine) { return new EmitterSettings(BestIndent, BestWidth, IsCanonical, MaxSimpleKeyLength, SkipAnchorName, IndentSequences, newLine, UseUtf16SurrogatePairs); } public EmitterSettings Canonical() { return new EmitterSettings(BestIndent, BestWidth, isCanonical: true, MaxSimpleKeyLength, SkipAnchorName); } public EmitterSettings WithoutAnchorName() { return new EmitterSettings(BestIndent, BestWidth, IsCanonical, MaxSimpleKeyLength, skipAnchorName: true, IndentSequences, NewLine, UseUtf16SurrogatePairs); } public EmitterSettings WithIndentedSequences() { return new EmitterSettings(BestIndent, BestWidth, IsCanonical, MaxSimpleKeyLength, SkipAnchorName, indentSequences: true, NewLine, UseUtf16SurrogatePairs); } public EmitterSettings WithUtf16SurrogatePairs() { return new EmitterSettings(BestIndent, BestWidth, IsCanonical, MaxSimpleKeyLength, SkipAnchorName, IndentSequences, NewLine, useUtf16SurrogatePairs: true); } } internal enum EmitterState { StreamStart, StreamEnd, FirstDocumentStart, DocumentStart, DocumentContent, DocumentEnd, FlowSequenceFirstItem, FlowSequenceItem, FlowMappingFirstKey, FlowMappingKey, FlowMappingSimpleValue, FlowMappingValue, BlockSequenceFirstItem, BlockSequenceItem, BlockMappingFirstKey, BlockMappingKey, BlockMappingSimpleValue, BlockMappingValue } internal sealed class ForwardAnchorNotSupportedException : YamlException { public ForwardAnchorNotSupportedException(string message) : base(message) { } public ForwardAnchorNotSupportedException(in Mark start, in Mark end, string message) : base(in start, in end, message) { } public ForwardAnchorNotSupportedException(string message, Exception inner) : base(message, inner) { } } internal static class HashCode { public static int CombineHashCodes(int h1, int h2) { return ((h1 << 5) + h1) ^ h2; } public static int CombineHashCodes(int h1, object? o2) { return CombineHashCodes(h1, GetHashCode(o2)); } private static int GetHashCode(object? obj) { return obj?.GetHashCode() ?? 0; } } internal interface IEmitter { void Emit(ParsingEvent @event); } internal interface ILookAheadBuffer { bool EndOfInput { get; } char Peek(int offset); void Skip(int length); } internal sealed class InsertionQueue : IEnumerable, IEnumerable { private const int DefaultInitialCapacity = 128; private T[] items; private int readPtr; private int writePtr; private int mask; private int count; public int Count => count; public int Capacity => items.Length; public InsertionQueue(int initialCapacity = 128) { if (initialCapacity <= 0) { throw new ArgumentOutOfRangeException("initialCapacity", "The initial capacity must be a positive number."); } if (!initialCapacity.IsPowerOfTwo()) { throw new ArgumentException("The initial capacity must be a power of 2.", "initialCapacity"); } items = new T[initialCapacity]; readPtr = initialCapacity / 2; writePtr = initialCapacity / 2; mask = initialCapacity - 1; } public void Enqueue(T item) { ResizeIfNeeded(); items[writePtr] = item; writePtr = (writePtr - 1) & mask; count++; } public T Dequeue() { if (count == 0) { throw new InvalidOperationException("The queue is empty"); } T result = items[readPtr]; readPtr = (readPtr - 1) & mask; count--; return result; } public void Insert(int index, T item) { if (index > count) { throw new InvalidOperationException("Cannot insert outside of the bounds of the queue"); } ResizeIfNeeded(); CalculateInsertionParameters(mask, count, index, ref readPtr, ref writePtr, out var insertPtr, out var copyIndex, out var copyOffset, out var copyLength); if (copyLength != 0) { Array.Copy(items, copyIndex, items, copyIndex + copyOffset, copyLength); } items[insertPtr] = item; count++; } private void ResizeIfNeeded() { int num = items.Length; if (count == num) { T[] destinationArray = new T[num * 2]; int num2 = readPtr + 1; if (num2 > 0) { Array.Copy(items, 0, destinationArray, 0, num2); } writePtr += num; int num3 = num - num2; if (num3 > 0) { Array.Copy(items, readPtr + 1, destinationArray, writePtr + 1, num3); } items = destinationArray; mask = mask * 2 + 1; } } internal static void CalculateInsertionParameters(int mask, int count, int index, ref int readPtr, ref int writePtr, out int insertPtr, out int copyIndex, out int copyOffset, out int copyLength) { int num = (readPtr + 1) & mask; if (index == 0) { insertPtr = (readPtr = num); copyIndex = 0; copyOffset = 0; copyLength = 0; return; } insertPtr = (readPtr - index) & mask; if (index == count) { writePtr = (writePtr - 1) & mask; copyIndex = 0; copyOffset = 0; copyLength = 0; return; } int num2 = ((num >= insertPtr) ? (readPtr - insertPtr) : int.MaxValue); int num3 = ((writePtr <= insertPtr) ? (insertPtr - writePtr) : int.MaxValue); if (num2 <= num3) { insertPtr++; readPtr++; copyIndex = insertPtr; copyOffset = 1; copyLength = num2; } else { copyIndex = writePtr + 1; copyOffset = -1; copyLength = num3; writePtr = (writePtr - 1) & mask; } } public IEnumerator GetEnumerator() { int ptr = readPtr; for (int i = 0; i < Count; i++) { yield return items[ptr]; ptr = (ptr - 1) & mask; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } internal interface IParser { ParsingEvent? Current { get; } bool MoveNext(); } internal interface IScanner { Mark CurrentPosition { get; } Token? Current { get; } bool MoveNext(); bool MoveNextWithoutConsuming(); void ConsumeCurrent(); } [DebuggerStepThrough] internal sealed class LookAheadBuffer : ILookAheadBuffer { private readonly TextReader input; private readonly char[] buffer; private readonly int blockSize; private readonly int mask; private int firstIndex; private int writeOffset; private int count; private bool endOfInput; public bool EndOfInput { get { if (endOfInput) { return count == 0; } return false; } } public LookAheadBuffer(TextReader input, int capacity) { if (capacity < 1) { throw new ArgumentOutOfRangeException("capacity", "The capacity must be positive."); } if (!capacity.IsPowerOfTwo()) { throw new ArgumentException("The capacity must be a power of 2.", "capacity"); } this.input = input ?? throw new ArgumentNullException("input"); blockSize = capacity; buffer = new char[capacity * 2]; mask = capacity * 2 - 1; } private int GetIndexForOffset(int offset) { return (firstIndex + offset) & mask; } public char Peek(int offset) { if (offset >= count) { FillBuffer(); } if (offset < count) { return buffer[(firstIndex + offset) & mask]; } return '\0'; } public void Cache(int length) { if (length >= count) { FillBuffer(); } } private void FillBuffer() { if (endOfInput) { return; } int num = blockSize; do { int num2 = input.Read(buffer, writeOffset, num); if (num2 == 0) { endOfInput = true; return; } num -= num2; writeOffset += num2; count += num2; } while (num > 0); if (writeOffset == buffer.Length) { writeOffset = 0; } } public void Skip(int length) { if (length < 1 || length > blockSize) { throw new ArgumentOutOfRangeException("length", "The length must be between 1 and the number of characters in the buffer. Use the Peek() and / or Cache() methods to fill the buffer."); } firstIndex = GetIndexForOffset(length); count -= length; } } internal readonly struct Mark : IEquatable, IComparable, IComparable { public static readonly Mark Empty = new Mark(0L, 1L, 1L); public long Index { get; } public long Line { get; } public long Column { get; } public Mark(long index, long line, long column) { if (index < 0) { ThrowHelper.ThrowArgumentOutOfRangeException("index", "Index must be greater than or equal to zero."); } if (line < 1) { ThrowHelper.ThrowArgumentOutOfRangeException("line", "Line must be greater than or equal to 1."); } if (column < 1) { ThrowHelper.ThrowArgumentOutOfRangeException("column", "Column must be greater than or equal to 1."); } Index = index; Line = line; Column = column; } public override string ToString() { return $"Line: {Line}, Col: {Column}, Idx: {Index}"; } public override bool Equals(object? obj) { return Equals((Mark)(obj ?? ((object)Empty))); } public bool Equals(Mark other) { if (Index == other.Index && Line == other.Line) { return Column == other.Column; } return false; } public override int GetHashCode() { return HashCode.CombineHashCodes(Index.GetHashCode(), HashCode.CombineHashCodes(Line.GetHashCode(), Column.GetHashCode())); } public int CompareTo(object? obj) { return CompareTo((Mark)(obj ?? ((object)Empty))); } public int CompareTo(Mark other) { int num = Line.CompareTo(other.Line); if (num == 0) { num = Column.CompareTo(other.Column); } return num; } public static bool operator ==(Mark left, Mark right) { return left.Equals(right); } public static bool operator !=(Mark left, Mark right) { return !(left == right); } public static bool operator <(Mark left, Mark right) { return left.CompareTo(right) < 0; } public static bool operator <=(Mark left, Mark right) { return left.CompareTo(right) <= 0; } public static bool operator >(Mark left, Mark right) { return left.CompareTo(right) > 0; } public static bool operator >=(Mark left, Mark right) { return left.CompareTo(right) >= 0; } } internal sealed class MaximumRecursionLevelReachedException : YamlException { public MaximumRecursionLevelReachedException(string message) : base(message) { } public MaximumRecursionLevelReachedException(in Mark start, in Mark end, string message) : base(in start, in end, message) { } public MaximumRecursionLevelReachedException(string message, Exception inner) : base(message, inner) { } } internal sealed class MergingParser : IParser { private sealed class ParsingEventCollection : IEnumerable>, IEnumerable { private readonly LinkedList events; private readonly HashSet> deleted; private readonly Dictionary> references; public ParsingEventCollection() { events = new LinkedList(); deleted = new HashSet>(); references = new Dictionary>(); } public void AddAfter(LinkedListNode node, IEnumerable items) { foreach (ParsingEvent item in items) { node = events.AddAfter(node, item); } } public void Add(ParsingEvent item) { LinkedListNode node = events.AddLast(item); AddReference(item, node); } public void MarkDeleted(LinkedListNode node) { deleted.Add(node); } public bool IsDeleted(LinkedListNode node) { return deleted.Contains(node); } public void CleanMarked() { foreach (LinkedListNode item in deleted) { events.Remove(item); } } public IEnumerable> FromAnchor(AnchorName anchor) { LinkedListNode next = references[anchor].Next; return Enumerate(next); } public IEnumerator> GetEnumerator() { return Enumerate(events.First).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private static IEnumerable> Enumerate(LinkedListNode? node) { while (node != null) { yield return node; node = node.Next; } } private void AddReference(ParsingEvent item, LinkedListNode node) { if (item is MappingStart { Anchor: { IsEmpty: false } anchor }) { references[anchor] = node; } } } private sealed class ParsingEventCloner : IParsingEventVisitor { private ParsingEvent? clonedEvent; public ParsingEvent Clone(ParsingEvent e) { e.Accept(this); if (clonedEvent == null) { throw new InvalidOperationException($"Could not clone event of type '{e.Type}'"); } return clonedEvent; } void IParsingEventVisitor.Visit(YamlDotNet.Core.Events.AnchorAlias e) { clonedEvent = new YamlDotNet.Core.Events.AnchorAlias(e.Value, e.Start, e.End); } void IParsingEventVisitor.Visit(YamlDotNet.Core.Events.StreamStart e) { throw new NotSupportedException(); } void IParsingEventVisitor.Visit(YamlDotNet.Core.Events.StreamEnd e) { throw new NotSupportedException(); } void IParsingEventVisitor.Visit(YamlDotNet.Core.Events.DocumentStart e) { throw new NotSupportedException(); } void IParsingEventVisitor.Visit(YamlDotNet.Core.Events.DocumentEnd e) { throw new NotSupportedException(); } void IParsingEventVisitor.Visit(YamlDotNet.Core.Events.Scalar e) { clonedEvent = new YamlDotNet.Core.Events.Scalar(AnchorName.Empty, e.Tag, e.Value, e.Style, e.IsPlainImplicit, e.IsQuotedImplicit, e.Start, e.End); } void IParsingEventVisitor.Visit(SequenceStart e) { clonedEvent = new SequenceStart(AnchorName.Empty, e.Tag, e.IsImplicit, e.Style, e.Start, e.End); } void IParsingEventVisitor.Visit(SequenceEnd e) { clonedEvent = new SequenceEnd(e.Start, e.End); } void IParsingEventVisitor.Visit(MappingStart e) { clonedEvent = new MappingStart(AnchorName.Empty, e.Tag, e.IsImplicit, e.Style, e.Start, e.End); } void IParsingEventVisitor.Visit(MappingEnd e) { clonedEvent = new MappingEnd(e.Start, e.End); } void IParsingEventVisitor.Visit(YamlDotNet.Core.Events.Comment e) { throw new NotSupportedException(); } } private readonly ParsingEventCollection events; private readonly IParser innerParser; private IEnumerator> iterator; private bool merged; public ParsingEvent? Current => iterator.Current?.Value; public MergingParser(IParser innerParser) { events = new ParsingEventCollection(); merged = false; iterator = events.GetEnumerator(); this.innerParser = innerParser; } public bool MoveNext() { if (!merged) { Merge(); events.CleanMarked(); iterator = events.GetEnumerator(); merged = true; } return iterator.MoveNext(); } private void Merge() { while (innerParser.MoveNext()) { events.Add(innerParser.Current); } foreach (LinkedListNode @event in events) { if (IsMergeToken(@event)) { events.MarkDeleted(@event); if (!HandleMerge(@event.Next)) { throw new SemanticErrorException(@event.Value.Start, @event.Value.End, "Unrecognized merge key pattern"); } } } } private bool HandleMerge(LinkedListNode? node) { if (node == null) { return false; } if (node.Value is YamlDotNet.Core.Events.AnchorAlias anchorAlias) { return HandleAnchorAlias(node, node, anchorAlias); } if (node.Value is SequenceStart) { return HandleSequence(node); } return false; } private bool HandleMergeSequence(LinkedListNode sequenceStart, LinkedListNode? node) { if (node == null) { return false; } if (node.Value is YamlDotNet.Core.Events.AnchorAlias anchorAlias) { return HandleAnchorAlias(sequenceStart, node, anchorAlias); } if (node.Value is SequenceStart) { return HandleSequence(node); } return false; } private static bool IsMergeToken(LinkedListNode node) { if (node.Value is YamlDotNet.Core.Events.Scalar scalar) { return scalar.Value == "<<"; } return false; } private bool HandleAnchorAlias(LinkedListNode node, LinkedListNode anchorNode, YamlDotNet.Core.Events.AnchorAlias anchorAlias) { IEnumerable mappingEvents = GetMappingEvents(anchorAlias.Value); events.AddAfter(node, mappingEvents); events.MarkDeleted(anchorNode); return true; } private bool HandleSequence(LinkedListNode node) { events.MarkDeleted(node); LinkedListNode linkedListNode = node; while (linkedListNode != null) { if (linkedListNode.Value is SequenceEnd) { events.MarkDeleted(linkedListNode); return true; } LinkedListNode next = linkedListNode.Next; HandleMergeSequence(node, next); linkedListNode = next; } return true; } private IEnumerable GetMappingEvents(AnchorName anchor) { ParsingEventCloner parsingEventCloner = new ParsingEventCloner(); int nesting = 0; return (from e in events.FromAnchor(anchor) where !events.IsDeleted(e) select e.Value).TakeWhile((ParsingEvent e) => (nesting += e.NestingIncrease) >= 0).Select(parsingEventCloner.Clone); } } internal class Parser : IParser { private class EventQueue { private readonly Queue highPriorityEvents = new Queue(); private readonly Queue normalPriorityEvents = new Queue(); public int Count => highPriorityEvents.Count + normalPriorityEvents.Count; public void Enqueue(ParsingEvent @event) { EventType type = @event.Type; if (type == EventType.StreamStart || type == EventType.DocumentStart) { highPriorityEvents.Enqueue(@event); } else { normalPriorityEvents.Enqueue(@event); } } public ParsingEvent Dequeue() { if (highPriorityEvents.Count <= 0) { return normalPriorityEvents.Dequeue(); } return highPriorityEvents.Dequeue(); } } private readonly Stack states = new Stack(); private readonly TagDirectiveCollection tagDirectives = new TagDirectiveCollection(); private ParserState state; private readonly IScanner scanner; private Token? currentToken; private VersionDirective? version; private readonly EventQueue pendingEvents = new EventQueue(); public ParsingEvent? Current { get; private set; } private Token? GetCurrentToken() { if (currentToken == null) { while (scanner.MoveNextWithoutConsuming()) { currentToken = scanner.Current; if (!(currentToken is YamlDotNet.Core.Tokens.Comment comment)) { break; } pendingEvents.Enqueue(new YamlDotNet.Core.Events.Comment(comment.Value, comment.IsInline, comment.Start, comment.End)); scanner.ConsumeCurrent(); } } return currentToken; } public Parser(TextReader input) : this(new Scanner(input)) { } public Parser(IScanner scanner) { this.scanner = scanner; } public bool MoveNext() { if (state == ParserState.StreamEnd) { Current = null; return false; } if (pendingEvents.Count == 0) { pendingEvents.Enqueue(StateMachine()); } Current = pendingEvents.Dequeue(); return true; } private ParsingEvent StateMachine() { return state switch { ParserState.StreamStart => ParseStreamStart(), ParserState.ImplicitDocumentStart => ParseDocumentStart(isImplicit: true), ParserState.DocumentStart => ParseDocumentStart(isImplicit: false), ParserState.DocumentContent => ParseDocumentContent(), ParserState.DocumentEnd => ParseDocumentEnd(), ParserState.BlockNode => ParseNode(isBlock: true, isIndentlessSequence: false), ParserState.BlockNodeOrIndentlessSequence => ParseNode(isBlock: true, isIndentlessSequence: true), ParserState.FlowNode => ParseNode(isBlock: false, isIndentlessSequence: false), ParserState.BlockSequenceFirstEntry => ParseBlockSequenceEntry(isFirst: true), ParserState.BlockSequenceEntry => ParseBlockSequenceEntry(isFirst: false), ParserState.IndentlessSequenceEntry => ParseIndentlessSequenceEntry(), ParserState.BlockMappingFirstKey => ParseBlockMappingKey(isFirst: true), ParserState.BlockMappingKey => ParseBlockMappingKey(isFirst: false), ParserState.BlockMappingValue => ParseBlockMappingValue(), ParserState.FlowSequenceFirstEntry => ParseFlowSequenceEntry(isFirst: true), ParserState.FlowSequenceEntry => ParseFlowSequenceEntry(isFirst: false), ParserState.FlowSequenceEntryMappingKey => ParseFlowSequenceEntryMappingKey(), ParserState.FlowSequenceEntryMappingValue => ParseFlowSequenceEntryMappingValue(), ParserState.FlowSequenceEntryMappingEnd => ParseFlowSequenceEntryMappingEnd(), ParserState.FlowMappingFirstKey => ParseFlowMappingKey(isFirst: true), ParserState.FlowMappingKey => ParseFlowMappingKey(isFirst: false), ParserState.FlowMappingValue => ParseFlowMappingValue(isEmpty: false), ParserState.FlowMappingEmptyValue => ParseFlowMappingValue(isEmpty: true), _ => throw new InvalidOperationException(), }; } private void Skip() { if (currentToken != null) { currentToken = null; scanner.ConsumeCurrent(); } } private YamlDotNet.Core.Events.StreamStart ParseStreamStart() { Token token = GetCurrentToken(); if (!(token is YamlDotNet.Core.Tokens.StreamStart streamStart)) { throw new SemanticErrorException(token?.Start ?? Mark.Empty, token?.End ?? Mark.Empty, "Did not find expected ."); } Skip(); state = ParserState.ImplicitDocumentStart; return new YamlDotNet.Core.Events.StreamStart(streamStart.Start, streamStart.End); } private ParsingEvent ParseDocumentStart(bool isImplicit) { if (currentToken is VersionDirective) { throw new SyntaxErrorException("While parsing a document start node, could not find document end marker before version directive."); } Token token = GetCurrentToken(); if (!isImplicit) { while (token is YamlDotNet.Core.Tokens.DocumentEnd) { Skip(); token = GetCurrentToken(); } } if (token == null) { throw new SyntaxErrorException("Reached the end of the stream while parsing a document start."); } if (token is YamlDotNet.Core.Tokens.Scalar && (state == ParserState.ImplicitDocumentStart || state == ParserState.DocumentStart)) { isImplicit = true; } if ((isImplicit && !(token is VersionDirective) && !(token is TagDirective) && !(token is YamlDotNet.Core.Tokens.DocumentStart) && !(token is YamlDotNet.Core.Tokens.StreamEnd) && !(token is YamlDotNet.Core.Tokens.DocumentEnd)) || token is BlockMappingStart) { TagDirectiveCollection tags = new TagDirectiveCollection(); ProcessDirectives(tags); states.Push(ParserState.DocumentEnd); state = ParserState.BlockNode; return new YamlDotNet.Core.Events.DocumentStart(null, tags, isImplicit: true, token.Start, token.End); } if (!(token is YamlDotNet.Core.Tokens.StreamEnd) && !(token is YamlDotNet.Core.Tokens.DocumentEnd)) { Mark start = token.Start; TagDirectiveCollection tags2 = new TagDirectiveCollection(); VersionDirective versionDirective = ProcessDirectives(tags2); token = GetCurrentToken() ?? throw new SemanticErrorException("Reached the end of the stream while parsing a document start"); if (!(token is YamlDotNet.Core.Tokens.DocumentStart)) { throw new SemanticErrorException(token.Start, token.End, "Did not find expected ."); } states.Push(ParserState.DocumentEnd); state = ParserState.DocumentContent; Mark end = token.End; Skip(); return new YamlDotNet.Core.Events.DocumentStart(versionDirective, tags2, isImplicit: false, start, end); } if (token is YamlDotNet.Core.Tokens.DocumentEnd) { Skip(); } state = ParserState.StreamEnd; token = GetCurrentToken() ?? throw new SemanticErrorException("Reached the end of the stream while parsing a document start"); YamlDotNet.Core.Events.StreamEnd result = new YamlDotNet.Core.Events.StreamEnd(token.Start, token.End); if (scanner.MoveNextWithoutConsuming()) { throw new InvalidOperationException("The scanner should contain no more tokens."); } return result; } private VersionDirective? ProcessDirectives(TagDirectiveCollection tags) { bool flag = false; VersionDirective result = null; while (true) { if (GetCurrentToken() is VersionDirective versionDirective) { if (version != null) { throw new SemanticErrorException(versionDirective.Start, versionDirective.End, "Found duplicate %YAML directive."); } if (versionDirective.Version.Major != 1 || versionDirective.Version.Minor > 3) { throw new SemanticErrorException(versionDirective.Start, versionDirective.End, "Found incompatible YAML document."); } result = (version = versionDirective); flag = true; } else { if (!(GetCurrentToken() is TagDirective tagDirective)) { break; } if (tags.Contains(tagDirective.Handle)) { throw new SemanticErrorException(tagDirective.Start, tagDirective.End, "Found duplicate %TAG directive."); } tags.Add(tagDirective); flag = true; } Skip(); } if (GetCurrentToken() is YamlDotNet.Core.Tokens.DocumentStart && (version == null || (version.Version.Major == 1 && version.Version.Minor > 1))) { if (GetCurrentToken() is YamlDotNet.Core.Tokens.DocumentStart && version == null) { version = new VersionDirective(new Version(1, 2)); } flag = true; } AddTagDirectives(tags, Constants.DefaultTagDirectives); if (flag) { tagDirectives.Clear(); } AddTagDirectives(tagDirectives, tags); return result; } private static void AddTagDirectives(TagDirectiveCollection directives, IEnumerable source) { foreach (TagDirective item in source) { if (!directives.Contains(item)) { directives.Add(item); } } } private ParsingEvent ParseDocumentContent() { if (GetCurrentToken() is VersionDirective || GetCurrentToken() is TagDirective || GetCurrentToken() is YamlDotNet.Core.Tokens.DocumentStart || GetCurrentToken() is YamlDotNet.Core.Tokens.DocumentEnd || GetCurrentToken() is YamlDotNet.Core.Tokens.StreamEnd) { state = states.Pop(); return ProcessEmptyScalar(scanner.CurrentPosition); } return ParseNode(isBlock: true, isIndentlessSequence: false); } private static YamlDotNet.Core.Events.Scalar ProcessEmptyScalar(in Mark position) { return new YamlDotNet.Core.Events.Scalar(AnchorName.Empty, TagName.Empty, string.Empty, ScalarStyle.Plain, isPlainImplicit: true, isQuotedImplicit: false, position, position); } private ParsingEvent ParseNode(bool isBlock, bool isIndentlessSequence) { if (GetCurrentToken() is Error { Start: var start } error) { throw new SemanticErrorException(in start, error.End, error.Value); } Token token = GetCurrentToken() ?? throw new SemanticErrorException("Reached the end of the stream while parsing a node"); if (token is YamlDotNet.Core.Tokens.AnchorAlias anchorAlias) { state = states.Pop(); ParsingEvent result = new YamlDotNet.Core.Events.AnchorAlias(anchorAlias.Value, anchorAlias.Start, anchorAlias.End); Skip(); return result; } Mark start2 = token.Start; AnchorName anchor = AnchorName.Empty; TagName tag = TagName.Empty; Anchor anchor2 = null; Tag tag2 = null; while (true) { if (anchor.IsEmpty && token is Anchor anchor3) { anchor2 = anchor3; anchor = anchor3.Value; Skip(); } else { if (!tag.IsEmpty || !(token is Tag tag3)) { if (token is Anchor { Start: var start3 } anchor4) { throw new SemanticErrorException(in start3, anchor4.End, "While parsing a node, found more than one anchor."); } if (token is YamlDotNet.Core.Tokens.AnchorAlias { Start: var start4 } anchorAlias2) { throw new SemanticErrorException(in start4, anchorAlias2.End, "While parsing a node, did not find expected token."); } if (!(token is Error error2)) { break; } if (tag2 != null && anchor2 != null && !anchor.IsEmpty) { return new YamlDotNet.Core.Events.Scalar(anchor, default(TagName), string.Empty, ScalarStyle.Any, isPlainImplicit: false, isQuotedImplicit: false, anchor2.Start, anchor2.End); } throw new SemanticErrorException(error2.Start, error2.End, error2.Value); } tag2 = tag3; if (string.IsNullOrEmpty(tag3.Handle)) { tag = new TagName(tag3.Suffix); } else { if (!tagDirectives.Contains(tag3.Handle)) { throw new SemanticErrorException(tag3.Start, tag3.End, "While parsing a node, found undefined tag handle."); } tag = new TagName(tagDirectives[tag3.Handle].Prefix + tag3.Suffix); } Skip(); } token = GetCurrentToken() ?? throw new SemanticErrorException("Reached the end of the stream while parsing a node"); } bool isEmpty = tag.IsEmpty; if (isIndentlessSequence && GetCurrentToken() is BlockEntry) { state = ParserState.IndentlessSequenceEntry; return new SequenceStart(anchor, tag, isEmpty, SequenceStyle.Block, start2, token.End); } if (token is YamlDotNet.Core.Tokens.Scalar scalar) { bool isPlainImplicit = false; bool isQuotedImplicit = false; if ((scalar.Style == ScalarStyle.Plain && tag.IsEmpty) || tag.IsNonSpecific) { isPlainImplicit = true; } else if (tag.IsEmpty) { isQuotedImplicit = true; } state = states.Pop(); Skip(); ParsingEvent result2 = new YamlDotNet.Core.Events.Scalar(anchor, tag, scalar.Value, scalar.Style, isPlainImplicit, isQuotedImplicit, start2, scalar.End, scalar.IsKey); if (!anchor.IsEmpty && scanner.MoveNextWithoutConsuming()) { currentToken = scanner.Current; if (currentToken is Error) { Error error3 = currentToken as Error; throw new SemanticErrorException(error3.Start, error3.End, error3.Value); } } if (state == ParserState.FlowMappingKey && !(scanner.Current is FlowMappingEnd) && scanner.MoveNextWithoutConsuming()) { currentToken = scanner.Current; if (currentToken != null && !(currentToken is FlowEntry) && !(currentToken is FlowMappingEnd)) { throw new SemanticErrorException(currentToken.Start, currentToken.End, "While parsing a flow mapping, did not find expected ',' or '}'."); } } return result2; } if (token is FlowSequenceStart flowSequenceStart) { state = ParserState.FlowSequenceFirstEntry; return new SequenceStart(anchor, tag, isEmpty, SequenceStyle.Flow, start2, flowSequenceStart.End); } if (token is FlowMappingStart flowMappingStart) { state = ParserState.FlowMappingFirstKey; return new MappingStart(anchor, tag, isEmpty, MappingStyle.Flow, start2, flowMappingStart.End); } if (isBlock) { if (token is BlockSequenceStart blockSequenceStart) { state = ParserState.BlockSequenceFirstEntry; return new SequenceStart(anchor, tag, isEmpty, SequenceStyle.Block, start2, blockSequenceStart.End); } if (token is BlockMappingStart blockMappingStart) { state = ParserState.BlockMappingFirstKey; return new MappingStart(anchor, tag, isEmpty, MappingStyle.Block, start2, blockMappingStart.End); } } if (!anchor.IsEmpty || !tag.IsEmpty) { state = states.Pop(); return new YamlDotNet.Core.Events.Scalar(anchor, tag, string.Empty, ScalarStyle.Plain, isEmpty, isQuotedImplicit: false, start2, token.End); } throw new SemanticErrorException(token.Start, token.End, "While parsing a node, did not find expected node content."); } private YamlDotNet.Core.Events.DocumentEnd ParseDocumentEnd() { Token token = GetCurrentToken() ?? throw new SemanticErrorException("Reached the end of the stream while parsing a document end"); bool isImplicit = true; Mark start = token.Start; Mark end = start; if (token is YamlDotNet.Core.Tokens.DocumentEnd) { end = token.End; Skip(); isImplicit = false; } else if (!(currentToken is YamlDotNet.Core.Tokens.StreamEnd) && !(currentToken is YamlDotNet.Core.Tokens.DocumentStart) && !(currentToken is FlowSequenceEnd) && !(currentToken is VersionDirective) && (!(Current is YamlDotNet.Core.Events.Scalar) || !(currentToken is Error))) { throw new SemanticErrorException(in start, in end, "Did not find expected ."); } if (version != null && version.Version.Major == 1 && version.Version.Minor > 1) { version = null; } state = ParserState.DocumentStart; return new YamlDotNet.Core.Events.DocumentEnd(isImplicit, start, end); } private ParsingEvent ParseBlockSequenceEntry(bool isFirst) { if (isFirst) { GetCurrentToken(); Skip(); } Token token = GetCurrentToken(); if (token is BlockEntry { End: var position }) { Skip(); token = GetCurrentToken(); if (!(token is BlockEntry) && !(token is BlockEnd)) { states.Push(ParserState.BlockSequenceEntry); return ParseNode(isBlock: true, isIndentlessSequence: false); } state = ParserState.BlockSequenceEntry; return ProcessEmptyScalar(in position); } if (token is BlockEnd blockEnd) { state = states.Pop(); ParsingEvent result = new SequenceEnd(blockEnd.Start, blockEnd.End); Skip(); return result; } throw new SemanticErrorException(token?.Start ?? Mark.Empty, token?.End ?? Mark.Empty, "While parsing a block collection, did not find expected '-' indicator."); } private ParsingEvent ParseIndentlessSequenceEntry() { Token token = GetCurrentToken(); if (token is BlockEntry { End: var position }) { Skip(); token = GetCurrentToken(); if (!(token is BlockEntry) && !(token is Key) && !(token is Value) && !(token is BlockEnd)) { states.Push(ParserState.IndentlessSequenceEntry); return ParseNode(isBlock: true, isIndentlessSequence: false); } state = ParserState.IndentlessSequenceEntry; return ProcessEmptyScalar(in position); } state = states.Pop(); return new SequenceEnd(token?.Start ?? Mark.Empty, token?.End ?? Mark.Empty); } private ParsingEvent ParseBlockMappingKey(bool isFirst) { if (isFirst) { GetCurrentToken(); Skip(); } Token token = GetCurrentToken(); if (token is Key { End: var position }) { Skip(); token = GetCurrentToken(); if (!(token is Key) && !(token is Value) && !(token is BlockEnd)) { states.Push(ParserState.BlockMappingValue); return ParseNode(isBlock: true, isIndentlessSequence: true); } state = ParserState.BlockMappingValue; return ProcessEmptyScalar(in position); } if (token is Value value) { Skip(); return ProcessEmptyScalar(value.End); } if (token is YamlDotNet.Core.Tokens.AnchorAlias anchorAlias) { Skip(); return new YamlDotNet.Core.Events.AnchorAlias(anchorAlias.Value, anchorAlias.Start, anchorAlias.End); } if (token is BlockEnd blockEnd) { state = states.Pop(); ParsingEvent result = new MappingEnd(blockEnd.Start, blockEnd.End); Skip(); return result; } if (GetCurrentToken() is Error { Start: var start } error) { throw new SyntaxErrorException(in start, error.End, error.Value); } throw new SemanticErrorException(token?.Start ?? Mark.Empty, token?.End ?? Mark.Empty, "While parsing a block mapping, did not find expected key."); } private ParsingEvent ParseBlockMappingValue() { Token token = GetCurrentToken(); if (token is Value { End: var position }) { Skip(); token = GetCurrentToken(); if (!(token is Key) && !(token is Value) && !(token is BlockEnd)) { states.Push(ParserState.BlockMappingKey); return ParseNode(isBlock: true, isIndentlessSequence: true); } state = ParserState.BlockMappingKey; return ProcessEmptyScalar(in position); } if (token is Error { Start: var start } error) { throw new SemanticErrorException(in start, error.End, error.Value); } state = ParserState.BlockMappingKey; return ProcessEmptyScalar(token?.Start ?? Mark.Empty); } private ParsingEvent ParseFlowSequenceEntry(bool isFirst) { if (isFirst) { GetCurrentToken(); Skip(); } Token token = GetCurrentToken(); ParsingEvent result; if (!(token is FlowSequenceEnd)) { if (!isFirst) { if (!(token is FlowEntry)) { throw new SemanticErrorException(token?.Start ?? Mark.Empty, token?.End ?? Mark.Empty, "While parsing a flow sequence, did not find expected ',' or ']'."); } Skip(); token = GetCurrentToken(); } if (token is Key) { state = ParserState.FlowSequenceEntryMappingKey; result = new MappingStart(AnchorName.Empty, TagName.Empty, isImplicit: true, MappingStyle.Flow); Skip(); return result; } if (!(token is FlowSequenceEnd)) { states.Push(ParserState.FlowSequenceEntry); return ParseNode(isBlock: false, isIndentlessSequence: false); } } state = states.Pop(); result = new SequenceEnd(token?.Start ?? Mark.Empty, token?.End ?? Mark.Empty); Skip(); return result; } private ParsingEvent ParseFlowSequenceEntryMappingKey() { Token token = GetCurrentToken(); if (!(token is Value) && !(token is FlowEntry) && !(token is FlowSequenceEnd)) { states.Push(ParserState.FlowSequenceEntryMappingValue); return ParseNode(isBlock: false, isIndentlessSequence: false); } Mark position = token?.End ?? Mark.Empty; Skip(); state = ParserState.FlowSequenceEntryMappingValue; return ProcessEmptyScalar(in position); } private ParsingEvent ParseFlowSequenceEntryMappingValue() { Token token = GetCurrentToken(); if (token is Value) { Skip(); token = GetCurrentToken(); if (!(token is FlowEntry) && !(token is FlowSequenceEnd)) { states.Push(ParserState.FlowSequenceEntryMappingEnd); return ParseNode(isBlock: false, isIndentlessSequence: false); } } state = ParserState.FlowSequenceEntryMappingEnd; return ProcessEmptyScalar(token?.Start ?? Mark.Empty); } private MappingEnd ParseFlowSequenceEntryMappingEnd() { state = ParserState.FlowSequenceEntry; Token token = GetCurrentToken(); return new MappingEnd(token?.Start ?? Mark.Empty, token?.End ?? Mark.Empty); } private ParsingEvent ParseFlowMappingKey(bool isFirst) { if (isFirst) { GetCurrentToken(); Skip(); } Token token = GetCurrentToken(); if (!(token is FlowMappingEnd)) { if (!isFirst) { if (token is FlowEntry) { Skip(); token = GetCurrentToken(); } else if (!(token is YamlDotNet.Core.Tokens.Scalar)) { throw new SemanticErrorException(token?.Start ?? Mark.Empty, token?.End ?? Mark.Empty, "While parsing a flow mapping, did not find expected ',' or '}'."); } } if (token is Key) { Skip(); token = GetCurrentToken(); if (!(token is Value) && !(token is FlowEntry) && !(token is FlowMappingEnd)) { states.Push(ParserState.FlowMappingValue); return ParseNode(isBlock: false, isIndentlessSequence: false); } state = ParserState.FlowMappingValue; return ProcessEmptyScalar(token?.Start ?? Mark.Empty); } if (token is YamlDotNet.Core.Tokens.Scalar) { states.Push(ParserState.FlowMappingValue); return ParseNode(isBlock: false, isIndentlessSequence: false); } if (!(token is FlowMappingEnd)) { states.Push(ParserState.FlowMappingEmptyValue); return ParseNode(isBlock: false, isIndentlessSequence: false); } } state = states.Pop(); Skip(); return new MappingEnd(token?.Start ?? Mark.Empty, token?.End ?? Mark.Empty); } private ParsingEvent ParseFlowMappingValue(bool isEmpty) { Token token = GetCurrentToken(); if (!isEmpty && token is Value) { Skip(); token = GetCurrentToken(); if (!(token is FlowEntry) && !(token is FlowMappingEnd)) { states.Push(ParserState.FlowMappingKey); return ParseNode(isBlock: false, isIndentlessSequence: false); } } state = ParserState.FlowMappingKey; if (!isEmpty && token is YamlDotNet.Core.Tokens.Scalar scalar) { Skip(); return new YamlDotNet.Core.Events.Scalar(AnchorName.Empty, TagName.Empty, scalar.Value, scalar.Style, isPlainImplicit: false, isQuotedImplicit: false, token.Start, scalar.End); } return ProcessEmptyScalar(token?.Start ?? Mark.Empty); } } internal static class ParserExtensions { public static T Consume(this IParser parser) where T : ParsingEvent { T result = parser.Require(); parser.MoveNext(); return result; } public static bool TryConsume(this IParser parser, [MaybeNullWhen(false)] out T @event) where T : ParsingEvent { if (parser.Accept(out @event)) { parser.MoveNext(); return true; } return false; } public static T Require(this IParser parser) where T : ParsingEvent { if (!parser.Accept(out var @event)) { ParsingEvent current = parser.Current; if (current == null) { throw new YamlException("Expected '" + typeof(T).Name + "', got nothing."); } throw new YamlException(current.Start, current.End, $"Expected '{typeof(T).Name}', got '{current.GetType().Name}' (at {current.Start})."); } return @event; } public static bool Accept(this IParser parser, [MaybeNullWhen(false)] out T @event) where T : ParsingEvent { if (parser.Current == null && !parser.MoveNext()) { throw new EndOfStreamException(); } if (parser.Current is T val) { @event = val; return true; } @event = null; return false; } public static void SkipThisAndNestedEvents(this IParser parser) { int num = 0; do { ParsingEvent parsingEvent = parser.Consume(); num += parsingEvent.NestingIncrease; } while (num > 0); } [Obsolete("Please use Consume() instead")] public static T Expect(this IParser parser) where T : ParsingEvent { return parser.Consume(); } [Obsolete("Please use TryConsume(out var evt) instead")] [return: MaybeNull] public static T? Allow(this IParser parser) where T : ParsingEvent { if (!parser.TryConsume(out var @event)) { return null; } return @event; } [Obsolete("Please use Accept(out var evt) instead")] [return: MaybeNull] public static T? Peek(this IParser parser) where T : ParsingEvent { if (!parser.Accept(out var @event)) { return null; } return @event; } [Obsolete("Please use TryConsume(out var evt) or Accept(out var evt) instead")] public static bool Accept(this IParser parser) where T : ParsingEvent { T @event; return parser.Accept(out @event); } public static bool TryFindMappingEntry(this IParser parser, Func selector, [MaybeNullWhen(false)] out YamlDotNet.Core.Events.Scalar? key, [MaybeNullWhen(false)] out ParsingEvent? value) { if (parser.TryConsume(out var _)) { while (parser.Current != null) { ParsingEvent current = parser.Current; if (!(current is YamlDotNet.Core.Events.Scalar scalar)) { if (current is MappingStart || current is SequenceStart) { parser.SkipThisAndNestedEvents(); } else { parser.MoveNext(); } continue; } bool flag = selector(scalar); parser.MoveNext(); if (flag) { value = parser.Current; key = scalar; return true; } parser.SkipThisAndNestedEvents(); } } key = null; value = null; return false; } } internal enum ParserState { StreamStart, StreamEnd, ImplicitDocumentStart, DocumentStart, DocumentContent, DocumentEnd, BlockNode, BlockNodeOrIndentlessSequence, FlowNode, BlockSequenceFirstEntry, BlockSequenceEntry, IndentlessSequenceEntry, BlockMappingFirstKey, BlockMappingKey, BlockMappingValue, FlowSequenceFirstEntry, FlowSequenceEntry, FlowSequenceEntryMappingKey, FlowSequenceEntryMappingValue, FlowSequenceEntryMappingEnd, FlowMappingFirstKey, FlowMappingKey, FlowMappingValue, FlowMappingEmptyValue } internal sealed class RecursionLevel { private int current; public int Maximum { get; } public RecursionLevel(int maximum) { Maximum = maximum; } public void Increment() { if (!TryIncrement()) { throw new MaximumRecursionLevelReachedException("Maximum level of recursion reached"); } } public bool TryIncrement() { if (current < Maximum) { current++; return true; } return false; } public void Decrement() { if (current == 0) { throw new InvalidOperationException("Attempted to decrement RecursionLevel to a negative value"); } current--; } } internal enum ScalarStyle { Any, Plain, SingleQuoted, DoubleQuoted, Literal, Folded, ForcePlain } internal class Scanner : IScanner { private const int MaxVersionNumberLength = 9; private static readonly SortedDictionary SimpleEscapeCodes = new SortedDictionary { { '0', '\0' }, { 'a', '\a' }, { 'b', '\b' }, { 't', '\t' }, { '\t', '\t' }, { 'n', '\n' }, { 'v', '\v' }, { 'f', '\f' }, { 'r', '\r' }, { 'e', '\u001b' }, { ' ', ' ' }, { '"', '"' }, { '\\', '\\' }, { '/', '/' }, { 'N', '\u0085' }, { '_', '\u00a0' }, { 'L', '\u2028' }, { 'P', '\u2029' } }; private readonly Stack indents = new Stack(); private readonly InsertionQueue tokens = new InsertionQueue(); private readonly Stack simpleKeys = new Stack(); private readonly CharacterAnalyzer analyzer; private readonly Cursor cursor; private bool streamStartProduced; private bool streamEndProduced; private bool plainScalarFollowedByComment; private bool flowCollectionFetched; private bool startFlowCollectionFetched; private long indent = -1L; private bool flowScalarFetched; private bool simpleKeyAllowed; private int flowLevel; private int tokensParsed; private bool tokenAvailable; private Token? previous; private Anchor? previousAnchor; private YamlDotNet.Core.Tokens.Scalar? lastScalar; private readonly int maxKeySize; private static readonly byte[] EmptyBytes = Array.Empty(); public bool SkipComments { get; private set; } public Token? Current { get; private set; } public Mark CurrentPosition => cursor.Mark(); private bool IsDocumentStart() { if (!analyzer.EndOfInput && cursor.LineOffset == 0L && analyzer.Check('-') && analyzer.Check('-', 1) && analyzer.Check('-', 2)) { return analyzer.IsWhiteBreakOrZero(3); } return false; } private bool IsDocumentEnd() { if (!analyzer.EndOfInput && cursor.LineOffset == 0L && analyzer.Check('.') && analyzer.Check('.', 1) && analyzer.Check('.', 2)) { return analyzer.IsWhiteBreakOrZero(3); } return false; } private bool IsDocumentIndicator() { if (!IsDocumentStart()) { return IsDocumentEnd(); } return true; } public Scanner(TextReader input, bool skipComments = true) : this(input, skipComments, 1024) { } public Scanner(TextReader input, bool skipComments, int maxKeySize) { analyzer = new CharacterAnalyzer(new LookAheadBuffer(input, 1024)); cursor = new Cursor(); SkipComments = skipComments; this.maxKeySize = maxKeySize; } public bool MoveNext() { if (Current != null) { ConsumeCurrent(); } return MoveNextWithoutConsuming(); } public bool MoveNextWithoutConsuming() { if (!tokenAvailable && !streamEndProduced) { FetchMoreTokens(); } if (tokens.Count > 0) { Current = tokens.Dequeue(); tokenAvailable = false; return true; } Current = null; return false; } public void ConsumeCurrent() { tokensParsed++; tokenAvailable = false; previous = Current; Current = null; } private char ReadCurrentCharacter() { char result = analyzer.Peek(0); Skip(); return result; } private char ReadLine() { if (analyzer.Check("\r\n\u0085")) { SkipLine(); return '\n'; } char result = analyzer.Peek(0); SkipLine(); return result; } private void FetchMoreTokens() { while (true) { bool flag = false; if (tokens.Count == 0) { flag = true; } else { foreach (SimpleKey simpleKey in simpleKeys) { if (simpleKey.IsPossible && simpleKey.TokenNumber == tokensParsed) { flag = true; break; } } } if (!flag) { break; } FetchNextToken(); } tokenAvailable = true; } private static bool StartsWith(StringBuilder what, char start) { if (what.Length > 0) { return what[0] == start; } return false; } private void StaleSimpleKeys() { foreach (SimpleKey simpleKey in simpleKeys) { if (simpleKey.IsPossible && (simpleKey.Line < cursor.Line || simpleKey.Index + maxKeySize < cursor.Index)) { if (simpleKey.IsRequired) { Mark mark = cursor.Mark(); tokens.Enqueue(new Error("While scanning a simple key, could not find expected ':'.", mark, mark)); } simpleKey.MarkAsImpossible(); } } } private void FetchNextToken() { if (!streamStartProduced) { FetchStreamStart(); return; } ScanToNextToken(); StaleSimpleKeys(); UnrollIndent(cursor.LineOffset); analyzer.Buffer.Cache(4); if (analyzer.Buffer.EndOfInput) { lastScalar = null; FetchStreamEnd(); } if (cursor.LineOffset == 0L && analyzer.Check('%')) { lastScalar = null; FetchDirective(); return; } if (IsDocumentStart()) { lastScalar = null; FetchDocumentIndicator(isStartToken: true); return; } if (IsDocumentEnd()) { lastScalar = null; FetchDocumentIndicator(isStartToken: false); return; } if (analyzer.Check('[')) { lastScalar = null; FetchFlowCollectionStart(isSequenceToken: true); return; } if (analyzer.Check('{')) { lastScalar = null; FetchFlowCollectionStart(isSequenceToken: false); return; } if (analyzer.Check(']')) { lastScalar = null; FetchFlowCollectionEnd(isSequenceToken: true); return; } if (analyzer.Check('}')) { lastScalar = null; FetchFlowCollectionEnd(isSequenceToken: false); return; } if (analyzer.Check(',')) { lastScalar = null; FetchFlowEntry(); return; } if (analyzer.Check('-')) { if (analyzer.IsWhiteBreakOrZero(1)) { FetchBlockEntry(); return; } if (flowLevel > 0 && analyzer.Check(",[]{}", 1)) { tokens.Enqueue(new Error("Invalid key indicator format.", cursor.Mark(), cursor.Mark())); } } if (analyzer.Check('?') && (flowLevel > 0 || analyzer.IsWhiteBreakOrZero(1)) && analyzer.IsWhiteBreakOrZero(1)) { FetchKey(); } else if (analyzer.Check(':') && (flowLevel > 0 || analyzer.IsWhiteBreakOrZero(1)) && (!simpleKeyAllowed || flowLevel <= 0) && (!flowScalarFetched || !analyzer.Check(':', 1)) && (analyzer.IsWhiteBreakOrZero(1) || analyzer.Check(',', 1) || flowScalarFetched || flowCollectionFetched || startFlowCollectionFetched)) { if (lastScalar != null) { lastScalar.IsKey = true; lastScalar = null; } FetchValue(); } else if (analyzer.Check('*')) { FetchAnchor(isAlias: true); } else if (analyzer.Check('&')) { FetchAnchor(isAlias: false); } else if (analyzer.Check('!')) { FetchTag(); } else if (analyzer.Check('|') && flowLevel == 0) { FetchBlockScalar(isLiteral: true); } else if (analyzer.Check('>') && flowLevel == 0) { FetchBlockScalar(isLiteral: false); } else if (analyzer.Check('\'')) { FetchQuotedScalar(isSingleQuoted: true); } else if (analyzer.Check('"')) { FetchQuotedScalar(isSingleQuoted: false); } else if ((!analyzer.IsWhiteBreakOrZero() && !analyzer.Check("-?:,[]{}#&*!|>'\"%@`")) || (analyzer.Check('-') && !analyzer.IsWhite(1)) || (analyzer.Check("?:") && !analyzer.IsWhiteBreakOrZero(1)) || (simpleKeyAllowed && flowLevel > 0)) { if (plainScalarFollowedByComment) { Mark mark = cursor.Mark(); tokens.Enqueue(new Error("While scanning plain scalar, found a comment between adjacent scalars.", mark, mark)); } if ((flowScalarFetched || (flowCollectionFetched && !startFlowCollectionFetched)) && analyzer.Check(':')) { Skip(); } flowScalarFetched = false; flowCollectionFetched = false; startFlowCollectionFetched = false; plainScalarFollowedByComment = false; FetchPlainScalar(); } else { if (simpleKeyAllowed && indent >= cursor.LineOffset && analyzer.IsTab()) { throw new SyntaxErrorException("While scanning a mapping, found invalid tab as indentation."); } if (!analyzer.IsWhiteBreakOrZero()) { Mark start = cursor.Mark(); Skip(); throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning for the next token, found character that cannot start any token."); } Skip(); } } private bool CheckWhiteSpace() { if (!analyzer.Check(' ')) { if (flowLevel > 0 || !simpleKeyAllowed) { return analyzer.Check('\t'); } return false; } return true; } private void Skip() { cursor.Skip(); analyzer.Buffer.Skip(1); } private void SkipLine() { if (analyzer.IsCrLf()) { cursor.SkipLineByOffset(2); analyzer.Buffer.Skip(2); } else if (analyzer.IsBreak()) { cursor.SkipLineByOffset(1); analyzer.Buffer.Skip(1); } else if (!analyzer.IsZero()) { throw new InvalidOperationException("Not at a break."); } } private void ScanToNextToken() { while (true) { if (CheckWhiteSpace()) { Skip(); continue; } ProcessComment(); if (analyzer.IsBreak()) { SkipLine(); if (flowLevel == 0) { simpleKeyAllowed = true; } continue; } break; } } private void ProcessComment() { if (!analyzer.Check('#')) { return; } Mark start = cursor.Mark(); Skip(); while (analyzer.IsSpace()) { Skip(); } StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; while (!analyzer.IsBreakOrZero()) { builder.Append(ReadCurrentCharacter()); } if (!SkipComments) { bool isInline = previous != null && previous.End.Line == start.Line && previous.End.Column != 1 && !(previous is YamlDotNet.Core.Tokens.StreamStart); tokens.Enqueue(new YamlDotNet.Core.Tokens.Comment(builder.ToString(), isInline, start, cursor.Mark())); } } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } } private void FetchStreamStart() { simpleKeys.Push(new SimpleKey()); simpleKeyAllowed = true; streamStartProduced = true; Mark start = cursor.Mark(); tokens.Enqueue(new YamlDotNet.Core.Tokens.StreamStart(in start, in start)); } private void UnrollIndent(long column) { if (flowLevel == 0) { while (indent > column) { Mark start = cursor.Mark(); tokens.Enqueue(new BlockEnd(in start, in start)); indent = indents.Pop(); } } } private void FetchStreamEnd() { cursor.ForceSkipLineAfterNonBreak(); UnrollIndent(-1L); RemoveSimpleKey(); simpleKeyAllowed = false; streamEndProduced = true; Mark start = cursor.Mark(); tokens.Enqueue(new YamlDotNet.Core.Tokens.StreamEnd(in start, in start)); } private void FetchDirective() { UnrollIndent(-1L); RemoveSimpleKey(); simpleKeyAllowed = false; Token token = ScanDirective(); if (token != null) { tokens.Enqueue(token); } } private Token? ScanDirective() { Mark start = cursor.Mark(); Skip(); string text = ScanDirectiveName(in start); Token result; if (!(text == "YAML")) { if (!(text == "TAG")) { while (!analyzer.EndOfInput && !analyzer.Check('#') && !analyzer.IsBreak()) { Skip(); } return null; } result = ScanTagDirectiveValue(in start); } else { if (!(previous is YamlDotNet.Core.Tokens.DocumentStart) && !(previous is YamlDotNet.Core.Tokens.StreamStart) && !(previous is YamlDotNet.Core.Tokens.DocumentEnd)) { throw new SemanticErrorException(in start, cursor.Mark(), "While scanning a version directive, did not find preceding ."); } result = ScanVersionDirectiveValue(in start); } while (analyzer.IsWhite()) { Skip(); } ProcessComment(); if (!analyzer.IsBreakOrZero()) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a directive, did not find expected comment or line break."); } if (analyzer.IsBreak()) { SkipLine(); } return result; } private void FetchDocumentIndicator(bool isStartToken) { UnrollIndent(-1L); RemoveSimpleKey(); simpleKeyAllowed = false; Mark start = cursor.Mark(); Skip(); Skip(); Skip(); if (isStartToken) { tokens.Enqueue(new YamlDotNet.Core.Tokens.DocumentStart(in start, cursor.Mark())); return; } Token token = null; while (!analyzer.EndOfInput && !analyzer.IsBreak() && !analyzer.Check('#')) { if (!analyzer.IsWhite()) { token = new Error("While scanning a document end, found invalid content after '...' marker.", start, cursor.Mark()); break; } Skip(); } tokens.Enqueue(new YamlDotNet.Core.Tokens.DocumentEnd(in start, in start)); if (token != null) { tokens.Enqueue(token); } } private void FetchFlowCollectionStart(bool isSequenceToken) { SaveSimpleKey(); IncreaseFlowLevel(); simpleKeyAllowed = true; Mark start = cursor.Mark(); Skip(); Token item = ((!isSequenceToken) ? ((Token)new FlowMappingStart(in start, in start)) : ((Token)new FlowSequenceStart(in start, in start))); tokens.Enqueue(item); startFlowCollectionFetched = true; } private void IncreaseFlowLevel() { simpleKeys.Push(new SimpleKey()); flowLevel++; } private void FetchFlowCollectionEnd(bool isSequenceToken) { RemoveSimpleKey(); DecreaseFlowLevel(); simpleKeyAllowed = false; Mark start = cursor.Mark(); Skip(); Token token = null; Token item; if (isSequenceToken) { if (analyzer.Check('#')) { token = new Error("While scanning a flow sequence end, found invalid comment after ']'.", start, start); } item = new FlowSequenceEnd(in start, in start); } else { item = new FlowMappingEnd(in start, in start); } tokens.Enqueue(item); if (token != null) { tokens.Enqueue(token); } flowCollectionFetched = true; } private void DecreaseFlowLevel() { if (flowLevel > 0) { flowLevel--; simpleKeys.Pop(); } } private void FetchFlowEntry() { RemoveSimpleKey(); simpleKeyAllowed = true; Mark start = cursor.Mark(); Skip(); Mark end = cursor.Mark(); if (analyzer.Check('#')) { tokens.Enqueue(new Error("While scanning a flow entry, found invalid comment after comma.", start, end)); } else { tokens.Enqueue(new FlowEntry(in start, in end)); } } private void FetchBlockEntry() { if (flowLevel == 0) { if (!simpleKeyAllowed) { if (previousAnchor != null && previousAnchor.End.Line == cursor.Line) { throw new SemanticErrorException(previousAnchor.Start, previousAnchor.End, "Anchor before sequence entry on same line is not allowed."); } Mark mark = cursor.Mark(); tokens.Enqueue(new Error("Block sequence entries are not allowed in this context.", mark, mark)); } RollIndent(cursor.LineOffset, -1, isSequence: true, cursor.Mark()); } RemoveSimpleKey(); simpleKeyAllowed = true; Mark start = cursor.Mark(); Skip(); tokens.Enqueue(new BlockEntry(in start, cursor.Mark())); } private void FetchKey() { if (flowLevel == 0) { if (!simpleKeyAllowed) { Mark start = cursor.Mark(); throw new SyntaxErrorException(in start, in start, "Mapping keys are not allowed in this context."); } RollIndent(cursor.LineOffset, -1, isSequence: false, cursor.Mark()); } RemoveSimpleKey(); simpleKeyAllowed = flowLevel == 0; Mark start2 = cursor.Mark(); Skip(); tokens.Enqueue(new Key(in start2, cursor.Mark())); } private void FetchValue() { SimpleKey simpleKey = simpleKeys.Peek(); if (simpleKey.IsPossible) { tokens.Insert(simpleKey.TokenNumber - tokensParsed, new Key(simpleKey.Mark, simpleKey.Mark)); RollIndent(simpleKey.LineOffset, simpleKey.TokenNumber, isSequence: false, simpleKey.Mark); simpleKey.MarkAsImpossible(); simpleKeyAllowed = false; } else { bool flag = flowLevel == 0; if (flag) { if (!simpleKeyAllowed) { Mark mark = cursor.Mark(); tokens.Enqueue(new Error("Mapping values are not allowed in this context.", mark, mark)); return; } RollIndent(cursor.LineOffset, -1, isSequence: false, cursor.Mark()); if (cursor.LineOffset == 0L && simpleKey.LineOffset == 0L) { tokens.Insert(tokens.Count, new Key(simpleKey.Mark, simpleKey.Mark)); flag = false; } } simpleKeyAllowed = flag; } Mark start = cursor.Mark(); Skip(); tokens.Enqueue(new Value(in start, cursor.Mark())); } private void RollIndent(long column, int number, bool isSequence, Mark position) { if (flowLevel <= 0 && indent < column) { indents.Push(indent); indent = column; Token item = ((!isSequence) ? ((Token)new BlockMappingStart(in position, in position)) : ((Token)new BlockSequenceStart(in position, in position))); if (number == -1) { tokens.Enqueue(item); } else { tokens.Insert(number - tokensParsed, item); } } } private void FetchAnchor(bool isAlias) { SaveSimpleKey(); simpleKeyAllowed = false; tokens.Enqueue(ScanAnchor(isAlias)); } private Token ScanAnchor(bool isAlias) { Mark start = cursor.Mark(); Skip(); bool flag = false; if (isAlias) { SimpleKey simpleKey = simpleKeys.Peek(); flag = simpleKey.IsRequired && simpleKey.IsPossible; } StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; while (!analyzer.IsWhiteBreakOrZero() && !analyzer.Check("[]{},") && (!flag || !analyzer.Check(':') || !analyzer.IsWhiteBreakOrZero(1))) { builder.Append(ReadCurrentCharacter()); } if (builder.Length == 0 || (!analyzer.IsWhiteBreakOrZero() && !analyzer.Check("?:,]}%@`"))) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning an anchor or alias, found value containing disallowed: []{},"); } AnchorName value = new AnchorName(builder.ToString()); if (isAlias) { return new YamlDotNet.Core.Tokens.AnchorAlias(value, start, cursor.Mark()); } return previousAnchor = new Anchor(value, start, cursor.Mark()); } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } } private void FetchTag() { SaveSimpleKey(); simpleKeyAllowed = false; tokens.Enqueue(ScanTag()); } private Tag ScanTag() { Mark start = cursor.Mark(); string text; string text2; if (analyzer.Check('<', 1)) { text = string.Empty; Skip(); Skip(); text2 = ScanTagUri(null, start); if (!analyzer.Check('>')) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a tag, did not find the expected '>'."); } Skip(); } else { string text3 = ScanTagHandle(isDirective: false, start); if (text3.Length > 1 && text3[0] == '!' && text3[text3.Length - 1] == '!') { text = text3; text2 = ScanTagUri(null, start); } else { text2 = ScanTagUri(text3, start); text = "!"; if (text2.Length == 0) { text2 = text; text = string.Empty; } } } if (!analyzer.IsWhiteBreakOrZero() && !analyzer.Check(',')) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a tag, did not find expected whitespace, comma or line break."); } return new Tag(text, text2, start, cursor.Mark()); } private void FetchBlockScalar(bool isLiteral) { SaveSimpleKey(); simpleKeyAllowed = true; tokens.Enqueue(ScanBlockScalar(isLiteral)); } private YamlDotNet.Core.Tokens.Scalar ScanBlockScalar(bool isLiteral) { StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; StringBuilderPool.BuilderWrapper builderWrapper2 = StringBuilderPool.Rent(); try { StringBuilder builder2 = builderWrapper2.Builder; StringBuilderPool.BuilderWrapper builderWrapper3 = StringBuilderPool.Rent(); try { StringBuilder builder3 = builderWrapper3.Builder; int num = 0; int num2 = 0; long currentIndent = 0L; bool flag = false; bool? isFirstLine = null; Mark start = cursor.Mark(); Skip(); if (analyzer.Check("+-")) { num = (analyzer.Check('+') ? 1 : (-1)); Skip(); if (analyzer.IsDigit()) { if (analyzer.Check('0')) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a block scalar, found an indentation indicator equal to 0."); } num2 = analyzer.AsDigit(); Skip(); } } else if (analyzer.IsDigit()) { if (analyzer.Check('0')) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a block scalar, found an indentation indicator equal to 0."); } num2 = analyzer.AsDigit(); Skip(); if (analyzer.Check("+-")) { num = (analyzer.Check('+') ? 1 : (-1)); Skip(); } } if (analyzer.Check('#')) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a block scalar, found a comment without whtespace after '>' indicator."); } while (analyzer.IsWhite()) { Skip(); } ProcessComment(); if (!analyzer.IsBreakOrZero()) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a block scalar, did not find expected comment or line break."); } if (analyzer.IsBreak()) { SkipLine(); if (!isFirstLine.HasValue) { isFirstLine = true; } else if (isFirstLine == true) { isFirstLine = false; } } Mark end = cursor.Mark(); if (num2 != 0) { currentIndent = ((indent >= 0) ? (indent + num2) : num2); } currentIndent = ScanBlockScalarBreaks(currentIndent, builder3, isLiteral, ref end, ref isFirstLine); isFirstLine = false; while (cursor.LineOffset == currentIndent && !analyzer.IsZero() && !IsDocumentEnd()) { bool flag2 = analyzer.IsWhite(); if (!isLiteral && StartsWith(builder2, '\n') && !flag && !flag2) { if (builder3.Length == 0) { builder.Append(' '); } builder2.Length = 0; } else { builder.Append((object?)builder2); builder2.Length = 0; } builder.Append((object?)builder3); builder3.Length = 0; flag = analyzer.IsWhite(); while (!analyzer.IsBreakOrZero()) { builder.Append(ReadCurrentCharacter()); } char c = ReadLine(); if (c != 0) { builder2.Append(c); } currentIndent = ScanBlockScalarBreaks(currentIndent, builder3, isLiteral, ref end, ref isFirstLine); } if (num != -1) { builder.Append((object?)builder2); } if (num == 1) { builder.Append((object?)builder3); } ScalarStyle style = (isLiteral ? ScalarStyle.Literal : ScalarStyle.Folded); return new YamlDotNet.Core.Tokens.Scalar(builder.ToString(), style, start, end); } finally { ((IDisposable)builderWrapper3/*cast due to .constrained prefix*/).Dispose(); } } finally { ((IDisposable)builderWrapper2/*cast due to .constrained prefix*/).Dispose(); } } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } } private long ScanBlockScalarBreaks(long currentIndent, StringBuilder breaks, bool isLiteral, ref Mark end, ref bool? isFirstLine) { long num = 0L; long num2 = -1L; end = cursor.Mark(); while (true) { if ((currentIndent == 0L || cursor.LineOffset < currentIndent) && analyzer.IsSpace()) { Skip(); continue; } if (cursor.LineOffset > num) { num = cursor.LineOffset; } if (!analyzer.IsBreak()) { break; } if (isFirstLine == true) { isFirstLine = false; num2 = cursor.LineOffset; } breaks.Append(ReadLine()); end = cursor.Mark(); } if (isLiteral && isFirstLine == true) { long num3 = cursor.LineOffset; int num4 = 0; while (!analyzer.IsBreak(num4) && analyzer.IsSpace(num4)) { num4++; num3++; } if (analyzer.IsBreak(num4) && num3 > cursor.LineOffset) { isFirstLine = false; num2 = num3; } } if (isLiteral && num2 > 1 && currentIndent < num2 - 1) { throw new SemanticErrorException(in end, cursor.Mark(), "While scanning a literal block scalar, found extra spaces in first line."); } if (!isLiteral && num > cursor.LineOffset && num2 > -1) { throw new SemanticErrorException(in end, cursor.Mark(), "While scanning a literal block scalar, found more spaces in lines above first content line."); } if (currentIndent == 0L && (cursor.LineOffset > 0 || indent > -1)) { currentIndent = Math.Max(num, Math.Max(indent + 1, 1L)); } return currentIndent; } private void FetchQuotedScalar(bool isSingleQuoted) { SaveSimpleKey(); simpleKeyAllowed = false; flowScalarFetched = flowLevel > 0; YamlDotNet.Core.Tokens.Scalar item = ScanFlowScalar(isSingleQuoted); tokens.Enqueue(item); lastScalar = item; if (!isSingleQuoted && analyzer.Check('#')) { Mark mark = cursor.Mark(); tokens.Enqueue(new Error("While scanning a flow sequence end, found invalid comment after double-quoted scalar.", mark, mark)); } } private YamlDotNet.Core.Tokens.Scalar ScanFlowScalar(bool isSingleQuoted) { Mark start = cursor.Mark(); Skip(); StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; StringBuilderPool.BuilderWrapper builderWrapper2 = StringBuilderPool.Rent(); try { StringBuilder builder2 = builderWrapper2.Builder; StringBuilderPool.BuilderWrapper builderWrapper3 = StringBuilderPool.Rent(); try { StringBuilder builder3 = builderWrapper3.Builder; StringBuilderPool.BuilderWrapper builderWrapper4 = StringBuilderPool.Rent(); try { StringBuilder builder4 = builderWrapper4.Builder; bool flag = false; while (true) { if (IsDocumentIndicator()) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a quoted scalar, found unexpected document indicator."); } if (analyzer.IsZero()) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a quoted scalar, found unexpected end of stream."); } if (flag && !isSingleQuoted && indent >= cursor.LineOffset) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a multi-line double-quoted scalar, found wrong indentation."); } flag = false; while (!analyzer.IsWhiteBreakOrZero()) { if (isSingleQuoted && analyzer.Check('\'') && analyzer.Check('\'', 1)) { builder.Append('\''); Skip(); Skip(); continue; } if (analyzer.Check(isSingleQuoted ? '\'' : '"')) { break; } if (!isSingleQuoted && analyzer.Check('\\') && analyzer.IsBreak(1)) { Skip(); SkipLine(); flag = true; break; } if (!isSingleQuoted && analyzer.Check('\\')) { int num = 0; char c = analyzer.Peek(1); switch (c) { case 'x': num = 2; break; case 'u': num = 4; break; case 'U': num = 8; break; default: { if (SimpleEscapeCodes.TryGetValue(c, out var value)) { builder.Append(value); break; } throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a quoted scalar, found unknown escape character."); } } Skip(); Skip(); if (num <= 0) { continue; } int num2 = 0; for (int i = 0; i < num; i++) { if (!analyzer.IsHex(i)) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a quoted scalar, did not find expected hexadecimal number."); } num2 = (num2 << 4) + analyzer.AsHex(i); } if (num2 >= 55296 && num2 <= 57343) { for (int j = 0; j < num; j++) { Skip(); } if (analyzer.Peek(0) != '\\' || (analyzer.Peek(1) != 'u' && analyzer.Peek(1) != 'U')) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a quoted scalar, found invalid Unicode surrogates."); } Skip(); num = ((analyzer.Peek(0) != 'u') ? 8 : 4); Skip(); int num3 = 0; for (int k = 0; k < num; k++) { if (!analyzer.IsHex(0)) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a quoted scalar, did not find expected hexadecimal number."); } num3 = (num3 << 4) + analyzer.AsHex(k); } for (int l = 0; l < num; l++) { Skip(); } num2 = char.ConvertToUtf32((char)num2, (char)num3); } else { if (num2 > 1114111) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a quoted scalar, found invalid Unicode character escape code."); } for (int m = 0; m < num; m++) { Skip(); } } builder.Append(char.ConvertFromUtf32(num2)); } else { builder.Append(ReadCurrentCharacter()); } } if (analyzer.Check(isSingleQuoted ? '\'' : '"')) { break; } while (analyzer.IsWhite() || analyzer.IsBreak()) { if (analyzer.IsWhite()) { if (!flag) { builder2.Append(ReadCurrentCharacter()); } else { Skip(); } } else if (!flag) { builder2.Length = 0; builder3.Append(ReadLine()); flag = true; } else { builder4.Append(ReadLine()); } } if (flag) { if (StartsWith(builder3, '\n')) { if (builder4.Length == 0) { builder.Append(' '); } else { builder.Append((object?)builder4); } } else { builder.Append((object?)builder3); builder.Append((object?)builder4); } builder3.Length = 0; builder4.Length = 0; } else { builder.Append((object?)builder2); builder2.Length = 0; } } Skip(); return new YamlDotNet.Core.Tokens.Scalar(builder.ToString(), isSingleQuoted ? ScalarStyle.SingleQuoted : ScalarStyle.DoubleQuoted, start, cursor.Mark()); } finally { ((IDisposable)builderWrapper4/*cast due to .constrained prefix*/).Dispose(); } } finally { ((IDisposable)builderWrapper3/*cast due to .constrained prefix*/).Dispose(); } } finally { ((IDisposable)builderWrapper2/*cast due to .constrained prefix*/).Dispose(); } } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } } private void FetchPlainScalar() { SaveSimpleKey(); simpleKeyAllowed = false; bool isMultiline = false; YamlDotNet.Core.Tokens.Scalar item = (lastScalar = ScanPlainScalar(ref isMultiline)); if (isMultiline && analyzer.Check(':') && flowLevel == 0 && indent < cursor.LineOffset) { tokens.Enqueue(new Error("While scanning a multiline plain scalar, found invalid mapping.", cursor.Mark(), cursor.Mark())); } tokens.Enqueue(item); } private YamlDotNet.Core.Tokens.Scalar ScanPlainScalar(ref bool isMultiline) { StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; StringBuilderPool.BuilderWrapper builderWrapper2 = StringBuilderPool.Rent(); try { StringBuilder builder2 = builderWrapper2.Builder; StringBuilderPool.BuilderWrapper builderWrapper3 = StringBuilderPool.Rent(); try { StringBuilder builder3 = builderWrapper3.Builder; StringBuilderPool.BuilderWrapper builderWrapper4 = StringBuilderPool.Rent(); try { StringBuilder builder4 = builderWrapper4.Builder; bool flag = false; long num = indent + 1; Mark start = cursor.Mark(); Mark end = start; SimpleKey simpleKey = simpleKeys.Peek(); while (!IsDocumentIndicator()) { if (analyzer.Check('#')) { if (indent < 0 && flowLevel == 0) { plainScalarFollowedByComment = true; } break; } bool flag2 = analyzer.Check('*') && (!simpleKey.IsPossible || !simpleKey.IsRequired); while (!analyzer.IsWhiteBreakOrZero()) { if ((analyzer.Check(':') && !flag2 && (analyzer.IsWhiteBreakOrZero(1) || (flowLevel > 0 && analyzer.Check(',', 1)))) || (flowLevel > 0 && analyzer.Check(",[]{}"))) { if (flowLevel == 0 && !simpleKey.IsPossible) { tokens.Enqueue(new Error("While scanning a plain scalar value, found invalid mapping.", cursor.Mark(), cursor.Mark())); } break; } if (flag || builder2.Length > 0) { if (flag) { if (StartsWith(builder3, '\n')) { if (builder4.Length == 0) { builder.Append(' '); } else { builder.Append((object?)builder4); } } else { builder.Append((object?)builder3); builder.Append((object?)builder4); } builder3.Length = 0; builder4.Length = 0; flag = false; } else { builder.Append((object?)builder2); builder2.Length = 0; } } if (flowLevel > 0 && cursor.LineOffset < num) { throw new InvalidOperationException(); } builder.Append(ReadCurrentCharacter()); end = cursor.Mark(); } if (!analyzer.IsWhite() && !analyzer.IsBreak()) { break; } while (analyzer.IsWhite() || analyzer.IsBreak()) { if (analyzer.IsWhite()) { if (flag && cursor.LineOffset < num && analyzer.IsTab()) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a plain scalar, found a tab character that violate indentation."); } if (!flag) { builder2.Append(ReadCurrentCharacter()); } else { Skip(); } } else { isMultiline = true; if (!flag) { builder2.Length = 0; builder3.Append(ReadLine()); flag = true; } else { builder4.Append(ReadLine()); } } } if (flowLevel == 0 && cursor.LineOffset < num) { break; } } if (flag) { simpleKeyAllowed = true; } return new YamlDotNet.Core.Tokens.Scalar(builder.ToString(), ScalarStyle.Plain, start, end); } finally { ((IDisposable)builderWrapper4/*cast due to .constrained prefix*/).Dispose(); } } finally { ((IDisposable)builderWrapper3/*cast due to .constrained prefix*/).Dispose(); } } finally { ((IDisposable)builderWrapper2/*cast due to .constrained prefix*/).Dispose(); } } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } } private void RemoveSimpleKey() { SimpleKey simpleKey = simpleKeys.Peek(); if (simpleKey.IsPossible && simpleKey.IsRequired) { throw new SyntaxErrorException(simpleKey.Mark, simpleKey.Mark, "While scanning a simple key, could not find expected ':'."); } simpleKey.MarkAsImpossible(); } private string ScanDirectiveName(in Mark start) { StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; while (analyzer.IsAlphaNumericDashOrUnderscore()) { builder.Append(ReadCurrentCharacter()); } if (builder.Length == 0) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a directive, could not find expected directive name."); } if (analyzer.EndOfInput) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a directive, found unexpected end of stream."); } if (!analyzer.IsWhiteBreakOrZero()) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a directive, found unexpected non-alphabetical character."); } return builder.ToString(); } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } } private void SkipWhitespaces() { while (analyzer.IsWhite()) { Skip(); } } private VersionDirective ScanVersionDirectiveValue(in Mark start) { SkipWhitespaces(); int major = ScanVersionDirectiveNumber(in start); if (!analyzer.Check('.')) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a %YAML directive, did not find expected digit or '.' character."); } Skip(); int minor = ScanVersionDirectiveNumber(in start); return new VersionDirective(new Version(major, minor), start, start); } private TagDirective ScanTagDirectiveValue(in Mark start) { SkipWhitespaces(); string handle = ScanTagHandle(isDirective: true, start); if (!analyzer.IsWhite()) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a %TAG directive, did not find expected whitespace."); } SkipWhitespaces(); string prefix = ScanTagUri(null, start); if (!analyzer.IsWhiteBreakOrZero()) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a %TAG directive, did not find expected whitespace or line break."); } return new TagDirective(handle, prefix, start, start); } private string ScanTagUri(string? head, Mark start) { StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; if (head != null && head.Length > 1) { builder.Append(head.Substring(1)); } while (analyzer.IsAlphaNumericDashOrUnderscore() || analyzer.Check(";/?:@&=+$.!~*'()[]%") || (analyzer.Check(',') && !analyzer.IsBreak(1))) { if (analyzer.Check('%')) { builder.Append(ScanUriEscapes(in start)); } else if (analyzer.Check('+')) { builder.Append(' '); Skip(); } else { builder.Append(ReadCurrentCharacter()); } } if (builder.Length == 0) { return string.Empty; } string text = builder.ToString(); if (Polyfills.EndsWith(text, ',')) { throw new SyntaxErrorException(cursor.Mark(), cursor.Mark(), "Unexpected comma at end of tag"); } return text; } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } } private string ScanUriEscapes(in Mark start) { byte[] array = EmptyBytes; int count = 0; int num = 0; do { if (!analyzer.Check('%') || !analyzer.IsHex(1) || !analyzer.IsHex(2)) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a tag, did not find URI escaped octet."); } int num2 = (analyzer.AsHex(1) << 4) + analyzer.AsHex(2); if (num == 0) { num = (((num2 & 0x80) == 0) ? 1 : (((num2 & 0xE0) == 192) ? 2 : (((num2 & 0xF0) == 224) ? 3 : (((num2 & 0xF8) == 240) ? 4 : 0)))); if (num == 0) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a tag, found an incorrect leading UTF-8 octet."); } array = new byte[num]; } else if ((num2 & 0xC0) != 128) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a tag, found an incorrect trailing UTF-8 octet."); } array[count++] = (byte)num2; Skip(); Skip(); Skip(); } while (--num > 0); string text = Encoding.UTF8.GetString(array, 0, count); if (text.Length == 0 || text.Length > 2) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a tag, found an incorrect UTF-8 sequence."); } return text; } private string ScanTagHandle(bool isDirective, Mark start) { if (!analyzer.Check('!')) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a tag, did not find expected '!'."); } StringBuilderPool.BuilderWrapper builderWrapper = StringBuilderPool.Rent(); try { StringBuilder builder = builderWrapper.Builder; builder.Append(ReadCurrentCharacter()); while (analyzer.IsAlphaNumericDashOrUnderscore()) { builder.Append(ReadCurrentCharacter()); } if (analyzer.Check('!')) { builder.Append(ReadCurrentCharacter()); } else if (isDirective && (builder.Length != 1 || builder[0] != '!')) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a tag directive, did not find expected '!'."); } return builder.ToString(); } finally { ((IDisposable)builderWrapper/*cast due to .constrained prefix*/).Dispose(); } } private int ScanVersionDirectiveNumber(in Mark start) { int num = 0; int num2 = 0; while (analyzer.IsDigit()) { if (++num2 > 9) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a %YAML directive, found extremely long version number."); } num = num * 10 + analyzer.AsDigit(); Skip(); } if (num2 == 0) { throw new SyntaxErrorException(in start, cursor.Mark(), "While scanning a %YAML directive, did not find expected version number."); } return num; } private void SaveSimpleKey() { bool isRequired = flowLevel == 0 && indent == cursor.LineOffset; if (simpleKeyAllowed) { SimpleKey item = new SimpleKey(isRequired, tokensParsed + tokens.Count, cursor); RemoveSimpleKey(); simpleKeys.Pop(); simpleKeys.Push(item); } } } internal class SemanticErrorException : YamlException { public SemanticErrorException(string message) : base(message) { } public SemanticErrorException(in Mark start, in Mark end, string message) : base(in start, in end, message) { } public SemanticErrorException(string message, Exception inner) : base(message, inner) { } } internal sealed class SimpleKey { private readonly Cursor cursor; public bool IsPossible { get; private set; } public bool IsRequired { get; } public int TokenNumber { get; } public long Index => cursor.Index; public long Line => cursor.Line; public long LineOffset => cursor.LineOffset; public Mark Mark => cursor.Mark(); public void MarkAsImpossible() { IsPossible = false; } public SimpleKey() { cursor = new Cursor(); } public SimpleKey(bool isRequired, int tokenNumber, Cursor cursor) { IsPossible = true; IsRequired = isRequired; TokenNumber = tokenNumber; this.cursor = new Cursor(cursor); } } internal sealed class StringLookAheadBuffer : ILookAheadBuffer, IResettable { public string Value { get; set; } = string.Empty; public int Position { get; private set; } public int Length => Value.Length; public bool EndOfInput => IsOutside(Position); public char Peek(int offset) { int index = Position + offset; if (!IsOutside(index)) { return Value[index]; } return '\0'; } private bool IsOutside(int index) { return index >= Value.Length; } public void Skip(int length) { if (length < 0) { throw new ArgumentOutOfRangeException("length", "The length must be positive."); } Position += length; } public bool TryReset() { Position = 0; Value = string.Empty; return true; } } internal sealed class SyntaxErrorException : YamlException { public SyntaxErrorException(string message) : base(message) { } public SyntaxErrorException(in Mark start, in Mark end, string message) : base(in start, in end, message) { } public SyntaxErrorException(string message, Exception inner) : base(message, inner) { } } internal sealed class TagDirectiveCollection : KeyedCollection { public TagDirectiveCollection() { } public TagDirectiveCollection(IEnumerable tagDirectives) { foreach (TagDirective tagDirective in tagDirectives) { Add(tagDirective); } } protected override string GetKeyForItem(TagDirective item) { return item.Handle; } public new bool Contains(TagDirective directive) { return Contains(GetKeyForItem(directive)); } } internal readonly struct TagName : IEquatable { public static readonly TagName Empty; private readonly string? value; public string Value => value ?? throw new InvalidOperationException("Cannot read the Value of a non-specific tag"); public bool IsEmpty => value == null; public bool IsNonSpecific { get { if (!IsEmpty) { if (!(value == "!")) { return value == "?"; } return true; } return false; } } public bool IsLocal { get { if (!IsEmpty) { return Value[0] == '!'; } return false; } } public bool IsGlobal { get { if (!IsEmpty) { return !IsLocal; } return false; } } public TagName(string value) { this.value = value ?? throw new ArgumentNullException("value"); if (value.Length == 0) { throw new ArgumentException("Tag value must not be empty.", "value"); } if (IsGlobal && !Uri.IsWellFormedUriString(value, UriKind.RelativeOrAbsolute)) { throw new ArgumentException("Global tags must be valid URIs.", "value"); } } public override string ToString() { return value ?? "?"; } public bool Equals(TagName other) { return object.Equals(value, other.value); } public override bool Equals(object? obj) { if (obj is TagName other) { return Equals(other); } return false; } public override int GetHashCode() { return value?.GetHashCode() ?? 0; } public static bool operator ==(TagName left, TagName right) { return left.Equals(right); } public static bool operator !=(TagName left, TagName right) { return !(left == right); } public static bool operator ==(TagName left, string right) { return object.Equals(left.value, right); } public static bool operator !=(TagName left, string right) { return !(left == right); } public static implicit operator TagName(string? value) { if (value != null) { return new TagName(value); } return Empty; } } internal sealed class Version { public int Major { get; } public int Minor { get; } public Version(int major, int minor) { if (major < 0) { throw new ArgumentOutOfRangeException("major", $"{major} should be >= 0"); } Major = major; if (minor < 0) { throw new ArgumentOutOfRangeException("minor", $"{minor} should be >= 0"); } Minor = minor; } public override bool Equals(object? obj) { if (obj is Version version && Major == version.Major) { return Minor == version.Minor; } return false; } public override int GetHashCode() { return HashCode.CombineHashCodes(Major.GetHashCode(), Minor.GetHashCode()); } } internal class YamlException : Exception { public Mark Start { get; } public Mark End { get; } public YamlException(string message) : this(in Mark.Empty, in Mark.Empty, message) { } public YamlException(in Mark start, in Mark end, string message) : this(in start, in end, message, null) { } public YamlException(in Mark start, in Mark end, string message, Exception? innerException) : base(message, innerException) { Start = start; End = end; } public YamlException(string message, Exception inner) : this(in Mark.Empty, in Mark.Empty, message, inner) { } public override string ToString() { return $"({Start}) - ({End}): {Message}"; } } } namespace YamlDotNet.Core.Tokens { internal class Anchor : Token { public AnchorName Value { get; } public Anchor(AnchorName value) : this(value, Mark.Empty, Mark.Empty) { } public Anchor(AnchorName value, Mark start, Mark end) : base(in start, in end) { if (value.IsEmpty) { throw new ArgumentNullException("value"); } Value = value; } } internal sealed class AnchorAlias : Token { public AnchorName Value { get; } public AnchorAlias(AnchorName value) : this(value, Mark.Empty, Mark.Empty) { } public AnchorAlias(AnchorName value, Mark start, Mark end) : base(in start, in end) { if (value.IsEmpty) { throw new ArgumentNullException("value"); } Value = value; } } internal sealed class BlockEnd : Token { public BlockEnd() : this(in Mark.Empty, in Mark.Empty) { } public BlockEnd(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class BlockEntry : Token { public BlockEntry() : this(in Mark.Empty, in Mark.Empty) { } public BlockEntry(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class BlockMappingStart : Token { public BlockMappingStart() : this(in Mark.Empty, in Mark.Empty) { } public BlockMappingStart(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class BlockSequenceStart : Token { public BlockSequenceStart() : this(in Mark.Empty, in Mark.Empty) { } public BlockSequenceStart(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class Comment : Token { public string Value { get; } public bool IsInline { get; } public Comment(string value, bool isInline) : this(value, isInline, Mark.Empty, Mark.Empty) { } public Comment(string value, bool isInline, Mark start, Mark end) : base(in start, in end) { Value = value ?? throw new ArgumentNullException("value"); IsInline = isInline; } } internal sealed class DocumentEnd : Token { public DocumentEnd() : this(in Mark.Empty, in Mark.Empty) { } public DocumentEnd(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class DocumentStart : Token { public DocumentStart() : this(in Mark.Empty, in Mark.Empty) { } public DocumentStart(in Mark start, in Mark end) : base(in start, in end) { } } internal class Error : Token { public string Value { get; } public Error(string value, Mark start, Mark end) : base(in start, in end) { Value = value; } } internal sealed class FlowEntry : Token { public FlowEntry() : this(in Mark.Empty, in Mark.Empty) { } public FlowEntry(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class FlowMappingEnd : Token { public FlowMappingEnd() : this(in Mark.Empty, in Mark.Empty) { } public FlowMappingEnd(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class FlowMappingStart : Token { public FlowMappingStart() : this(in Mark.Empty, in Mark.Empty) { } public FlowMappingStart(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class FlowSequenceEnd : Token { public FlowSequenceEnd() : this(in Mark.Empty, in Mark.Empty) { } public FlowSequenceEnd(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class FlowSequenceStart : Token { public FlowSequenceStart() : this(in Mark.Empty, in Mark.Empty) { } public FlowSequenceStart(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class Key : Token { public Key() : this(in Mark.Empty, in Mark.Empty) { } public Key(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class Scalar : Token { public bool IsKey { get; set; } public string Value { get; } public ScalarStyle Style { get; } public Scalar(string value) : this(value, ScalarStyle.Any) { } public Scalar(string value, ScalarStyle style) : this(value, style, Mark.Empty, Mark.Empty) { } public Scalar(string value, ScalarStyle style, Mark start, Mark end) : base(in start, in end) { Value = value ?? throw new ArgumentNullException("value"); Style = style; } } internal sealed class StreamEnd : Token { public StreamEnd() : this(in Mark.Empty, in Mark.Empty) { } public StreamEnd(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class StreamStart : Token { public StreamStart() : this(in Mark.Empty, in Mark.Empty) { } public StreamStart(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class Tag : Token { public string Handle { get; } public string Suffix { get; } public Tag(string handle, string suffix) : this(handle, suffix, Mark.Empty, Mark.Empty) { } public Tag(string handle, string suffix, Mark start, Mark end) : base(in start, in end) { Handle = handle ?? throw new ArgumentNullException("handle"); Suffix = suffix ?? throw new ArgumentNullException("suffix"); } } internal class TagDirective : Token { private static readonly Regex TagHandlePattern = new Regex("^!([0-9A-Za-z_\\-]*!)?$", RegexOptions.Compiled); public string Handle { get; } public string Prefix { get; } public TagDirective(string handle, string prefix) : this(handle, prefix, Mark.Empty, Mark.Empty) { } public TagDirective(string handle, string prefix, Mark start, Mark end) : base(in start, in end) { if (string.IsNullOrEmpty(handle)) { throw new ArgumentNullException("handle", "Tag handle must not be empty."); } if (!TagHandlePattern.IsMatch(handle)) { throw new ArgumentException("Tag handle must start and end with '!' and contain alphanumerical characters only.", "handle"); } Handle = handle; if (string.IsNullOrEmpty(prefix)) { throw new ArgumentNullException("prefix", "Tag prefix must not be empty."); } Prefix = prefix; } public override bool Equals(object? obj) { if (obj is TagDirective tagDirective && Handle.Equals(tagDirective.Handle)) { return Prefix.Equals(tagDirective.Prefix); } return false; } public override int GetHashCode() { return Handle.GetHashCode() ^ Prefix.GetHashCode(); } public override string ToString() { return Handle + " => " + Prefix; } } internal abstract class Token { public Mark Start { get; } public Mark End { get; } protected Token(in Mark start, in Mark end) { Start = start; End = end; } } internal sealed class Value : Token { public Value() : this(in Mark.Empty, in Mark.Empty) { } public Value(in Mark start, in Mark end) : base(in start, in end) { } } internal sealed class VersionDirective : Token { public Version Version { get; } public VersionDirective(Version version) : this(version, Mark.Empty, Mark.Empty) { } public VersionDirective(Version version, Mark start, Mark end) : base(in start, in end) { Version = version; } public override bool Equals(object? obj) { if (obj is VersionDirective versionDirective) { return Version.Equals(versionDirective.Version); } return false; } public override int GetHashCode() { return Version.GetHashCode(); } } } namespace YamlDotNet.Core.ObjectPool { internal class DefaultObjectPool : ObjectPool where T : class { private readonly Func createFunc; private readonly Func returnFunc; private readonly int maxCapacity; private int numItems; private protected readonly ConcurrentQueue items = new ConcurrentQueue(); private protected T? fastItem; public DefaultObjectPool(IPooledObjectPolicy policy) : this(policy, Environment.ProcessorCount * 2) { } public DefaultObjectPool(IPooledObjectPolicy policy, int maximumRetained) { createFunc = policy.Create; returnFunc = policy.Return; maxCapacity = maximumRetained - 1; } public override T Get() { T result = fastItem; if (result == null || Interlocked.CompareExchange(ref fastItem, null, result) != result) { if (items.TryDequeue(out result)) { Interlocked.Decrement(ref numItems); return result; } return createFunc(); } return result; } public override void Return(T obj) { ReturnCore(obj); } private protected bool ReturnCore(T obj) { if (!returnFunc(obj)) { return false; } if (fastItem != null || Interlocked.CompareExchange(ref fastItem, obj, null) != null) { if (Interlocked.Increment(ref numItems) <= maxCapacity) { items.Enqueue(obj); return true; } Interlocked.Decrement(ref numItems); return false; } return true; } } internal class DefaultPooledObjectPolicy : IPooledObjectPolicy where T : class, new() { public T Create() { return new T(); } public bool Return(T obj) { if (obj is IResettable resettable) { return resettable.TryReset(); } return true; } } internal interface IPooledObjectPolicy where T : notnull { T Create(); bool Return(T obj); } internal interface IResettable { bool TryReset(); } internal abstract class ObjectPool where T : class { public abstract T Get(); public abstract void Return(T obj); } internal static class ObjectPool { public static ObjectPool Create(IPooledObjectPolicy? policy = null) where T : class, new() { return new DefaultObjectPool(policy ?? new DefaultPooledObjectPolicy()); } public static ObjectPool Create(int maximumRetained, IPooledObjectPolicy? policy = null) where T : class, new() { return new DefaultObjectPool(policy ?? new DefaultPooledObjectPolicy(), maximumRetained); } } [DebuggerStepThrough] internal static class StringBuilderPool { internal readonly struct BuilderWrapper : IDisposable { public readonly StringBuilder Builder; private readonly ObjectPool pool; public BuilderWrapper(StringBuilder builder, ObjectPool pool) { Builder = builder; this.pool = pool; } public override string ToString() { return Builder.ToString(); } public void Dispose() { pool.Return(Builder); } } private static readonly ObjectPool Pool = ObjectPool.Create(new StringBuilderPooledObjectPolicy { InitialCapacity = 16, MaximumRetainedCapacity = 1024 }); public static BuilderWrapper Rent() { StringBuilder builder = Pool.Get(); return new BuilderWrapper(builder, Pool); } } internal class StringBuilderPooledObjectPolicy : IPooledObjectPolicy { public int InitialCapacity { get; set; } = 100; public int MaximumRetainedCapacity { get; set; } = 4096; public StringBuilder Create() { return new StringBuilder(InitialCapacity); } public bool Return(StringBuilder obj) { if (obj.Capacity > MaximumRetainedCapacity) { return false; } obj.Clear(); return true; } } internal static class StringLookAheadBufferPool { internal readonly struct BufferWrapper : IDisposable { public readonly StringLookAheadBuffer Buffer; private readonly ObjectPool pool; public BufferWrapper(StringLookAheadBuffer buffer, ObjectPool pool) { Buffer = buffer; this.pool = pool; } public override string ToString() { return Buffer.ToString(); } public void Dispose() { pool.Return(Buffer); } } private static readonly ObjectPool Pool = ObjectPool.Create(new DefaultPooledObjectPolicy()); public static BufferWrapper Rent(string value) { StringLookAheadBuffer stringLookAheadBuffer = Pool.Get(); stringLookAheadBuffer.Value = value; return new BufferWrapper(stringLookAheadBuffer, Pool); } } } namespace YamlDotNet.Core.Events { internal sealed class AnchorAlias : ParsingEvent { internal override EventType Type => EventType.Alias; public AnchorName Value { get; } public AnchorAlias(AnchorName value, Mark start, Mark end) : base(in start, in end) { if (value.IsEmpty) { throw new YamlException(in start, in end, "Anchor value must not be empty."); } Value = value; } public AnchorAlias(AnchorName value) : this(value, Mark.Empty, Mark.Empty) { } public override string ToString() { return $"Alias [value = {Value}]"; } public override void Accept(IParsingEventVisitor visitor) { visitor.Visit(this); } } internal sealed class Comment : ParsingEvent { public string Value { get; } public bool IsInline { get; } internal override EventType Type => EventType.Comment; public Comment(string value, bool isInline) : this(value, isInline, Mark.Empty, Mark.Empty) { } public Comment(string value, bool isInline, Mark start, Mark end) : base(in start, in end) { Value = value; IsInline = isInline; } public override void Accept(IParsingEventVisitor visitor) { visitor.Visit(this); } public override string ToString() { return (IsInline ? "Inline" : "Block") + " Comment [" + Value + "]"; } } internal sealed class DocumentEnd : ParsingEvent { public override int NestingIncrease => -1; internal override EventType Type => EventType.DocumentEnd; public bool IsImplicit { get; } public DocumentEnd(bool isImplicit, Mark start, Mark end) : base(in start, in end) { IsImplicit = isImplicit; } public DocumentEnd(bool isImplicit) : this(isImplicit, Mark.Empty, Mark.Empty) { } public override string ToString() { return $"Document end [isImplicit = {IsImplicit}]"; } public override void Accept(IParsingEventVisitor visitor) { visitor.Visit(this); } } internal sealed class DocumentStart : ParsingEvent { public override int NestingIncrease => 1; internal override EventType Type => EventType.DocumentStart; public TagDirectiveCollection? Tags { get; } public VersionDirective? Version { get; } public bool IsImplicit { get; } public DocumentStart(VersionDirective? version, TagDirectiveCollection? tags, bool isImplicit, Mark start, Mark end) : base(in start, in end) { Version = version; Tags = tags; IsImplicit = isImplicit; } public DocumentStart(VersionDirective? version, TagDirectiveCollection? tags, bool isImplicit) : this(version, tags, isImplicit, Mark.Empty, Mark.Empty) { } public DocumentStart(in Mark start, in Mark end) : this(null, null, isImplicit: true, start, end) { } public DocumentStart() : this(null, null, isImplicit: true, Mark.Empty, Mark.Empty) { } public override string ToString() { return $"Document start [isImplicit = {IsImplicit}]"; } public override void Accept(IParsingEventVisitor visitor) { visitor.Visit(this); } } internal enum EventType { None, StreamStart, StreamEnd, DocumentStart, DocumentEnd, Alias, Scalar, SequenceStart, SequenceEnd, MappingStart, MappingEnd, Comment } internal interface IParsingEventVisitor { void Visit(AnchorAlias e); void Visit(StreamStart e); void Visit(StreamEnd e); void Visit(DocumentStart e); void Visit(DocumentEnd e); void Visit(Scalar e); void Visit(SequenceStart e); void Visit(SequenceEnd e); void Visit(MappingStart e); void Visit(MappingEnd e); void Visit(Comment e); } internal class MappingEnd : ParsingEvent { public override int NestingIncrease => -1; internal override EventType Type => EventType.MappingEnd; public MappingEnd(in Mark start, in Mark end) : base(in start, in end) { } public MappingEnd() : this(in Mark.Empty, in Mark.Empty) { } public override string ToString() { return "Mapping end"; } public override void Accept(IParsingEventVisitor visitor) { visitor.Visit(this); } } internal sealed class MappingStart : NodeEvent { public override int NestingIncrease => 1; internal override EventType Type => EventType.MappingStart; public bool IsImplicit { get; } public override bool IsCanonical => !IsImplicit; public MappingStyle Style { get; } public MappingStart(AnchorName anchor, TagName tag, bool isImplicit, MappingStyle style, Mark start, Mark end) : base(anchor, tag, start, end) { IsImplicit = isImplicit; Style = style; } public MappingStart(AnchorName anchor, TagName tag, bool isImplicit, MappingStyle style) : this(anchor, tag, isImplicit, style, Mark.Empty, Mark.Empty) { } public MappingStart() : this(AnchorName.Empty, TagName.Empty, isImplicit: true, MappingStyle.Any, Mark.Empty, Mark.Empty) { } public override string ToString() { return $"Mapping start [anchor = {base.Anchor}, tag = {base.Tag}, isImplicit = {IsImplicit}, style = {Style}]"; } public override void Accept(IParsingEventVisitor visitor) { visitor.Visit(this); } } internal enum MappingStyle { Any, Block, Flow } internal abstract class NodeEvent : ParsingEvent { public AnchorName Anchor { get; } public TagName Tag { get; } public abstract bool IsCanonical { get; } protected NodeEvent(AnchorName anchor, TagName tag, Mark start, Mark end) : base(in start, in end) { Anchor = anchor; Tag = tag; } protected NodeEvent(AnchorName anchor, TagName tag) : this(anchor, tag, Mark.Empty, Mark.Empty) { } } internal abstract class ParsingEvent { public virtual int NestingIncrease => 0; internal abstract EventType Type { get; } public Mark Start { get; } public Mark End { get; } public abstract void Accept(IParsingEventVisitor visitor); internal ParsingEvent(in Mark start, in Mark end) { Start = start; End = end; } } internal sealed class Scalar : NodeEvent { internal override EventType Type => EventType.Scalar; public string Value { get; } public ScalarStyle Style { get; } public bool IsPlainImplicit { get; } public bool IsQuotedImplicit { get; } public override bool IsCanonical { get { if (!IsPlainImplicit) { return !IsQuotedImplicit; } return false; } } public bool IsKey { get; } public Scalar(AnchorName anchor, TagName tag, string value, ScalarStyle style, bool isPlainImplicit, bool isQuotedImplicit, Mark start, Mark end, bool isKey = false) : base(anchor, tag, start, end) { Value = value; Style = style; IsPlainImplicit = isPlainImplicit; IsQuotedImplicit = isQuotedImplicit; IsKey = isKey; } public Scalar(AnchorName anchor, TagName tag, string value, ScalarStyle style, bool isPlainImplicit, bool isQuotedImplicit) : this(anchor, tag, value, style, isPlainImplicit, isQuotedImplicit, Mark.Empty, Mark.Empty) { } public Scalar(string value) : this(AnchorName.Empty, TagName.Empty, value, ScalarStyle.Any, isPlainImplicit: true, isQuotedImplicit: true, Mark.Empty, Mark.Empty) { } public Scalar(TagName tag, string value) : this(AnchorName.Empty, tag, value, ScalarStyle.Any, isPlainImplicit: true, isQuotedImplicit: true, Mark.Empty, Mark.Empty) { } public Scalar(AnchorName anchor, TagName tag, string value) : this(anchor, tag, value, ScalarStyle.Any, isPlainImplicit: true, isQuotedImplicit: true, Mark.Empty, Mark.Empty) { } public override string ToString() { return $"Scalar [anchor = {base.Anchor}, tag = {base.Tag}, value = {Value}, style = {Style}, isPlainImplicit = {IsPlainImplicit}, isQuotedImplicit = {IsQuotedImplicit}]"; } public override void Accept(IParsingEventVisitor visitor) { visitor.Visit(this); } } internal sealed class SequenceEnd : ParsingEvent { public override int NestingIncrease => -1; internal override EventType Type => EventType.SequenceEnd; public SequenceEnd(in Mark start, in Mark end) : base(in start, in end) { } public SequenceEnd() : this(in Mark.Empty, in Mark.Empty) { } public override string ToString() { return "Sequence end"; } public override void Accept(IParsingEventVisitor visitor) { visitor.Visit(this); } } internal sealed class SequenceStart : NodeEvent { public override int NestingIncrease => 1; internal override EventType Type => EventType.SequenceStart; public bool IsImplicit { get; } public override bool IsCanonical => !IsImplicit; public SequenceStyle Style { get; } public SequenceStart(AnchorName anchor, TagName tag, bool isImplicit, SequenceStyle style, Mark start, Mark end) : base(anchor, tag, start, end) { IsImplicit = isImplicit; Style = style; } public SequenceStart(AnchorName anchor, TagName tag, bool isImplicit, SequenceStyle style) : this(anchor, tag, isImplicit, style, Mark.Empty, Mark.Empty) { } public override string ToString() { return $"Sequence start [anchor = {base.Anchor}, tag = {base.Tag}, isImplicit = {IsImplicit}, style = {Style}]"; } public override void Accept(IParsingEventVisitor visitor) { visitor.Visit(this); } } internal enum SequenceStyle { Any, Block, Flow } internal sealed class StreamEnd : ParsingEvent { public override int NestingIncrease => -1; internal override EventType Type => EventType.StreamEnd; public StreamEnd(in Mark start, in Mark end) : base(in start, in end) { } public StreamEnd() : this(in Mark.Empty, in Mark.Empty) { } public override string ToString() { return "Stream end"; } public override void Accept(IParsingEventVisitor visitor) { visitor.Visit(this); } } internal sealed class StreamStart : ParsingEvent { public override int NestingIncrease => 1; internal override EventType Type => EventType.StreamStart; public StreamStart() : this(in Mark.Empty, in Mark.Empty) { } public StreamStart(in Mark start, in Mark end) : base(in start, in end) { } public override string ToString() { return "Stream start"; } public override void Accept(IParsingEventVisitor visitor) { visitor.Visit(this); } } } namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)] [ExcludeFromCodeCoverage] [DebuggerNonUserCode] internal sealed class AllowNullAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)] [ExcludeFromCodeCoverage] [DebuggerNonUserCode] internal sealed class DisallowNullAttribute : Attribute { } [AttributeUsage(AttributeTargets.Method, Inherited = false)] [ExcludeFromCodeCoverage] [DebuggerNonUserCode] internal sealed class DoesNotReturnAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [ExcludeFromCodeCoverage] [DebuggerNonUserCode] internal sealed class DoesNotReturnIfAttribute : Attribute { public bool ParameterValue { get; } public DoesNotReturnIfAttribute(bool parameterValue) { ParameterValue = parameterValue; } } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)] [ExcludeFromCodeCoverage] [DebuggerNonUserCode] internal sealed class MaybeNullAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [ExcludeFromCodeCoverage] [DebuggerNonUserCode] internal sealed class MaybeNullWhenAttribute : Attribute { public bool ReturnValue { get; } public MaybeNullWhenAttribute(bool returnValue) { ReturnValue = returnValue; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] [DebuggerNonUserCode] internal sealed class MemberNotNullAttribute : Attribute { public string[] Members { get; } public MemberNotNullAttribute(string member) { Members = new string[1] { member }; } public MemberNotNullAttribute(params string[] members) { Members = members; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] [DebuggerNonUserCode] internal sealed class MemberNotNullWhenAttribute : Attribute { public bool ReturnValue { get; } public string[] Members { get; } public MemberNotNullWhenAttribute(bool returnValue, string member) { ReturnValue = returnValue; Members = new string[1] { member }; } public MemberNotNullWhenAttribute(bool returnValue, params string[] members) { ReturnValue = returnValue; Members = members; } } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)] [ExcludeFromCodeCoverage] [DebuggerNonUserCode] internal sealed class NotNullAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] [ExcludeFromCodeCoverage] [DebuggerNonUserCode] internal sealed class NotNullIfNotNullAttribute : Attribute { public string ParameterName { get; } public NotNullIfNotNullAttribute(string parameterName) { ParameterName = parameterName; } } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [ExcludeFromCodeCoverage] [DebuggerNonUserCode] internal sealed class NotNullWhenAttribute : Attribute { public bool ReturnValue { get; } public NotNullWhenAttribute(bool returnValue) { ReturnValue = returnValue; } } }